aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbjormel <bjormel@yahooinc.com>2023-10-01 12:23:12 +0000
committerbjormel <bjormel@yahooinc.com>2023-10-01 12:23:12 +0000
commite9058b555d4dfea2f6c872d9a677e8678b569569 (patch)
treefa1b67c6e39712c1e0d9f308b0dd55573b43f913
parent0ad931fa86658904fe9212b014d810236b0e00e4 (diff)
parent16030193ec04ee41e98779a3d7ee6a6c1d0d0d6f (diff)
Merge branch 'master' into bjormel/aws-main-controller
-rw-r--r--build_settings.cmake4
-rw-r--r--client/go/internal/admin/jvm/memory.go6
-rw-r--r--client/js/app/yarn.lock177
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationFile.java15
-rw-r--r--config-model-api/abi-spec.json1713
-rw-r--r--config-model-api/pom.xml4
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeployLogger.java5
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/package-info.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/package-info.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java8
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java3
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/OnnxModelCost.java34
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/container/package-info.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java2
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java16
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java4
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java21
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java2
-rw-r--r--config-model/src/main/java/com/yahoo/schema/derived/IndexInfo.java11
-rw-r--r--config-model/src/main/java/com/yahoo/schema/document/Matching.java20
-rw-r--r--config-model/src/main/java/com/yahoo/schema/document/SDField.java9
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/IndexingInputs.java24
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java3
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/NGramMatch.java6
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/PredicateProcessor.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryField.java28
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java5
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidator.java54
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/UrlConfigValidator.java50
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeMessageBuilder.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomComponentBuilder.java16
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java19
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java119
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ContainerThreadpool.java82
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/BertEmbedder.java17
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/ColBertEmbedder.java35
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceEmbedder.java33
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceTokenizer.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/Model.java69
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/chain/ChainedComponent.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java78
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ModelIdResolver.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFiles.java32
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelProbe.java31
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc12
-rw-r--r--config-model/src/test/derived/ngram/chunk.sd20
-rw-r--r--config-model/src/test/derived/ngram/index-info.cfg21
-rw-r--r--config-model/src/test/examples/indexing_attribute_changed.sd8
-rw-r--r--config-model/src/test/examples/indexing_attribute_other.sd8
-rw-r--r--config-model/src/test/examples/indexing_extra_field_input_extra_field.sd12
-rw-r--r--config-model/src/test/examples/indexing_extra_field_input_implicit.sd9
-rw-r--r--config-model/src/test/examples/indexing_extra_field_input_null.sd9
-rw-r--r--config-model/src/test/examples/indexing_extra_field_input_self.sd9
-rw-r--r--config-model/src/test/examples/indexing_index_changed.sd8
-rw-r--r--config-model/src/test/examples/indexing_index_other.sd8
-rw-r--r--config-model/src/test/examples/indexing_modify_field_no_output.sd8
-rw-r--r--config-model/src/test/examples/indexing_output_conflict.sd11
-rw-r--r--config-model/src/test/examples/indexing_output_other_field.sd11
-rw-r--r--config-model/src/test/examples/indexing_summary_other.sd8
-rw-r--r--config-model/src/test/examples/matchphase/non_existing_attribute.sd14
-rw-r--r--config-model/src/test/examples/matchphase/non_fast_search_attribute.sd14
-rw-r--r--config-model/src/test/examples/matchphase/wrong_collection_type_attribute.sd14
-rw-r--r--config-model/src/test/examples/matchphase/wrong_data_type_attribute.sd14
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/MockModelContext.java3
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java7
-rw-r--r--config-model/src/test/java/com/yahoo/schema/AttributeSettingsTestCase.java2
-rw-r--r--config-model/src/test/java/com/yahoo/schema/derived/NGramTestCase.java15
-rw-r--r--config-model/src/test/java/com/yahoo/schema/parser/SchemaParserTestCase.java1
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/AssertIndexingScript.java2
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/AssertSearchBuilder.java29
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/IndexingInputsTestCase.java160
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/IndexingOutputsTestCase.java59
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/IndexingValidationTestCase.java176
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/IndexingValuesTestCase.java45
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/MatchPhaseSettingsValidatorTestCase.java105
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/NGramTestCase.java6
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsConsumersTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java130
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java107
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java59
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java1
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java20
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFilesTest.java16
-rw-r--r--config-model/src/test/schema-test-files/services.xml6
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java9
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java2
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/WireguardKeyWithTimestamp.java39
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java13
-rw-r--r--configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java28
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/FallbackOnnxModelCostProvider.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java18
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java123
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java145
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLogger.java15
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java41
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java56
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java14
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java18
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplication.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFile.java9
-rw-r--r--configserver/src/main/resources/configserver-app/services.xml1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java123
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java4
-rw-r--r--container-disc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/logging/LoggerEntry.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/logging/Spooler.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java4
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/semantics/test/EquivTestCase.java35
-rw-r--r--container-search/src/test/java/com/yahoo/search/querytransform/test/NGramSearcherTestCase.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java10
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MockVpcEndpointService.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/VpcEndpointService.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudAccountInfo.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java7
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java9
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java31
-rw-r--r--controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-east-3.json2
-rw-r--r--controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-west-1.json2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java72
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java48
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java29
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java45
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java46
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java116
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-cloud.json35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java104
-rw-r--r--dependency-versions/pom.xml14
-rw-r--r--document/src/vespa/document/select/parse_utils.cpp2
-rw-r--r--eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp2
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.cpp6
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java38
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java51
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java9
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizer.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateAdapter.java9
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateAdapter.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateHelper.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java14
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapter.java8
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java48
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ConstantExpression.java (renamed from indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueExpression.java)11
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/EmbedExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExecutionContext.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java21
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HashExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java98
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/InputExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/JoinExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java18
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarExpression.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java8
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java6
-rw-r--r--indexinglanguage/src/main/javacc/IndexingParser.jj3
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizerTestCase.java6
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionVisitorTestCase.java5
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ScriptTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapterTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ValueTransformProviderTestCase.java17
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticTestCase.java30
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeTestCase.java6
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64EncodeTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CatTestCase.java20
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpressionTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/EchoTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExactTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionTestCase.java12
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/FlattenTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachTestCase.java16
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldTestCase.java6
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GuardTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeTestCase.java8
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexEncodeTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenTestCase.java53
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/InputTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/JoinTestCase.java6
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/LowerCaseTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/MathResolverTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NGramTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NormalizeTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateTestCase.java12
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OutputAssert.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptTestCase.java22
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputTestCase.java12
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetLanguageTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueTestCase.java18
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarTestCase.java6
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SimpleExpression.java31
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SplitTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/StatementTestCase.java8
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SubstringTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchTestCase.java18
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ThisTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToArrayTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToBoolTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToByteTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToDoubleTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToEpochSecondExpressionTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToFloatTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToIntegerTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToLongTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToPositionTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToStringTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToWsetTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TokenizeTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TrimTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ZCurveTestCase.java4
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ExpressionTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/FieldNameTestCase.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ScriptTestCase.java4
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java55
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java56
-rw-r--r--lucene-linguistics/src/main/java/com/yahoo/language/lucene/AnalyzerFactory.java45
-rw-r--r--lucene-linguistics/src/test/java/com/yahoo/language/lucene/LuceneTokenizerTest.java29
-rw-r--r--maven-plugins/allowed-maven-dependencies.txt111
-rw-r--r--maven-plugins/pom.xml2
-rw-r--r--metrics/src/main/java/ai/vespa/metrics/docs/DocumentationGenerator.java65
-rw-r--r--metrics/src/main/java/ai/vespa/metrics/docs/MetricDocumentation.java57
-rw-r--r--metrics/src/main/java/ai/vespa/metrics/docs/MetricSetDocumentation.java105
-rw-r--r--metrics/src/main/java/ai/vespa/metrics/docs/UnitDocumentation.java56
-rw-r--r--model-integration/src/main/java/ai/vespa/embedding/ColBertEmbedder.java60
-rw-r--r--model-integration/src/test/java/ai/vespa/embedding/ColBertEmbedderTest.java18
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java40
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java65
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetWireguardResponse.java28
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java20
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeer.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepositoryTest.java16
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeerTest.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java106
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java29
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java17
-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.java14
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java15
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java19
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java9
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java102
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java53
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java36
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java73
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java18
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json6
-rw-r--r--parent/pom.xml2
-rw-r--r--searchcore/src/tests/proton/matching/query_test.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp19
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp3
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_thread.cpp76
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_thread.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/query.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/query.h5
-rw-r--r--searchlib/src/tests/attribute/dfa_fuzzy_matcher/dfa_fuzzy_matcher_test.cpp172
-rw-r--r--searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp18
-rw-r--r--searchlib/src/tests/attribute/searchable/attribute_weighted_set_blueprint_test.cpp99
-rw-r--r--searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp20
-rw-r--r--searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp4
-rw-r--r--searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_weighted_set_blueprint.cpp19
-rw-r--r--searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.cpp86
-rw-r--r--searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.h48
-rw-r--r--searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp73
-rw-r--r--searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.h12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/iterator_pack.h8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h134
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp149
-rw-r--r--searchlib/src/vespa/searchlib/attribute/string_matcher.h5
-rw-r--r--searchlib/src/vespa/searchlib/attribute/string_search_helper.cpp60
-rw-r--r--searchlib/src/vespa/searchlib/attribute/string_search_helper.h10
-rw-r--r--searchlib/src/vespa/searchlib/fef/termfieldmatchdata.h66
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/blueprint.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/executeinfo.cpp14
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/executeinfo.h38
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/iterator_pack.h12
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.cpp209
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.h48
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp15
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp15
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.h2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp21
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.h13
-rw-r--r--storage/src/vespa/storage/storageserver/CMakeLists.txt1
-rw-r--r--storage/src/vespa/storage/storageserver/distributornode.cpp2
-rw-r--r--storage/src/vespa/storage/storageserver/opslogger.cpp144
-rw-r--r--storage/src/vespa/storage/storageserver/opslogger.h51
-rw-r--r--storage/src/vespa/storage/storageserver/servicelayernode.cpp2
-rw-r--r--vespa-dependencies-enforcer/allowed-maven-dependencies.txt349
-rw-r--r--vespa-dependencies-enforcer/pom.xml10
-rw-r--r--vespa-enforcer-extensions/pom.xml18
-rw-r--r--vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/AllowedDependencies.java305
-rw-r--r--vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java319
-rw-r--r--vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java108
-rw-r--r--vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt11
-rw-r--r--vespalib/CMakeLists.txt1
-rw-r--r--vespalib/src/tests/fuzzy/levenshtein_dfa_test.cpp50
-rw-r--r--vespalib/src/tests/fuzzy/table_dfa/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/fuzzy/table_dfa/table_dfa_test.cpp395
-rw-r--r--vespalib/src/tests/memorydatastore/memorydatastore.cpp56
-rw-r--r--vespalib/src/tests/stllike/hash_test.cpp8
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreeiterator.h10
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreeiterator.hpp8
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenode.h18
-rw-r--r--vespalib/src/vespa/vespalib/data/memorydatastore.cpp17
-rw-r--r--vespalib/src/vespa/vespalib/data/memorydatastore.h85
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/named_symbol_lookup.h2
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/slime.cpp30
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/slime.h39
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/symbol.h4
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/symbol_table.cpp22
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/symbol_table.h13
-rw-r--r--vespalib/src/vespa/vespalib/datastore/compaction_strategy.h3
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.cpp5
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h3
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/inline_tfa.hpp91
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.cpp16
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.h38
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/match_algorithm.hpp76
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/sparse_state.h6
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/table_dfa.cpp10
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/table_dfa.h63
-rw-r--r--vespalib/src/vespa/vespalib/fuzzy/table_dfa.hpp586
-rw-r--r--vespalib/src/vespa/vespalib/stllike/hashtable.h10
-rw-r--r--vespalib/src/vespa/vespalib/stllike/hashtable.hpp5
-rw-r--r--vespalib/src/vespa/vespalib/text/utf8.h1
-rw-r--r--vespalib/src/vespa/vespalib/util/address_space.h1
-rw-r--r--vespalog/src/vespa/log/log.h1
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java14
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorCompletionWaiter.java12
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java4
424 files changed, 9077 insertions, 3613 deletions
diff --git a/build_settings.cmake b/build_settings.cmake
index fc4ba131969..9192f163a9b 100644
--- a/build_settings.cmake
+++ b/build_settings.cmake
@@ -115,6 +115,10 @@ endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines")
endif()
+if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 17.0)
+ # Turn off dynamic_cast optimization that came with clang 17.0.1
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-assume-unique-vtables")
+endif()
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGOOGLE_PROTOBUF_NO_RDTSC")
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0)
diff --git a/client/go/internal/admin/jvm/memory.go b/client/go/internal/admin/jvm/memory.go
index 8caa1a3be22..2ace1f4ac88 100644
--- a/client/go/internal/admin/jvm/memory.go
+++ b/client/go/internal/admin/jvm/memory.go
@@ -75,11 +75,11 @@ func ParseJvmMemorySpec(spec string) (result AmountOfMemory, err error) {
n, err = fmt.Sscanf(spec, "%d%c", &val, &suffix)
if n == 2 && err == nil {
switch suffix {
- case 'k':
+ case 'k', 'K':
result = KiloBytesOfMemory(val)
- case 'm':
+ case 'm', 'M':
result = MegaBytesOfMemory(int(val))
- case 'g':
+ case 'g', 'G':
result = GigaBytesOfMemory(int(val))
default:
err = fmt.Errorf("Unknown suffix in JVM memory spec '%s'", spec)
diff --git a/client/js/app/yarn.lock b/client/js/app/yarn.lock
index 1c8f628fe18..572c3095570 100644
--- a/client/js/app/yarn.lock
+++ b/client/js/app/yarn.lock
@@ -24,9 +24,9 @@
chalk "^2.4.2"
"@babel/compat-data@^7.22.9":
- version "7.22.9"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730"
- integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
+ integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==
"@babel/core@^7.1.0", "@babel/core@^7.12.17":
version "7.22.9"
@@ -70,21 +70,21 @@
json5 "^2.2.3"
semver "^6.3.1"
-"@babel/core@^7.22.9":
- version "7.22.11"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24"
- integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==
+"@babel/core@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7"
+ integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==
dependencies:
"@ampproject/remapping" "^2.2.0"
- "@babel/code-frame" "^7.22.10"
- "@babel/generator" "^7.22.10"
- "@babel/helper-compilation-targets" "^7.22.10"
- "@babel/helper-module-transforms" "^7.22.9"
- "@babel/helpers" "^7.22.11"
- "@babel/parser" "^7.22.11"
- "@babel/template" "^7.22.5"
- "@babel/traverse" "^7.22.11"
- "@babel/types" "^7.22.11"
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.22.15"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-module-transforms" "^7.22.20"
+ "@babel/helpers" "^7.22.15"
+ "@babel/parser" "^7.22.16"
+ "@babel/template" "^7.22.15"
+ "@babel/traverse" "^7.22.20"
+ "@babel/types" "^7.22.19"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -111,7 +111,7 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
-"@babel/helper-compilation-targets@^7.22.10", "@babel/helper-compilation-targets@^7.22.15":
+"@babel/helper-compilation-targets@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==
@@ -133,10 +133,10 @@
lru-cache "^5.1.1"
semver "^6.3.1"
-"@babel/helper-environment-visitor@^7.22.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
- integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
+"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-function-name@^7.22.5":
version "7.22.5"
@@ -178,6 +178,17 @@
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/helper-validator-identifier" "^7.22.15"
+"@babel/helper-module-transforms@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e"
+ integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-simple-access" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/helper-validator-identifier" "^7.22.20"
+
"@babel/helper-module-transforms@^7.22.5":
version "7.22.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129"
@@ -213,17 +224,17 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
-"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5":
- version "7.22.15"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044"
- integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==
+"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.22.5":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==
-"@babel/helpers@^7.22.11", "@babel/helpers@^7.22.15":
+"@babel/helpers@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1"
integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==
@@ -242,11 +253,11 @@
"@babel/types" "^7.22.11"
"@babel/highlight@^7.22.13":
- version "7.22.13"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16"
- integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
- "@babel/helper-validator-identifier" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
@@ -404,7 +415,7 @@
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
-"@babel/traverse@^7.22.11", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17":
+"@babel/traverse@^7.22.11", "@babel/traverse@^7.22.17":
version "7.22.17"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44"
integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==
@@ -420,6 +431,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9"
+ integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.22.15"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.22.16"
+ "@babel/types" "^7.22.19"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/traverse@^7.22.8":
version "7.22.11"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c"
@@ -436,7 +463,16 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.5", "@babel/types@^7.3.3":
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.19", "@babel/types@^7.22.5":
+ version "7.22.19"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684"
+ integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.19"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.3.3":
version "7.22.17"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee"
integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==
@@ -1244,22 +1280,40 @@
"@types/babel__template" "*"
"@types/babel__traverse" "*"
+"@types/babel__core@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756"
+ integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
"@types/babel__generator@*":
- version "7.6.4"
- resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7"
- integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==
+ version "7.6.5"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.5.tgz#281f4764bcbbbc51fdded0f25aa587b4ce14da95"
+ integrity sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==
dependencies:
"@babel/types" "^7.0.0"
"@types/babel__template@*":
- version "7.4.1"
- resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969"
- integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.2.tgz#843e9f1f47c957553b0c374481dc4772921d6a6b"
+ integrity sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
-"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
+"@types/babel__traverse@*":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.2.tgz#4ddf99d95cfdd946ff35d2b65c978d9c9bf2645d"
+ integrity sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==
+ dependencies:
+ "@babel/types" "^7.20.7"
+
+"@types/babel__traverse@^7.0.6":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf"
integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==
@@ -1337,13 +1391,14 @@
"@types/yargs-parser" "*"
"@vitejs/plugin-react@^4":
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz#31c3f779dc534e045c4b134e7cf7b150af0a7646"
- integrity sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz#e4f56f46fd737c5d386bb1f1ade86ba275fe09bd"
+ integrity sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==
dependencies:
- "@babel/core" "^7.22.9"
+ "@babel/core" "^7.22.20"
"@babel/plugin-transform-react-jsx-self" "^7.22.5"
"@babel/plugin-transform-react-jsx-source" "^7.22.5"
+ "@types/babel__core" "^7.20.2"
react-refresh "^0.14.0"
acorn-jsx@^5.3.2:
@@ -1725,14 +1780,14 @@ braces@^3.0.2:
fill-range "^7.0.1"
browserslist@^4.21.9:
- version "4.21.10"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0"
- integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==
+ version "4.21.11"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.11.tgz#35f74a3e51adc4d193dcd76ea13858de7b8fecb8"
+ integrity sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==
dependencies:
- caniuse-lite "^1.0.30001517"
- electron-to-chromium "^1.4.477"
+ caniuse-lite "^1.0.30001538"
+ electron-to-chromium "^1.4.526"
node-releases "^2.0.13"
- update-browserslist-db "^1.0.11"
+ update-browserslist-db "^1.0.13"
bser@2.1.1:
version "2.1.1"
@@ -1791,10 +1846,10 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-caniuse-lite@^1.0.30001517:
- version "1.0.30001533"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001533.tgz#1180daeb2518b93c82f19b904d1fefcf82197707"
- integrity sha512-9aY/b05NKU4Yl2sbcJhn4A7MsGwR1EPfW/nrqsnqVA0Oq50wpmPaGI+R1Z0UKlUl96oxUkGEOILWtOHck0eCWw==
+caniuse-lite@^1.0.30001538:
+ version "1.0.30001539"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz#325a387ab1ed236df2c12dc6cd43a4fff9903a44"
+ integrity sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2124,10 +2179,10 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
-electron-to-chromium@^1.4.477:
- version "1.4.515"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.515.tgz#f5fec9662106ac5752894af221606cf4db443e70"
- integrity sha512-VTq6vjk3kCfG2qdzQRd/i9dIyVVm0dbtZIgFzrLgfB73mXDQT2HPKVRc1EoZcAVUv9XhXAu08DWqJuababdGGg==
+electron-to-chromium@^1.4.526:
+ version "1.4.528"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz#7c900fd73d9d2e8bb0dab0e301f25f0f4776ef2c"
+ integrity sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==
emittery@^0.13.1:
version "0.13.1"
@@ -5366,10 +5421,10 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
-update-browserslist-db@^1.0.11:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
- integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
dependencies:
escalade "^3.1.1"
picocolors "^1.0.0"
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationFile.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationFile.java
index 6b0adb5d079..2dbbc8a5820 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationFile.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationFile.java
@@ -5,13 +5,20 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.io.IOUtils;
import com.yahoo.path.Path;
-import java.util.logging.Level;
-import com.yahoo.yolean.Exceptions;
import com.yahoo.vespa.config.util.ConfigUtils;
+import com.yahoo.yolean.Exceptions;
-import java.io.*;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -208,6 +215,8 @@ public class FilesApplicationFile extends ApplicationFile {
}
}
+ @Override public long getSize() { return file.length(); }
+
@Override
public int compareTo(ApplicationFile other) {
if (other == this) return 0;
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
new file mode 100644
index 00000000000..2c5be906633
--- /dev/null
+++ b/config-model-api/abi-spec.json
@@ -0,0 +1,1713 @@
+{
+ "com.yahoo.config.application.api.ApplicationFile$MetaData" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public void <init>(java.lang.String, java.lang.String)",
+ "public java.lang.String getStatus()",
+ "public java.lang.String getMd5()"
+ ],
+ "fields" : [
+ "public java.lang.String status",
+ "public java.lang.String md5"
+ ]
+ },
+ "com.yahoo.config.application.api.ApplicationFile$PathFilter" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract boolean accept(com.yahoo.path.Path)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ApplicationFile" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "java.lang.Comparable"
+ ],
+ "attributes" : [
+ "public",
+ "abstract"
+ ],
+ "methods" : [
+ "protected void <init>(com.yahoo.path.Path)",
+ "public abstract boolean isDirectory()",
+ "public abstract boolean exists()",
+ "public abstract java.io.Reader createReader()",
+ "public abstract java.io.InputStream createInputStream()",
+ "public abstract com.yahoo.config.application.api.ApplicationFile createDirectory()",
+ "public abstract com.yahoo.config.application.api.ApplicationFile writeFile(java.io.Reader)",
+ "public abstract com.yahoo.config.application.api.ApplicationFile appendFile(java.lang.String)",
+ "public java.util.List listFiles()",
+ "public abstract java.util.List listFiles(com.yahoo.config.application.api.ApplicationFile$PathFilter)",
+ "public java.util.List listFiles(boolean)",
+ "public abstract com.yahoo.config.application.api.ApplicationFile delete()",
+ "public com.yahoo.path.Path getPath()",
+ "public java.lang.String toString()",
+ "public boolean equals(java.lang.Object)",
+ "protected com.yahoo.path.Path getMetaPath()",
+ "public abstract com.yahoo.config.application.api.ApplicationFile$MetaData getMetaData()",
+ "public abstract long getSize()"
+ ],
+ "fields" : [
+ "public static final java.lang.String ContentStatusNew",
+ "public static final java.lang.String ContentStatusChanged",
+ "public static final java.lang.String ContentStatusDeleted",
+ "protected final com.yahoo.path.Path path"
+ ]
+ },
+ "com.yahoo.config.application.api.ApplicationMetaData" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
+ "public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, com.yahoo.config.provision.Tags, java.lang.String, java.lang.Long, long)",
+ "public void <init>(java.lang.String, java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
+ "public java.lang.String getDeployedByUser()",
+ "public com.yahoo.config.provision.Tags getTags()",
+ "public java.lang.String getDeployPath()",
+ "public com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public java.lang.Long getDeployTimestamp()",
+ "public java.lang.Long getGeneration()",
+ "public boolean isInternalRedeploy()",
+ "public java.lang.String getChecksum()",
+ "public long getPreviousActiveGeneration()",
+ "public java.lang.String toString()",
+ "public static com.yahoo.config.application.api.ApplicationMetaData fromJsonString(java.lang.String)",
+ "public com.yahoo.slime.Slime getSlime()",
+ "public java.lang.String asJsonString()",
+ "public byte[] asJsonBytes()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ApplicationPackage" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public abstract java.io.Reader getServices()",
+ "public abstract java.io.Reader getHosts()",
+ "public java.util.List getUserIncludeDirs()",
+ "public void validateIncludeDir(java.lang.String)",
+ "public abstract java.util.Map getAllExistingConfigDefs()",
+ "public abstract java.util.List getFiles(com.yahoo.path.Path, java.lang.String, boolean)",
+ "public java.util.List getFiles(com.yahoo.path.Path, java.lang.String)",
+ "public java.util.Optional getMajorVersion()",
+ "public abstract com.yahoo.config.application.api.ApplicationFile getFile(com.yahoo.path.Path)",
+ "public java.util.List getQueryProfileFiles()",
+ "public java.util.List getQueryProfileTypeFiles()",
+ "public java.util.List getPageTemplateFiles()",
+ "public com.yahoo.config.application.api.ApplicationFile getClientSecurityFile()",
+ "public abstract java.lang.String getHostSource()",
+ "public abstract java.lang.String getServicesSource()",
+ "public abstract java.util.Optional getDeployment()",
+ "public abstract com.yahoo.config.application.api.DeploymentSpec getDeploymentSpec()",
+ "public com.yahoo.config.application.api.DeploymentSpec parseDeploymentSpec(boolean)",
+ "public abstract java.util.Optional getValidationOverrides()",
+ "public abstract java.util.List getComponentsInfo(com.yahoo.component.Version)",
+ "public abstract java.io.Reader getRankingExpression(java.lang.String)",
+ "public static java.lang.String getFileName(java.util.jar.JarEntry)",
+ "public abstract com.yahoo.config.application.api.ApplicationMetaData getMetaData()",
+ "public abstract java.io.File getFileReference(com.yahoo.path.Path)",
+ "public void validateXML()",
+ "public void validateXMLFor(java.util.Optional)",
+ "public void writeMetaData()",
+ "public java.util.Optional getAllocatedHosts()",
+ "public java.util.Map getFileRegistries()",
+ "public java.util.Map legacyOverrides()",
+ "public abstract java.util.Collection getSchemas()",
+ "public com.yahoo.config.application.api.ApplicationPackage preprocess(com.yahoo.config.provision.Zone, com.yahoo.config.application.api.DeployLogger)"
+ ],
+ "fields" : [
+ "public static final java.lang.String HOSTS",
+ "public static final java.lang.String SERVICES",
+ "public static final com.yahoo.path.Path SCHEMAS_DIR",
+ "public static final com.yahoo.path.Path SEARCH_DEFINITIONS_DIR",
+ "public static final java.lang.String COMPONENT_DIR",
+ "public static final java.lang.String SEARCHCHAINS_DIR",
+ "public static final java.lang.String DOCPROCCHAINS_DIR",
+ "public static final java.lang.String PROCESSORCHAINS_DIR",
+ "public static final java.lang.String ROUTINGTABLES_DIR",
+ "public static final com.yahoo.path.Path MODELS_DIR",
+ "public static final com.yahoo.path.Path MODELS_GENERATED_DIR",
+ "public static final com.yahoo.path.Path MODELS_GENERATED_REPLICATED_DIR",
+ "public static final com.yahoo.path.Path CONSTANTS_DIR",
+ "public static final java.lang.String CONFIG_DEFINITIONS_DIR",
+ "public static final com.yahoo.path.Path QUERY_PROFILES_DIR",
+ "public static final com.yahoo.path.Path QUERY_PROFILE_TYPES_DIR",
+ "public static final com.yahoo.path.Path PAGE_TEMPLATES_DIR",
+ "public static final com.yahoo.path.Path RULES_DIR",
+ "public static final com.yahoo.path.Path DEPLOYMENT_FILE",
+ "public static final com.yahoo.path.Path VALIDATION_OVERRIDES",
+ "public static final com.yahoo.path.Path SECURITY_DIR",
+ "public static final java.lang.String SD_NAME_SUFFIX",
+ "public static final java.lang.String RANKEXPRESSION_NAME_SUFFIX",
+ "public static final java.lang.String RANKPROFILE_NAME_SUFFIX",
+ "public static final java.lang.String RULES_NAME_SUFFIX",
+ "public static final java.lang.String EXT_DIR"
+ ]
+ },
+ "com.yahoo.config.application.api.Bcp$Group" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List, java.time.Duration)",
+ "public java.util.List members()",
+ "public java.util.Set memberRegions()",
+ "public java.time.Duration deadline()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.Bcp$RegionMember" : {
+ "superClass" : "java.lang.Record",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "record"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.RegionName, double)",
+ "public final java.lang.String toString()",
+ "public final int hashCode()",
+ "public final boolean equals(java.lang.Object)",
+ "public com.yahoo.config.provision.RegionName region()",
+ "public double fraction()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.Bcp" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List, java.util.Optional)",
+ "public java.util.Optional defaultDeadline()",
+ "public java.util.List groups()",
+ "public com.yahoo.config.application.api.Bcp withGroups(java.util.List)",
+ "public java.util.Set regions()",
+ "public boolean isEmpty()",
+ "public com.yahoo.config.application.api.Bcp orElse(com.yahoo.config.application.api.Bcp)",
+ "public static com.yahoo.config.application.api.Bcp empty()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ComponentInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String)",
+ "public java.lang.String getPathRelativeToAppDir()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeployLogger" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract void log(java.util.logging.Level, java.lang.String)",
+ "public void log(java.util.logging.Level, java.util.function.Supplier)",
+ "public void log(java.util.logging.Level, java.util.function.Supplier, java.lang.Throwable)",
+ "public void logApplicationPackage(java.util.logging.Level, java.lang.String)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentInstanceSpec" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Steps",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Tags, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, com.yahoo.config.application.api.DeploymentSpec$RevisionTarget, com.yahoo.config.application.api.DeploymentSpec$RevisionChange, com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout, int, int, int, java.util.List, java.util.Optional, java.util.Map, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List, java.util.Map, com.yahoo.config.application.api.Bcp, java.time.Instant)",
+ "public com.yahoo.config.provision.InstanceName name()",
+ "public com.yahoo.config.provision.Tags tags()",
+ "public com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy upgradePolicy()",
+ "public com.yahoo.config.application.api.DeploymentSpec$RevisionTarget revisionTarget()",
+ "public com.yahoo.config.application.api.DeploymentSpec$RevisionChange revisionChange()",
+ "public com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout upgradeRollout()",
+ "public int minRisk()",
+ "public int maxRisk()",
+ "public int maxIdleHours()",
+ "public java.util.List changeBlocker()",
+ "public java.util.Optional globalServiceId()",
+ "public boolean canUpgradeAt(java.time.Instant)",
+ "public boolean canChangeRevisionAt(java.time.Instant)",
+ "public java.util.Optional athenzService(com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
+ "public java.util.Map cloudAccounts(com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
+ "public java.util.Optional hostTTL(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public com.yahoo.config.application.api.Notifications notifications()",
+ "public java.util.List endpoints()",
+ "public com.yahoo.config.application.api.Bcp bcp()",
+ "public boolean deploysTo(com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
+ "public java.util.Map zoneEndpoints(com.yahoo.config.provision.zone.ZoneId)",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$ChangeBlocker" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(boolean, boolean, com.yahoo.config.application.api.TimeWindow)",
+ "public boolean blocksRevisions()",
+ "public boolean blocksVersions()",
+ "public com.yahoo.config.application.api.TimeWindow window()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$DeclaredTest" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.RegionName, java.util.Optional)",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean isTest()",
+ "public com.yahoo.config.provision.RegionName region()",
+ "public java.util.Optional hostTTL()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$DeclaredZone" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.Environment)",
+ "public void <init>(com.yahoo.config.provision.Environment, java.util.Optional, java.util.Optional, java.util.Optional, java.util.Map, java.util.Optional)",
+ "public com.yahoo.config.provision.Environment environment()",
+ "public java.util.Optional region()",
+ "public boolean active()",
+ "public java.util.Optional testerFlavor()",
+ "public java.util.List zones()",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean isTest()",
+ "public int hashCode()",
+ "public boolean equals(java.lang.Object)",
+ "public java.lang.String toString()",
+ "public java.util.Optional hostTTL()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$Delay" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.time.Duration)",
+ "public java.time.Duration delay()",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$DeprecatedElement" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(int, java.lang.String, java.util.List, java.lang.String)",
+ "public int majorVersion()",
+ "public java.lang.String humanReadableString()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$ParallelSteps" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Steps",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List)",
+ "public java.time.Duration delay()",
+ "public boolean isOrdered()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$RevisionChange" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.DeploymentSpec$RevisionChange[] values()",
+ "public static com.yahoo.config.application.api.DeploymentSpec$RevisionChange valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$RevisionChange whenClear",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$RevisionChange whenFailing",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$RevisionChange always"
+ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$RevisionTarget" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.DeploymentSpec$RevisionTarget[] values()",
+ "public static com.yahoo.config.application.api.DeploymentSpec$RevisionTarget valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$RevisionTarget next",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$RevisionTarget latest"
+ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$Step" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "abstract"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public final boolean concerns(com.yahoo.config.provision.Environment)",
+ "public abstract boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public java.util.List zones()",
+ "public java.time.Duration delay()",
+ "public java.util.List steps()",
+ "public boolean isTest()",
+ "public boolean isOrdered()",
+ "public java.util.Optional hostTTL()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$Steps" : {
+ "superClass" : "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List)",
+ "public java.util.List zones()",
+ "public java.util.List steps()",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public java.time.Duration delay()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy[] values()",
+ "public static com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy canary",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy defaultPolicy",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy conservative"
+ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout[] values()",
+ "public static com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout separate",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout leading",
+ "public static final enum com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout simultaneous"
+ ]
+ },
+ "com.yahoo.config.application.api.DeploymentSpec" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, java.util.Map, java.util.Optional, java.util.List, java.lang.String, java.util.List)",
+ "public boolean isEmpty()",
+ "public java.util.Optional majorVersion()",
+ "public java.util.List steps()",
+ "public java.util.Optional athenzDomain()",
+ "public java.util.Optional athenzService()",
+ "public java.util.Optional athenzService(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
+ "public com.yahoo.config.provision.CloudAccount cloudAccount(com.yahoo.config.provision.CloudName, com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.zone.ZoneId)",
+ "public java.util.Map cloudAccounts()",
+ "public java.util.Optional hostTTL(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
+ "public com.yahoo.config.provision.ZoneEndpoint zoneEndpoint(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.zone.ZoneId, com.yahoo.config.provision.ClusterSpec$Id)",
+ "public com.yahoo.config.application.api.Bcp bcp()",
+ "public java.lang.String xmlForm()",
+ "public java.util.Optional instance(com.yahoo.config.provision.InstanceName)",
+ "public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(java.lang.String)",
+ "public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(com.yahoo.config.provision.InstanceName)",
+ "public java.util.List instanceNames()",
+ "public java.util.List instances()",
+ "public java.util.List endpoints()",
+ "public java.util.List deprecatedElements()",
+ "public static com.yahoo.config.application.api.DeploymentSpec fromXml(java.io.Reader)",
+ "public static com.yahoo.config.application.api.DeploymentSpec fromXml(java.lang.String)",
+ "public static com.yahoo.config.application.api.DeploymentSpec fromXml(java.lang.String, boolean)",
+ "public static java.lang.String toMessageString(java.lang.Throwable)",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public int deployableHashCode()"
+ ],
+ "fields" : [
+ "public static final com.yahoo.config.application.api.DeploymentSpec empty"
+ ]
+ },
+ "com.yahoo.config.application.api.Endpoint$Level" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.Endpoint$Level[] values()",
+ "public static com.yahoo.config.application.api.Endpoint$Level valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.Endpoint$Level application",
+ "public static final enum com.yahoo.config.application.api.Endpoint$Level instance"
+ ]
+ },
+ "com.yahoo.config.application.api.Endpoint$Target" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.RegionName, com.yahoo.config.provision.InstanceName, int)",
+ "public com.yahoo.config.provision.RegionName region()",
+ "public com.yahoo.config.provision.InstanceName instance()",
+ "public int weight()",
+ "public java.lang.String toString()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.Endpoint" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String, com.yahoo.config.application.api.Endpoint$Level, java.util.List)",
+ "public java.lang.String endpointId()",
+ "public java.lang.String containerId()",
+ "public java.util.List regions()",
+ "public com.yahoo.config.application.api.Endpoint$Level level()",
+ "public java.util.List targets()",
+ "public com.yahoo.config.application.api.Endpoint withTargets(java.util.List)",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [
+ "public static final java.lang.String DEFAULT_ID"
+ ]
+ },
+ "com.yahoo.config.application.api.FileRegistry$Entry" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, com.yahoo.config.FileReference)"
+ ],
+ "fields" : [
+ "public final java.lang.String relativePath",
+ "public final com.yahoo.config.FileReference reference"
+ ]
+ },
+ "com.yahoo.config.application.api.FileRegistry" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.FileReference addFile(java.lang.String)",
+ "public abstract com.yahoo.config.FileReference addUri(java.lang.String)",
+ "public abstract com.yahoo.config.FileReference addBlob(java.lang.String, java.nio.ByteBuffer)",
+ "public com.yahoo.config.FileReference addApplicationPackage()",
+ "public abstract java.util.List export()",
+ "public java.util.Set asSet()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.Notifications$Role" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.Notifications$Role[] values()",
+ "public static com.yahoo.config.application.api.Notifications$Role valueOf(java.lang.String)",
+ "public static java.lang.String toValue(com.yahoo.config.application.api.Notifications$Role)",
+ "public static com.yahoo.config.application.api.Notifications$Role fromValue(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.Notifications$Role author"
+ ]
+ },
+ "com.yahoo.config.application.api.Notifications$When" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.Notifications$When[] values()",
+ "public static com.yahoo.config.application.api.Notifications$When valueOf(java.lang.String)",
+ "public static java.lang.String toValue(com.yahoo.config.application.api.Notifications$When)",
+ "public static com.yahoo.config.application.api.Notifications$When fromValue(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.Notifications$When failing",
+ "public static final enum com.yahoo.config.application.api.Notifications$When failingCommit"
+ ]
+ },
+ "com.yahoo.config.application.api.Notifications" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.Notifications none()",
+ "public static com.yahoo.config.application.api.Notifications of(java.util.Map, java.util.Map)",
+ "public java.util.Set emailAddressesFor(com.yahoo.config.application.api.Notifications$When)",
+ "public java.util.Set emailRolesFor(com.yahoo.config.application.api.Notifications$When)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.TimeWindow$LocalDateRange" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public java.util.Optional start()",
+ "public java.util.Optional end()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.TimeWindow" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public java.util.List days()",
+ "public java.util.List hours()",
+ "public java.time.ZoneId zone()",
+ "public com.yahoo.config.application.api.TimeWindow$LocalDateRange dateRange()",
+ "public boolean includes(java.time.Instant)",
+ "public java.lang.String toString()",
+ "public static com.yahoo.config.application.api.TimeWindow from(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.UnparsedConfigDefinition" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.vespa.config.ConfigDefinition parse()",
+ "public abstract java.lang.String getUnparsedContent()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ValidationId" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.application.api.ValidationId[] values()",
+ "public static com.yahoo.config.application.api.ValidationId valueOf(java.lang.String)",
+ "public java.lang.String value()",
+ "public java.lang.String toString()",
+ "public static java.util.Optional from(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.application.api.ValidationId indexingChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId indexModeChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId fieldTypeChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId clusterSizeReduction",
+ "public static final enum com.yahoo.config.application.api.ValidationId tensorTypeChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId resourcesReduction",
+ "public static final enum com.yahoo.config.application.api.ValidationId contentTypeRemoval",
+ "public static final enum com.yahoo.config.application.api.ValidationId contentClusterRemoval",
+ "public static final enum com.yahoo.config.application.api.ValidationId deploymentRemoval",
+ "public static final enum com.yahoo.config.application.api.ValidationId globalDocumentChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId configModelVersionMismatch",
+ "public static final enum com.yahoo.config.application.api.ValidationId skipOldConfigModels",
+ "public static final enum com.yahoo.config.application.api.ValidationId accessControl",
+ "public static final enum com.yahoo.config.application.api.ValidationId globalEndpointChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId zoneEndpointChange",
+ "public static final enum com.yahoo.config.application.api.ValidationId redundancyIncrease",
+ "public static final enum com.yahoo.config.application.api.ValidationId redundancyOne",
+ "public static final enum com.yahoo.config.application.api.ValidationId pagedSettingRemoval",
+ "public static final enum com.yahoo.config.application.api.ValidationId certificateRemoval"
+ ]
+ },
+ "com.yahoo.config.application.api.ValidationOverrides$Allow" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.application.api.ValidationId, java.time.Instant)",
+ "public boolean allows(com.yahoo.config.application.api.ValidationId, java.time.Instant)",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ValidationOverrides$ValidationException" : {
+ "superClass" : "java.lang.IllegalArgumentException",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.application.api.ValidationOverrides" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.List)",
+ "public void invalid(java.util.Map, java.time.Instant)",
+ "public void invalid(com.yahoo.config.application.api.ValidationId, java.lang.String, java.time.Instant)",
+ "public boolean allows(java.lang.String, java.time.Instant)",
+ "public boolean allows(com.yahoo.config.application.api.ValidationId, java.time.Instant)",
+ "public boolean validate(java.time.Instant)",
+ "public java.lang.String xmlForm()",
+ "public static java.lang.String toAllowMessage(com.yahoo.config.application.api.ValidationId)",
+ "public static com.yahoo.config.application.api.ValidationOverrides fromXml(java.io.Reader)",
+ "public static com.yahoo.config.application.api.ValidationOverrides fromXml(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final com.yahoo.config.application.api.ValidationOverrides empty"
+ ]
+ },
+ "com.yahoo.config.application.api.xml.DeploymentSpecXmlReader" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(boolean, java.time.Clock)",
+ "public void <init>()",
+ "public void <init>(boolean)",
+ "public com.yahoo.config.application.api.DeploymentSpec read(java.io.Reader)",
+ "public com.yahoo.config.application.api.DeploymentSpec read(java.lang.String)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod[] values()",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod mtls",
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod token"
+ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder dnsName(com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder zoneScope()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder scope(com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder sharedRouting()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder sharedL4Routing()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder routingMethod(com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder weight(int)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder hosts(java.util.List)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder clusterId(java.lang.String)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder authMethod(com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod)",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint build()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "java.lang.Comparable"
+ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public java.lang.String value()",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName sharedL4NameFrom(com.yahoo.config.provision.SystemName, com.yahoo.config.provision.ClusterSpec$Id, com.yahoo.config.provision.ApplicationId, java.lang.String)",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName from(java.lang.String)",
+ "public java.lang.String toString()",
+ "public int compareTo(com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName)",
+ "public bridge synthetic int compareTo(java.lang.Object)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod[] values()",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod shared",
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod sharedLayer4",
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod exclusive"
+ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope[] values()",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope application",
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope global",
+ "public static final enum com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope zone"
+ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterEndpoint" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$DnsName dnsName()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope scope()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod routingMethod()",
+ "public int weight()",
+ "public java.util.List hostNames()",
+ "public java.lang.String clusterId()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod authMethod()",
+ "public java.lang.String toString()",
+ "public static com.yahoo.config.model.api.ApplicationClusterEndpoint$Builder builder()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationClusterInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract java.util.List endpoints()",
+ "public abstract boolean getDeferChangesUntilRestart()",
+ "public abstract java.lang.String name()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.provision.ApplicationId, long, com.yahoo.config.model.api.Model)",
+ "public com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public long getGeneration()",
+ "public com.yahoo.config.model.api.Model getModel()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ApplicationRoles" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String)",
+ "public static com.yahoo.config.model.api.ApplicationRoles fromString(java.lang.String, java.lang.String)",
+ "public java.lang.String applicationContainerRole()",
+ "public java.lang.String applicationHostRole()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeAction$Type" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ConfigChangeAction$Type[] values()",
+ "public static com.yahoo.config.model.api.ConfigChangeAction$Type valueOf(java.lang.String)",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type RESTART",
+ "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type REFEED",
+ "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type REINDEX"
+ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeAction" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public abstract java.lang.String getMessage()",
+ "public abstract java.util.List getServices()",
+ "public java.util.Optional validationId()",
+ "public abstract com.yahoo.config.provision.ClusterSpec$Id clusterId()",
+ "public boolean ignoreForInternalRedeploy()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeRefeedAction" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "com.yahoo.config.model.api.ConfigChangeAction"
+ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public java.lang.String name()",
+ "public abstract java.lang.String getDocumentType()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeReindexAction" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "com.yahoo.config.model.api.ConfigChangeAction"
+ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public java.lang.String name()",
+ "public abstract java.lang.String getDocumentType()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeRestartAction" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "com.yahoo.config.model.api.ConfigChangeAction"
+ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigDefinitionRepo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract java.util.Map getConfigDefinitions()",
+ "public abstract com.yahoo.vespa.config.buildergen.ConfigDefinition get(com.yahoo.vespa.config.ConfigDefinitionKey)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigDefinitionStore" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.vespa.config.ConfigDefinition getConfigDefinition(com.yahoo.vespa.config.ConfigDefinitionKey)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigModelPlugin" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ConfigServerSpec" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract java.lang.String getHostName()",
+ "public abstract int getConfigServerPort()",
+ "public abstract int getZooKeeperPort()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ContainerEndpoint" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope, java.util.List)",
+ "public void <init>(java.lang.String, com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope, java.util.List, java.util.OptionalInt)",
+ "public void <init>(java.lang.String, com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope, java.util.List, java.util.OptionalInt, com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod)",
+ "public void <init>(java.lang.String, com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope, java.util.List, java.util.OptionalInt, com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod, com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod)",
+ "public java.lang.String clusterId()",
+ "public java.util.List names()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$Scope scope()",
+ "public java.util.OptionalInt weight()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$RoutingMethod routingMethod()",
+ "public com.yahoo.config.model.api.ApplicationClusterEndpoint$AuthMethod authMethod()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.EndpointCertificateMetadata" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String, int)",
+ "public java.lang.String keyName()",
+ "public java.lang.String certName()",
+ "public int version()",
+ "public java.lang.String toString()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.EndpointCertificateSecrets" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String)",
+ "public void <init>(java.lang.String, java.lang.String, int)",
+ "public java.lang.String certificate()",
+ "public java.lang.String key()",
+ "public int version()",
+ "public static com.yahoo.config.model.api.EndpointCertificateSecrets missing(int)",
+ "public boolean isMissing()"
+ ],
+ "fields" : [
+ "public static final com.yahoo.config.model.api.EndpointCertificateSecrets MISSING"
+ ]
+ },
+ "com.yahoo.config.model.api.FileDistribution" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract void startDownload(java.lang.String, int, java.util.Set)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.HostInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.util.Collection)",
+ "public java.lang.String getHostname()",
+ "public java.util.Collection getServices()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.HostProvisioner" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.provision.HostSpec allocateHost(java.lang.String)",
+ "public abstract java.util.List prepare(com.yahoo.config.provision.ClusterSpec, com.yahoo.config.provision.Capacity, com.yahoo.config.provision.ProvisionLogger)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.Model" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.ConfigInstance$Builder getConfigInstance(com.yahoo.vespa.config.ConfigKey, com.yahoo.vespa.config.buildergen.ConfigDefinition)",
+ "public abstract java.util.Set allConfigsProduced()",
+ "public abstract java.util.Collection getHosts()",
+ "public abstract java.util.Set allConfigIds()",
+ "public abstract java.util.Set fileReferences()",
+ "public abstract com.yahoo.config.provision.AllocatedHosts allocatedHosts()",
+ "public boolean allowModelVersionMismatch(java.time.Instant)",
+ "public boolean skipOldConfigModels(java.time.Instant)",
+ "public com.yahoo.component.Version version()",
+ "public com.yahoo.component.Version wantedNodeVersion()",
+ "public com.yahoo.config.model.api.Provisioned provisioned()",
+ "public java.util.Map documentTypesByCluster()",
+ "public java.util.Map indexedDocumentTypesByCluster()",
+ "public java.util.Set applicationClusterInfo()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelContext$FeatureFlags" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public double defaultTermwiseLimit()",
+ "public java.lang.String feedSequencerType()",
+ "public java.lang.String responseSequencerType()",
+ "public java.lang.String queryDispatchPolicy()",
+ "public double queryDispatchWarmup()",
+ "public int defaultNumResponseThreads()",
+ "public int mbusNetworkThreads()",
+ "public int mbusJavaRpcNumTargets()",
+ "public int mbusJavaEventsBeforeWakeup()",
+ "public int mbusCppRpcNumTargets()",
+ "public int mbusCppEventsBeforeWakeup()",
+ "public int rpcNumTargets()",
+ "public int rpcEventsBeforeWakeup()",
+ "public boolean useAsyncMessageHandlingOnSchedule()",
+ "public double feedConcurrency()",
+ "public double feedNiceness()",
+ "public int maxUnCommittedMemory()",
+ "public boolean sharedStringRepoNoReclaim()",
+ "public boolean loadCodeAsHugePages()",
+ "public boolean containerDumpHeapOnShutdownTimeout()",
+ "public double containerShutdownTimeout()",
+ "public int heapSizePercentage()",
+ "public java.util.List allowedAthenzProxyIdentities()",
+ "public int maxActivationInhibitedOutOfSyncGroups()",
+ "public java.lang.String jvmOmitStackTraceInFastThrowOption(com.yahoo.config.provision.ClusterSpec$Type)",
+ "public double resourceLimitDisk()",
+ "public double resourceLimitMemory()",
+ "public double minNodeRatioPerGroup()",
+ "public boolean forwardIssuesAsErrors()",
+ "public boolean useV8GeoPositions()",
+ "public int maxCompactBuffers()",
+ "public java.util.List ignoredHttpUserAgents()",
+ "public com.yahoo.config.provision.NodeResources$Architecture adminClusterArchitecture()",
+ "public boolean enableProxyProtocolMixedMode()",
+ "public java.lang.String logFileCompressionAlgorithm(java.lang.String)",
+ "public boolean useRestrictedDataPlaneBindings()",
+ "public boolean enableGlobalPhase()",
+ "public java.lang.String summaryDecodePolicy()",
+ "public boolean allowMoreThanOneContentGroupDown(com.yahoo.config.provision.ClusterSpec$Id)",
+ "public boolean enableConditionalPutRemoveWriteRepair()",
+ "public boolean enableDataplaneProxy()",
+ "public boolean enableNestedMultivalueGrouping()",
+ "public boolean useReconfigurableDispatcher()",
+ "public int contentLayerMetadataFeatureLevel()",
+ "public boolean dynamicHeapSize()",
+ "public java.lang.String unknownConfigDefinition()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelContext$ModelFeatureFlag" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "java.lang.annotation.Annotation"
+ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract",
+ "annotation"
+ ],
+ "methods" : [
+ "public abstract java.lang.String[] owners()",
+ "public abstract java.lang.String removeAfter()",
+ "public abstract java.lang.String comment()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelContext$Properties" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.model.api.ModelContext$FeatureFlags featureFlags()",
+ "public abstract boolean multitenant()",
+ "public abstract com.yahoo.config.provision.ApplicationId applicationId()",
+ "public abstract java.util.List configServerSpecs()",
+ "public abstract com.yahoo.config.provision.HostName loadBalancerName()",
+ "public abstract java.net.URI ztsUrl()",
+ "public abstract java.lang.String athenzDnsSuffix()",
+ "public abstract boolean hostedVespa()",
+ "public abstract com.yahoo.config.provision.Zone zone()",
+ "public abstract java.util.Set endpoints()",
+ "public abstract boolean isBootstrap()",
+ "public abstract boolean isFirstTimeDeployment()",
+ "public java.util.Optional endpointCertificateSecrets()",
+ "public java.util.Optional athenzDomain()",
+ "public com.yahoo.config.model.api.Quota quota()",
+ "public java.util.List tenantSecretStores()",
+ "public java.lang.String jvmGCOptions()",
+ "public abstract java.lang.String jvmGCOptions(java.util.Optional)",
+ "public boolean useDedicatedNodeForLogserver()",
+ "public boolean allowDisableMtls()",
+ "public java.util.List operatorCertificates()",
+ "public java.util.List tlsCiphersOverride()",
+ "public java.util.List zoneDnsSuffixes()",
+ "public abstract java.util.List environmentVariables()",
+ "public java.util.Optional cloudAccount()",
+ "public boolean allowUserFilters()",
+ "public java.time.Duration endpointConnectionTtl()",
+ "public java.util.List dataplaneTokens()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelContext" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.application.api.ApplicationPackage applicationPackage()",
+ "public abstract java.util.Optional previousModel()",
+ "public abstract com.yahoo.config.model.api.HostProvisioner getHostProvisioner()",
+ "public abstract com.yahoo.config.model.api.Provisioned provisioned()",
+ "public abstract com.yahoo.config.application.api.DeployLogger deployLogger()",
+ "public abstract com.yahoo.config.model.api.ConfigDefinitionRepo configDefinitionRepo()",
+ "public abstract com.yahoo.config.application.api.FileRegistry getFileRegistry()",
+ "public abstract java.util.concurrent.ExecutorService getExecutor()",
+ "public java.util.Optional reindexing()",
+ "public abstract com.yahoo.config.model.api.ModelContext$Properties properties()",
+ "public java.util.Optional appDir()",
+ "public abstract com.yahoo.config.model.api.OnnxModelCost onnxModelCost()",
+ "public java.util.Optional wantedDockerImageRepo()",
+ "public abstract com.yahoo.component.Version modelVespaVersion()",
+ "public abstract com.yahoo.component.Version wantedNodeVespaVersion()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelCreateResult" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(com.yahoo.config.model.api.Model, java.util.List)",
+ "public com.yahoo.config.model.api.Model getModel()",
+ "public java.util.List getConfigChangeActions()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelFactory" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.component.Version version()",
+ "public abstract com.yahoo.config.model.api.Model createModel(com.yahoo.config.model.api.ModelContext)",
+ "public abstract com.yahoo.config.model.api.ModelCreateResult createAndValidateModel(com.yahoo.config.model.api.ModelContext, com.yahoo.config.model.api.ValidationParameters)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ModelState" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.model.api.Model getModel()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.OnnxModelCost$Calculator" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract long aggregatedModelCostInBytes()",
+ "public abstract void registerModel(com.yahoo.config.application.api.ApplicationFile)",
+ "public abstract void registerModel(com.yahoo.config.ModelReference)",
+ "public abstract void registerModel(java.net.URI)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.OnnxModelCost" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract com.yahoo.config.model.api.OnnxModelCost$Calculator newCalculator(com.yahoo.config.application.api.ApplicationPackage, com.yahoo.config.application.api.DeployLogger)",
+ "public static com.yahoo.config.model.api.OnnxModelCost disabled()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.PortInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(int, java.util.Collection)",
+ "public int getPort()",
+ "public java.util.Collection getTags()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.Provisioned" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public void add(com.yahoo.config.provision.ClusterSpec$Id, com.yahoo.config.provision.Capacity)",
+ "public java.util.Map all()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.Quota" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.util.Optional, java.util.Optional)",
+ "public static com.yahoo.config.model.api.Quota fromSlime(com.yahoo.slime.Inspector)",
+ "public com.yahoo.config.model.api.Quota withBudget(java.math.BigDecimal)",
+ "public com.yahoo.config.model.api.Quota withClusterSize(int)",
+ "public com.yahoo.slime.Slime toSlime()",
+ "public void toSlime(com.yahoo.slime.Cursor)",
+ "public static com.yahoo.config.model.api.Quota unlimited()",
+ "public java.util.Optional maxClusterSize()",
+ "public java.util.Optional budgetAsDecimal()",
+ "public java.util.Optional budget()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.Reindexing$Status" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract java.time.Instant ready()",
+ "public abstract double speed()",
+ "public abstract java.lang.String cause()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.Reindexing" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public java.util.Optional status(java.lang.String, java.lang.String)",
+ "public boolean enabled()"
+ ],
+ "fields" : [
+ "public static final com.yahoo.config.model.api.Reindexing DISABLED_INSTANCE"
+ ]
+ },
+ "com.yahoo.config.model.api.ServiceInfo" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String, java.util.Collection, java.util.Map, java.lang.String, java.lang.String)",
+ "public java.lang.String getServiceName()",
+ "public java.lang.String getConfigId()",
+ "public java.lang.String getServiceType()",
+ "public java.util.Optional getProperty(java.lang.String)",
+ "public java.util.Collection getPorts()",
+ "public java.lang.String getHostName()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.SuperModel" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public void <init>(java.util.Map, boolean)",
+ "public java.util.Map getModelsPerTenant()",
+ "public java.util.Map getModels()",
+ "public boolean isComplete()",
+ "public java.util.List getAllApplicationInfos()",
+ "public java.util.Optional getApplicationInfo(com.yahoo.config.provision.ApplicationId)",
+ "public com.yahoo.config.model.api.SuperModel cloneAndSetApplication(com.yahoo.config.model.api.ApplicationInfo)",
+ "public com.yahoo.config.model.api.SuperModel cloneAndRemoveApplication(com.yahoo.config.provision.ApplicationId)",
+ "public com.yahoo.config.model.api.SuperModel cloneAsComplete()",
+ "public java.util.Set getApplicationIds()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.SuperModelListener" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract void applicationActivated(com.yahoo.config.model.api.SuperModel, com.yahoo.config.model.api.ApplicationInfo)",
+ "public abstract void applicationRemoved(com.yahoo.config.model.api.SuperModel, com.yahoo.config.provision.ApplicationId)",
+ "public abstract void notifyOfCompleteness(com.yahoo.config.model.api.SuperModel)"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.SuperModelProvider" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract void registerListener(com.yahoo.config.model.api.SuperModelListener)",
+ "public abstract com.yahoo.config.model.api.SuperModel getSuperModel()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.TenantSecretStore" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, java.lang.String, java.lang.String)",
+ "public void <init>(java.lang.String, java.lang.String, java.lang.String, java.util.Optional)",
+ "public java.lang.String getName()",
+ "public java.lang.String getAwsId()",
+ "public java.lang.String getRole()",
+ "public java.util.Optional getExternalId()",
+ "public com.yahoo.config.model.api.TenantSecretStore withExternalId(java.lang.String)",
+ "public java.lang.String toString()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$CheckRouting" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors)",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors, com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange, com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
+ "public boolean ignoreValidationErrors()",
+ "public boolean failOnIncompatibleChanges()",
+ "public boolean checkRouting()"
+ ],
+ "fields" : [ ]
+ },
+ "com.yahoo.config.model.api.container.ContainerServiceType" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static com.yahoo.config.model.api.container.ContainerServiceType[] values()",
+ "public static com.yahoo.config.model.api.container.ContainerServiceType valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum com.yahoo.config.model.api.container.ContainerServiceType CONTAINER",
+ "public static final enum com.yahoo.config.model.api.container.ContainerServiceType QRSERVER",
+ "public static final enum com.yahoo.config.model.api.container.ContainerServiceType CLUSTERCONTROLLER_CONTAINER",
+ "public static final enum com.yahoo.config.model.api.container.ContainerServiceType LOGSERVER_CONTAINER",
+ "public static final enum com.yahoo.config.model.api.container.ContainerServiceType METRICS_PROXY_CONTAINER",
+ "public final java.lang.String serviceName"
+ ]
+ }
+} \ No newline at end of file
diff --git a/config-model-api/pom.xml b/config-model-api/pom.xml
index aaa26a136b5..9bc406452bb 100644
--- a/config-model-api/pom.xml
+++ b/config-model-api/pom.xml
@@ -106,6 +106,10 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>abi-check-plugin</artifactId>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java
index a55ae795d28..97336b2bca0 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java
@@ -160,6 +160,8 @@ public abstract class ApplicationFile implements Comparable<ApplicationFile> {
public abstract MetaData getMetaData();
+ public abstract long getSize();
+
public static class MetaData {
public String status = "unknown";
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeployLogger.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeployLogger.java
index d9ebd902e3e..65e6bc2803a 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeployLogger.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeployLogger.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.application.api;
+import java.util.function.Supplier;
import java.util.logging.Level;
/**
@@ -13,6 +14,10 @@ public interface DeployLogger {
/** Log a message unrelated to the application package, e.g. internal error/status. */
void log(Level level, String message);
+ default void log(Level level, Supplier<String> message) { log(level, message.get()); }
+
+ default void log(Level level, Supplier<String> message, Throwable throwable) { log(level, message); }
+
/**
* Log a message related to the application package. These messages should be actionable by the user, f.ex. to
* signal usage of invalid/deprecated syntax
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/package-info.java b/config-model-api/src/main/java/com/yahoo/config/application/api/package-info.java
index c1f7d4bd844..0ab9ba3fd63 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/package-info.java
@@ -1,5 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
+@PublicApi // Internal public API, not a real public API
package com.yahoo.config.application.api;
import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.api.annotations.PublicApi;
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/package-info.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/package-info.java
index 5ec2ebfa8f9..7c961746eba 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/package-info.java
@@ -1,5 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
+@PublicApi // Internal public API, not a real public API
package com.yahoo.config.application.api.xml;
import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.api.annotations.PublicApi;
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java
index 69749ee6f96..0276985d6a6 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java
@@ -168,13 +168,7 @@ public class ApplicationClusterEndpoint {
return name;
}
- public static DnsName sharedNameFrom(SystemName systemName, ClusterSpec.Id cluster, ApplicationId applicationId, String suffix) {
- String name = dnsParts(systemName, cluster, applicationId)
- .filter(Objects::nonNull) // remove null values that were "default"
- .collect(Collectors.joining("--"));
- return new DnsName(sanitize(name) + suffix); // Need to sanitize name since it is considered one label
- }
-
+ // TODO(mpolden): Remove when config-models < 8.232 are gone
public static DnsName sharedL4NameFrom(SystemName systemName, ClusterSpec.Id cluster, ApplicationId applicationId, String suffix) {
String name = dnsParts(systemName, cluster, applicationId)
.filter(Objects::nonNull) // remove null values that were "default"
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 37b24f0ac1d..57d013ebd01 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -47,6 +47,7 @@ public interface ModelContext {
default Optional<? extends Reindexing> reindexing() { return Optional.empty(); }
Properties properties();
default Optional<File> appDir() { return Optional.empty();}
+ OnnxModelCost onnxModelCost();
/** The Docker image repo we want to use for images for this deployment (optional, will use default if empty) */
default Optional<DockerImage> wantedDockerImageRepo() { return Optional.empty(); }
@@ -117,6 +118,8 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean enableNestedMultivalueGrouping() { return false; }
@ModelFeatureFlag(owners = {"jonmv"}) default boolean useReconfigurableDispatcher() { return false; }
@ModelFeatureFlag(owners = {"vekterli"}) default int contentLayerMetadataFeatureLevel() { return 0; }
+ @ModelFeatureFlag(owners = {"bjorncs"}) default boolean dynamicHeapSize() { return false; }
+ @ModelFeatureFlag(owners = {"hmusum"}) default String unknownConfigDefinition() { return "log"; }
}
/** Warning: As elsewhere in this package, do not make backwards incompatible changes that will break old config models! */
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/OnnxModelCost.java b/config-model-api/src/main/java/com/yahoo/config/model/api/OnnxModelCost.java
new file mode 100644
index 00000000000..33ed55ecaef
--- /dev/null
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/OnnxModelCost.java
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.config.model.api;
+
+import com.yahoo.config.ModelReference;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+
+import java.net.URI;
+
+/**
+ * @author bjorncs
+ */
+public interface OnnxModelCost {
+
+ Calculator newCalculator(ApplicationPackage appPkg, DeployLogger logger);
+
+ interface Calculator {
+ long aggregatedModelCostInBytes();
+ void registerModel(ApplicationFile path);
+ @Deprecated(forRemoval = true) void registerModel(ModelReference ref); // TODO(bjorncs): remove once no longer in use by old config models
+ void registerModel(URI uri);
+ }
+
+ static OnnxModelCost disabled() {
+ return (__, ___) -> new Calculator() {
+ @Override public long aggregatedModelCostInBytes() { return 0; }
+ @Override public void registerModel(ApplicationFile path) {}
+ @SuppressWarnings("removal") @Override public void registerModel(ModelReference ref) {}
+ @Override public void registerModel(URI uri) {}
+ };
+ }
+}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/container/package-info.java b/config-model-api/src/main/java/com/yahoo/config/model/api/container/package-info.java
index df269bb42e8..fc525b2b589 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/container/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/container/package-info.java
@@ -1,5 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
+@PublicApi // Internal public API, not a real public API
package com.yahoo.config.model.api.container;
import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.api.annotations.PublicApi;
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
index 9560a9658d5..7f6c084863f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
@@ -1,5 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
+@PublicApi // Internal public API, not a real public API
package com.yahoo.config.model.api;
import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.api.annotations.PublicApi;
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
index 4df7a76031a..a7e8cd52e01 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
@@ -18,6 +18,7 @@ import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.api.Reindexing;
import com.yahoo.config.model.api.ValidationParameters;
@@ -90,6 +91,7 @@ public class DeployState implements ConfigDefinitionStore {
private final Provisioned provisioned;
private final Reindexing reindexing;
private final ExecutorService executor;
+ private final OnnxModelCost onnxModelCost;
public static DeployState createTestState() {
return new Builder().build();
@@ -124,7 +126,8 @@ public class DeployState implements ConfigDefinitionStore {
boolean accessLoggingEnabledByDefault,
Optional<DockerImage> wantedDockerImageRepo,
Reindexing reindexing,
- Optional<ValidationOverrides> validationOverrides) {
+ Optional<ValidationOverrides> validationOverrides,
+ OnnxModelCost onnxModelCost) {
this.logger = deployLogger;
this.fileRegistry = fileRegistry;
this.executor = executor;
@@ -152,6 +155,7 @@ public class DeployState implements ConfigDefinitionStore {
this.now = now;
this.wantedDockerImageRepo = wantedDockerImageRepo;
this.reindexing = reindexing;
+ this.onnxModelCost = onnxModelCost;
}
public static HostProvisioner getDefaultModelHostProvisioner(ApplicationPackage applicationPackage) {
@@ -224,7 +228,7 @@ public class DeployState implements ConfigDefinitionStore {
// Mapping from key to something that can create a config definition.
private Map<ConfigDefinitionKey, UnparsedConfigDefinition> existingConfigDefs = null;
- // Cache of config defs for all [def,version] combinations looked up so far.
+ // Cache of config definitions looked up so far.
private final Map<ConfigDefinitionKey, ConfigDefinition> defArchive = new LinkedHashMap<>();
public ApplicationPackage getApplicationPackage() {
@@ -305,6 +309,8 @@ public class DeployState implements ConfigDefinitionStore {
public Optional<Reindexing> reindexing() { return Optional.ofNullable(reindexing); }
+ public OnnxModelCost onnxModelCost() { return onnxModelCost; }
+
public boolean isHostedTenantApplication(ApplicationType type) {
boolean isTesterApplication = getProperties().applicationId().instance().isTester();
return isHosted() && type == ApplicationType.DEFAULT && !isTesterApplication;
@@ -333,6 +339,7 @@ public class DeployState implements ConfigDefinitionStore {
private QueryProfiles queryProfiles = null;
private Reindexing reindexing = null;
private Optional<ValidationOverrides> validationOverrides = Optional.empty();
+ private OnnxModelCost onnxModelCost = OnnxModelCost.disabled();
public Builder() {}
@@ -450,6 +457,8 @@ public class DeployState implements ConfigDefinitionStore {
return this;
}
+ public Builder onnxModelCost(OnnxModelCost instance) { this.onnxModelCost = instance; return this; }
+
public DeployState build() {
return build(new ValidationParameters());
}
@@ -482,7 +491,8 @@ public class DeployState implements ConfigDefinitionStore {
accessLoggingEnabledByDefault,
wantedDockerImageRepo,
reindexing,
- validationOverrides);
+ validationOverrides,
+ onnxModelCost);
}
}
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
index 815c32e3c8f..77356292f9a 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
@@ -86,6 +86,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private boolean allowUserFilters = true;
private List<DataplaneToken> dataplaneTokens;
private int contentLayerMetadataFeatureLevel = 0;
+ private boolean dynamicHeapSize = false;
@Override public ModelContext.FeatureFlags featureFlags() { return this; }
@Override public boolean multitenant() { return multitenant; }
@@ -144,6 +145,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
@Override public boolean enableGlobalPhase() { return true; } // Enable global-phase by default for unit tests only
@Override public List<DataplaneToken> dataplaneTokens() { return dataplaneTokens; }
@Override public int contentLayerMetadataFeatureLevel() { return contentLayerMetadataFeatureLevel; }
+ @Override public boolean dynamicHeapSize() { return dynamicHeapSize; }
public TestProperties sharedStringRepoNoReclaim(boolean sharedStringRepoNoReclaim) {
this.sharedStringRepoNoReclaim = sharedStringRepoNoReclaim;
@@ -379,6 +381,8 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
return this;
}
+ public TestProperties setDynamicHeapSize(boolean b) { this.dynamicHeapSize = b; return this; }
+
public static class Spec implements ConfigServerSpec {
private final String hostName;
diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java b/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java
index 3d7eafe658f..b59293fbac1 100644
--- a/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java
+++ b/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java
@@ -2,7 +2,6 @@
package com.yahoo.config.model.producer;
import com.yahoo.vespa.config.ConfigDefinitionKey;
-import com.yahoo.vespa.config.ConfigPayload;
import com.yahoo.vespa.config.ConfigPayloadBuilder;
import java.util.LinkedHashMap;
@@ -16,31 +15,13 @@ import java.util.Set;
* @author Ulf Lilleengen
*/
public class UserConfigRepo {
+
private final Map<ConfigDefinitionKey, ConfigPayloadBuilder> userConfigsMap;
public UserConfigRepo() {
this.userConfigsMap = new LinkedHashMap<>();
}
- @Override
- public UserConfigRepo clone() {
- return new UserConfigRepo(copyBuilders(userConfigsMap));
- }
-
- /**
- * Must copy the builder, because the merge method on {@link TreeConfigProducer} might override the row's builders otherwise
- */
- private Map<ConfigDefinitionKey, ConfigPayloadBuilder> copyBuilders(Map<ConfigDefinitionKey, ConfigPayloadBuilder> source) {
- Map<ConfigDefinitionKey, ConfigPayloadBuilder> ret = new LinkedHashMap<>();
- for (Map.Entry<ConfigDefinitionKey, ConfigPayloadBuilder> e : source.entrySet()) {
- ConfigDefinitionKey key = e.getKey();
- ConfigPayloadBuilder sourceVal = e.getValue();
- ConfigPayloadBuilder destVal = new ConfigPayloadBuilder(ConfigPayload.fromBuilder(sourceVal));
- ret.put(key, destVal);
- }
- return ret;
- }
-
public UserConfigRepo(Map<ConfigDefinitionKey, ConfigPayloadBuilder> map) {
this.userConfigsMap = map;
}
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
index dbcd1cea2fa..342b5f243e7 100644
--- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
+++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
@@ -488,6 +488,8 @@ public class MockApplicationPackage implements ApplicationPackage {
throw new UnsupportedOperationException();
}
+ @Override public long getSize() { return file.length(); }
+
@Override
public int compareTo(ApplicationFile other) {
return this.getPath().getName().compareTo((other).getPath().getName());
diff --git a/config-model/src/main/java/com/yahoo/schema/derived/IndexInfo.java b/config-model/src/main/java/com/yahoo/schema/derived/IndexInfo.java
index 7d88985b2d5..f6a022e9930 100644
--- a/config-model/src/main/java/com/yahoo/schema/derived/IndexInfo.java
+++ b/config-model/src/main/java/com/yahoo/schema/derived/IndexInfo.java
@@ -82,7 +82,7 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
}
// Commands for summary fields
- // TODO: Move to fieldinfo and implement differently. This is not right
+ // TODO: Move to schemainfo and implement differently
for (SummaryField summaryField : schema.getUniqueNamedSummaryFields().values()) {
if (summaryField.getTransform().isTeaser()) {
addIndexCommand(summaryField.getName(), CMD_DYNTEASER);
@@ -90,6 +90,13 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
if (summaryField.getTransform().isBolded()) {
addIndexCommand(summaryField.getName(), CMD_HIGHLIGHT);
}
+
+ var sourceField = schema.getField(summaryField.getSourceField()); // Take the first as they should all be consistent
+ if (sourceField != null && sourceField.getMatching().getType().equals(MatchType.GRAM)) {
+ addIndexCommand(summaryField.getName(),
+ "ngram " + (sourceField.getMatching().getGramSize().orElse(NGramMatch.DEFAULT_GRAM_SIZE)));
+
+ }
}
}
@@ -452,7 +459,7 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
iiB.command(
new IndexInfoConfig.Indexinfo.Command.Builder()
.indexname(fieldSet.getName())
- .command("ngram "+(fieldSetMatching.getGramSize()>0 ? fieldSetMatching.getGramSize() : NGramMatch.DEFAULT_GRAM_SIZE)));
+ .command("ngram " + fieldSetMatching.getGramSize().orElse(NGramMatch.DEFAULT_GRAM_SIZE)));
} else if (fieldSetMatching.getType().equals(MatchType.TEXT)) {
}
diff --git a/config-model/src/main/java/com/yahoo/schema/document/Matching.java b/config-model/src/main/java/com/yahoo/schema/document/Matching.java
index 1fe947d672b..e3f49cb834d 100644
--- a/config-model/src/main/java/com/yahoo/schema/document/Matching.java
+++ b/config-model/src/main/java/com/yahoo/schema/document/Matching.java
@@ -1,7 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.document;
+import com.yahoo.schema.processing.NGramMatch;
+
import java.io.Serializable;
+import java.util.OptionalInt;
/**
* Defines how a field should be matched.
@@ -23,8 +26,8 @@ public class Matching implements Cloneable, Serializable {
private boolean algorithmUserSet = false;
- /** The gram size is the n in n-gram, or -1 if not set. Should only be set with gram matching. */
- private int gramSize = -1;
+ /** The gram size is the n in n-gram, or empty if not set. Should only be set with gram matching. */
+ private OptionalInt gramSize = OptionalInt.empty();
/** Maximum number of characters to consider when searching in this field. Used for limiting resources, especially in streaming search. */
private Integer maxLength;
@@ -67,10 +70,10 @@ public class Matching implements Cloneable, Serializable {
public boolean isSuffix() { return algorithm == MatchAlgorithm.SUFFIX; }
- /** Returns the gram size, or -1 if not set. Should only be set with gram matching. */
- public int getGramSize() { return gramSize; }
+ /** Returns the gram size, or empty if not set. Should only be set with gram matching. */
+ public OptionalInt getGramSize() { return gramSize; }
- public void setGramSize(int gramSize) { this.gramSize=gramSize; }
+ public void setGramSize(int gramSize) { this.gramSize = OptionalInt.of(gramSize); }
/**
* Merge data from another matching object
@@ -107,10 +110,11 @@ public class Matching implements Cloneable, Serializable {
@Override
public String toString() {
- return type + " matching [" + (type==MatchType.GRAM ? "gram size " + gramSize : "supports " + algorithm) +
- "], [exact-terminator "+exactMatchTerminator+"]";
+ return type + " matching [" + (type == MatchType.GRAM ? "gram size " + gramSize.orElse(NGramMatch.DEFAULT_GRAM_SIZE) : "supports " + algorithm) +
+ "], [exact-terminator " + exactMatchTerminator + "]";
}
+ @Override
public Matching clone() {
try {
return (Matching)super.clone();
@@ -129,7 +133,7 @@ public class Matching implements Cloneable, Serializable {
if ( this.exactMatchTerminator == null && other.exactMatchTerminator != null) return false;
if ( this.exactMatchTerminator != null && ( ! this.exactMatchTerminator.equals(other.exactMatchTerminator)) )
return false;
- if ( gramSize != other.gramSize) return false;
+ if ( ! gramSize.equals(other.gramSize)) return false;
return true;
}
diff --git a/config-model/src/main/java/com/yahoo/schema/document/SDField.java b/config-model/src/main/java/com/yahoo/schema/document/SDField.java
index 7821c101880..6cbdb38b9bc 100644
--- a/config-model/src/main/java/com/yahoo/schema/document/SDField.java
+++ b/config-model/src/main/java/com/yahoo/schema/document/SDField.java
@@ -196,6 +196,8 @@ public class SDField extends Field implements TypedKey, ImmutableSDField {
return isExtraField;
}
+ public boolean isDocumentField() { return ! isExtraField; }
+
@Override
public boolean isImportedField() {
return false;
@@ -613,11 +615,8 @@ public class SDField extends Field implements TypedKey, ImmutableSDField {
@Override
public RankType getRankType() { return this.rankType; }
- /**
- * Returns the search-time attribute settings of this field or null if none is set.
- *
- * <p>TODO: Make unmodifiable.</p>
- */
+ /** Returns the search-time attribute settings of this field or null if none is set. */
+ // TODO: Make unmodifiable
@Override
public Map<String, Attribute> getAttributes() { return attributes; }
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/IndexingInputs.java b/config-model/src/main/java/com/yahoo/schema/processing/IndexingInputs.java
index 985ec8653c7..0537f1704ab 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/IndexingInputs.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/IndexingInputs.java
@@ -31,9 +31,8 @@ public class IndexingInputs extends Processor {
ScriptExpression script = field.getIndexingScript();
if (script == null) continue;
- String fieldName = field.getName();
- script = (ScriptExpression)new DefaultToCurrentField(fieldName).convert(script);
- script = (ScriptExpression)new EnsureInputExpression(fieldName).convert(script);
+ script = (ScriptExpression)new DefaultToCurrentField(field).convert(script);
+ script = (ScriptExpression)new EnsureInputExpression(field).convert(script);
if (validate)
new VerifyInputExpression(schema, field).visit(script);
@@ -43,10 +42,10 @@ public class IndexingInputs extends Processor {
private static class DefaultToCurrentField extends ExpressionConverter {
- final String fieldName;
+ final SDField field;
- DefaultToCurrentField(String fieldName) {
- this.fieldName = fieldName;
+ DefaultToCurrentField(SDField field) {
+ this.field = field;
}
@Override
@@ -56,27 +55,28 @@ public class IndexingInputs extends Processor {
@Override
protected Expression doConvert(Expression exp) {
- return new InputExpression(fieldName);
+ return new InputExpression(field.getName());
}
}
private static class EnsureInputExpression extends ExpressionConverter {
- final String fieldName;
+ final SDField field;
- EnsureInputExpression(String fieldName) {
- this.fieldName = fieldName;
+ EnsureInputExpression(SDField field) {
+ this.field = field;
}
@Override
protected boolean shouldConvert(Expression exp) {
- return exp instanceof StatementExpression;
+ return exp instanceof StatementExpression
+ && ( field.isDocumentField() || ( field.getAttribute() != null && field.getAttribute().isMutable()));
}
@Override
protected Expression doConvert(Expression exp) {
if (exp.requiredInputType() != null) {
- return new StatementExpression(new InputExpression(fieldName), exp);
+ return new StatementExpression(new InputExpression(field.getName()), exp);
} else {
return exp;
}
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java b/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
index 3c7e9b4066f..e17b1e46a6e 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
@@ -24,6 +24,7 @@ import com.yahoo.vespa.indexinglanguage.expressions.SummaryExpression;
import com.yahoo.vespa.indexinglanguage.expressions.VerificationContext;
import com.yahoo.vespa.indexinglanguage.expressions.VerificationException;
import com.yahoo.vespa.model.container.search.QueryProfiles;
+import com.yahoo.yolean.Exceptions;
import java.util.HashSet;
import java.util.Set;
@@ -51,7 +52,7 @@ public class IndexingValidation extends Processor {
converter.convert(exp); // TODO: stop doing this explicitly when visiting a script does not branch
}
} catch (VerificationException e) {
- fail(schema, field, "For expression '" + e.getExpression() + "': " + e.getMessage());
+ fail(schema, field, "For expression '" + e.getExpression() + "': " + Exceptions.toMessageString(e));
}
}
}
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/NGramMatch.java b/config-model/src/main/java/com/yahoo/schema/processing/NGramMatch.java
index f1ff910be43..6ec5428156f 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/NGramMatch.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/NGramMatch.java
@@ -31,7 +31,7 @@ public class NGramMatch extends Processor {
for (SDField field : schema.allConcreteFields()) {
if (field.getMatching().getType().equals(MatchType.GRAM))
implementGramMatch(schema, field, validate);
- else if (validate && field.getMatching().getGramSize() >= 0)
+ else if (validate && field.getMatching().getGramSize().isPresent())
throw new IllegalArgumentException("gram-size can only be set when the matching mode is 'gram'");
}
}
@@ -40,9 +40,7 @@ public class NGramMatch extends Processor {
if (validate && field.doesAttributing() && ! field.doesIndexing())
throw new IllegalArgumentException("gram matching is not supported with attributes, use 'index' in indexing");
- int n = field.getMatching().getGramSize();
- if (n < 0)
- n = DEFAULT_GRAM_SIZE; // not set - use default gram size
+ int n = field.getMatching().getGramSize().orElse(DEFAULT_GRAM_SIZE);
if (validate && n == 0)
throw new IllegalArgumentException("Illegal gram size in " + field + ": Must be at least 1");
field.getNormalizing().inferCodepoint();
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/PredicateProcessor.java b/config-model/src/main/java/com/yahoo/schema/processing/PredicateProcessor.java
index 0362dc39c4c..1627320dc54 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/PredicateProcessor.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/PredicateProcessor.java
@@ -15,11 +15,11 @@ import com.yahoo.vespa.documentmodel.DocumentSummary;
import com.yahoo.vespa.documentmodel.SummaryField;
import com.yahoo.vespa.documentmodel.SummaryTransform;
import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.ConstantExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Expression;
import com.yahoo.vespa.indexinglanguage.expressions.OptimizePredicateExpression;
import com.yahoo.vespa.indexinglanguage.expressions.OutputExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.SetValueExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SetVarExpression;
import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
import com.yahoo.vespa.model.container.search.QueryProfiles;
@@ -112,14 +112,14 @@ public class PredicateProcessor extends Processor {
private Expression makeSetPredicateVariablesScript(BooleanIndexDefinition options) {
List<Expression> expressions = new ArrayList<>();
- expressions.add(new SetValueExpression(new IntegerFieldValue(options.getArity())));
+ expressions.add(new ConstantExpression(new IntegerFieldValue(options.getArity())));
expressions.add(new SetVarExpression("arity"));
if (options.hasLowerBound()) {
- expressions.add(new SetValueExpression(new LongFieldValue(options.getLowerBound())));
+ expressions.add(new ConstantExpression(new LongFieldValue(options.getLowerBound())));
expressions.add(new SetVarExpression("lower_bound"));
}
if (options.hasUpperBound()) {
- expressions.add(new SetValueExpression(new LongFieldValue(options.getUpperBound())));
+ expressions.add(new ConstantExpression(new LongFieldValue(options.getUpperBound())));
expressions.add(new SetVarExpression("upper_bound"));
}
return new StatementExpression(expressions);
diff --git a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryField.java b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryField.java
index 7439e65dee6..49cd36e4bc2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryField.java
+++ b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryField.java
@@ -17,9 +17,7 @@ import static com.yahoo.text.Lowercase.toLowerCase;
*/
public class SummaryField extends Field implements Cloneable, TypedKey {
- /**
- * A source (field name).
- */
+ /** A source (field name). */
public static class Source implements Serializable {
private final String name;
@@ -38,12 +36,8 @@ public class SummaryField extends Field implements Cloneable, TypedKey {
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof Source)) {
- return false;
- }
- Source other = (Source)obj;
- return name.equals(other.name) &&
- override == other.override;
+ if (!(obj instanceof Source other)) return false;
+ return name.equals(other.name) && override == other.override;
}
@Override
@@ -67,14 +61,14 @@ public class SummaryField extends Field implements Cloneable, TypedKey {
*/
private Set<Source> sources = new java.util.LinkedHashSet<>();
- private Set<String> destinations=new java.util.LinkedHashSet<>();
+ private Set<String> destinations =new java.util.LinkedHashSet<>();
/** True if this field was defined implicitly */
- private boolean implicit=false;
+ private boolean implicit = false;
/** Creates a summary field with NONE as transform */
public SummaryField(String name, DataType type) {
- this(name,type, SummaryTransform.NONE);
+ this(name, type, SummaryTransform.NONE);
}
/** Creates a summary field with NONE as transform */
@@ -97,7 +91,7 @@ public class SummaryField extends Field implements Cloneable, TypedKey {
public boolean isImplicit() { return implicit; }
public void setTransform(SummaryTransform transform) {
- this.transform=transform;
+ this.transform = transform;
if (SummaryTransform.DYNAMICTEASER.equals(transform) || SummaryTransform.BOLDED.equals(transform)) {
// This is the kind of logic we want to have in processing,
// but can't because of deriveDocuments mode, which doesn't run
@@ -110,9 +104,9 @@ public class SummaryField extends Field implements Cloneable, TypedKey {
/** Returns the first source field of this, or null if the source field is not present */
public String getSourceField() {
- String sourceName=getName();
- if (sources.size()>0)
- sourceName=sources.iterator().next().getName();
+ String sourceName = getName();
+ if ( ! sources.isEmpty())
+ sourceName = sources.iterator().next().getName();
return sourceName;
}
@@ -137,7 +131,7 @@ public class SummaryField extends Field implements Cloneable, TypedKey {
/** Returns the first source name of this, or the field name if no source has been set */
public String getSingleSource() {
- if (sources.size()==0) return getName();
+ if (sources.isEmpty()) return getName();
return sources.iterator().next().getName();
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
index 28ff8dff620..269cb2dfa08 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
@@ -7,8 +7,8 @@ import ai.vespa.rankingexpression.importer.onnx.OnnxImporter;
import ai.vespa.rankingexpression.importer.tensorflow.TensorFlowImporter;
import ai.vespa.rankingexpression.importer.vespa.VespaImporter;
import ai.vespa.rankingexpression.importer.xgboost.XGBoostImporter;
-import com.yahoo.component.annotation.Inject;
import com.yahoo.component.Version;
+import com.yahoo.component.annotation.Inject;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.ValidationOverrides;
@@ -197,7 +197,8 @@ public class VespaModelFactory implements ModelFactory {
.zone(zone)
.now(clock.instant())
.wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion())
- .wantedDockerImageRepo(modelContext.wantedDockerImageRepo());
+ .wantedDockerImageRepo(modelContext.wantedDockerImageRepo())
+ .onnxModelCost(modelContext.onnxModelCost());
modelContext.previousModel().ifPresent(builder::previousModel);
modelContext.reindexing().ifPresent(builder::reindexing);
return builder.build(validationParameters);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java
index aa3b8b3b821..d10a631fb90 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java
@@ -159,6 +159,7 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC
builder.consumer.add(toConsumerBuilder(MetricsConsumer.defaultConsumer));
builder.consumer.add(toConsumerBuilder(newDefaultConsumer()));
+ if (isHostedVespa()) builder.consumer.add(toConsumerBuilder(MetricsConsumer.vespa9));
getAdmin()
.map(Admin::getAmendedMetricsConsumers)
.map(consumers -> consumers.stream().map(ConsumersConfigGenerator::toConsumerBuilder).toList())
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java
index cfe3c01e03a..987812f11ad 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/MetricsConsumer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.model.admin.monitoring;
import ai.vespa.metrics.set.Metric;
import ai.vespa.metrics.set.MetricSet;
+import ai.vespa.metrics.set.Vespa9VespaMetricSet;
import ai.vespa.metricsproxy.core.VespaMetrics;
import ai.vespa.metricsproxy.http.ValuesFetcher;
@@ -41,6 +42,9 @@ public class MetricsConsumer {
public static final MetricsConsumer vespaCloud =
consumer("vespa-cloud", vespaMetricSet, systemMetricSet, networkMetricSet);
+ public static final MetricsConsumer vespa9 =
+ consumer("Vespa9", Vespa9VespaMetricSet.vespa9vespaMetricSet, systemMetricSet, networkMetricSet);
+
private final String id;
private final MetricSet metricSet;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidator.java
new file mode 100644
index 00000000000..f87cecb58fe
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidator.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.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.vespa.model.VespaModel;
+
+import java.util.logging.Level;
+
+/**
+ * Validates that the container node flavour has enough resources to run configured ONNX models.
+ *
+ * @author bjorncs
+ */
+public class JvmHeapSizeValidator extends Validator {
+
+ @Override
+ public void validate(VespaModel model, DeployState ds) {
+ if (!ds.featureFlags().dynamicHeapSize()) return;
+ if (!ds.isHostedTenantApplication(model.getAdmin().getApplicationType())) return;
+
+ model.getContainerClusters().forEach((clusterId, appCluster) -> {
+ var mp = appCluster.getMemoryPercentage().orElse(null);
+ if (mp == null) return;
+ if (mp.availableMemoryGb().isEmpty()) {
+ ds.getDeployLogger().log(Level.FINE, "Host resources unknown or percentage overridden with 'allocated-memory'");
+ return;
+ }
+ long jvmModelCost = appCluster.onnxModelCost().aggregatedModelCostInBytes();
+ if (jvmModelCost > 0) {
+ int percentLimit = 15;
+ double gbLimit = 0.6;
+ double availableMemoryGb = mp.availableMemoryGb().getAsDouble();
+ double modelCostGb = jvmModelCost / (1024D * 1024 * 1024);
+ ds.getDeployLogger().log(Level.FINE, () -> "JVM: %d%% (limit: %d%%), %.2fGB (limit: %.2fGB), ONNX: %.2fGB"
+ .formatted(mp.percentage(), percentLimit, availableMemoryGb, gbLimit, modelCostGb));
+ if (mp.percentage() < percentLimit) {
+ throw new IllegalArgumentException(
+ ("Allocated percentage of memory of JVM in cluster '%s' is too low (%d%% < %d%%). " +
+ "Estimated cost of ONNX models is %.2fGB. Either use a node flavor with more memory or use less expensive models. " +
+ "You may override this validation by specifying 'allocated-memory' (https://docs.vespa.ai/en/performance/container-tuning.html#jvm-heap-size).")
+ .formatted(clusterId, mp.percentage(), percentLimit, modelCostGb));
+ }
+ if (availableMemoryGb < gbLimit) {
+ throw new IllegalArgumentException(
+ ("Allocated memory to JVM in cluster '%s' is too low (%.2fGB < %.2fGB). " +
+ "Estimated cost of ONNX models is %.2fGB. Either use a node flavor with more memory or use less expensive models. " +
+ "You may override this validation by specifying 'allocated-memory' (https://docs.vespa.ai/en/performance/container-tuning.html#jvm-heap-size).")
+ .formatted(clusterId, availableMemoryGb, gbLimit, modelCostGb));
+ }
+ }
+ });
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/UrlConfigValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/UrlConfigValidator.java
new file mode 100644
index 00000000000..d9dd3729bd3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/UrlConfigValidator.java
@@ -0,0 +1,50 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
+
+/**
+ * Validates that config using s3:// urls is used in public system and with nodes that are exclusive.
+ *
+ * @author hmusum
+ */
+public class UrlConfigValidator extends Validator {
+
+ @Override
+ public void validate(VespaModel model, DeployState state) {
+ if (! state.isHostedTenantApplication(model.getAdmin().getApplicationType())) return;
+
+ model.getContainerClusters().forEach((__, cluster) -> {
+ var isExclusive = hasExclusiveNodes(model, cluster);
+ validateS3UlsInConfig(state, cluster, isExclusive);
+ });
+ }
+
+ private static boolean hasExclusiveNodes(VespaModel model, ApplicationContainerCluster cluster) {
+ return model.hostSystem().getHosts()
+ .stream()
+ .flatMap(hostResource -> hostResource.spec().membership().stream())
+ .filter(membership -> membership.cluster().id().equals(cluster.id()))
+ .anyMatch(membership -> membership.cluster().isExclusive());
+ }
+
+ private static void validateS3UlsInConfig(DeployState state, ApplicationContainerCluster cluster, boolean isExclusive) {
+ if (hasS3UrlInConfig(cluster)) {
+ // TODO: Would be even better if we could add which config/field the url is set for in the error message
+ String message = "Found s3:// urls in config for container cluster " + cluster.getName();
+ if ( ! state.zone().system().isPublic())
+ throw new IllegalArgumentException(message + ". This is only supported in public systems");
+ else if ( ! isExclusive)
+ throw new IllegalArgumentException(message + ". Nodes in the cluster need to be 'exclusive'," +
+ " see https://cloud.vespa.ai/en/reference/services#nodes");
+ }
+ }
+
+ private static boolean hasS3UrlInConfig(ApplicationContainerCluster cluster) {
+ return cluster.userConfiguredUrls().all().stream()
+ .anyMatch(url -> url.startsWith("s3://"));
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
index 53a553ee624..30aafe67be7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
@@ -87,6 +87,8 @@ public class Validation {
new AccessControlFilterExcludeValidator().validate(model, deployState);
new CloudUserFilterValidator().validate(model, deployState);
new CloudHttpConnectorValidator().validate(model, deployState);
+ new UrlConfigValidator().validate(model, deployState);
+ new JvmHeapSizeValidator().validate(model, deployState);
additionalValidators.forEach(v -> v.validate(model, deployState));
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeMessageBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeMessageBuilder.java
index bbfa939f8a3..f265f2d09a0 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeMessageBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeMessageBuilder.java
@@ -7,6 +7,7 @@ import com.yahoo.schema.document.Matching;
import com.yahoo.schema.document.MatchType;
import com.yahoo.schema.document.NormalizeLevel;
import com.yahoo.schema.document.Stemming;
+import com.yahoo.schema.processing.NGramMatch;
import com.yahoo.vespa.documentmodel.SummaryField;
import com.yahoo.vespa.documentmodel.SummaryTransform;
@@ -89,7 +90,7 @@ public class IndexingScriptChangeMessageBuilder {
MatchType type = matching.getType();
String retval = type.getName();
if (type == MatchType.GRAM) {
- retval += " (size " + matching.getGramSize() + ")";
+ retval += " (size " + matching.getGramSize().orElse(NGramMatch.DEFAULT_GRAM_SIZE) + ")";
}
return retval;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomComponentBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomComponentBuilder.java
index 7501f6162c7..9ecd359f90d 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomComponentBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomComponentBuilder.java
@@ -7,11 +7,12 @@ import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.text.XML;
-import com.yahoo.vespa.model.container.component.HuggingFaceEmbedder;
-import com.yahoo.vespa.model.container.component.HuggingFaceTokenizer;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.component.BertEmbedder;
import com.yahoo.vespa.model.container.component.ColBertEmbedder;
import com.yahoo.vespa.model.container.component.Component;
+import com.yahoo.vespa.model.container.component.HuggingFaceEmbedder;
+import com.yahoo.vespa.model.container.component.HuggingFaceTokenizer;
import com.yahoo.vespa.model.container.xml.BundleInstantiationSpecificationBuilder;
import org.w3c.dom.Element;
@@ -35,19 +36,20 @@ public class DomComponentBuilder extends VespaDomBuilder.DomConfigProducerBuilde
@Override
protected Component<? super Component<?, ?>, ?> doBuild(DeployState deployState, TreeConfigProducer<AnyConfigProducer> ancestor, Element spec) {
- var component = buildComponent(spec, deployState);
+ var component = buildComponent(spec, deployState, ancestor);
addChildren(deployState, ancestor, spec, component);
return component;
}
- private Component<? super Component<?, ?>, ?> buildComponent(Element spec, DeployState state) {
+ private Component<? super Component<?, ?>, ?> buildComponent(
+ Element spec, DeployState state, TreeConfigProducer<AnyConfigProducer> ancestor) {
if (spec.hasAttribute("type")) {
var type = spec.getAttribute("type");
return switch (type) {
- case "hugging-face-embedder" -> new HuggingFaceEmbedder(spec, state);
+ case "hugging-face-embedder" -> new HuggingFaceEmbedder((ApplicationContainerCluster)ancestor, spec, state);
case "hugging-face-tokenizer" -> new HuggingFaceTokenizer(spec, state);
- case "bert-embedder" -> new BertEmbedder(spec, state);
- case "colbert-embedder" -> new ColBertEmbedder(spec, state);
+ case "colbert-embedder" -> new ColBertEmbedder((ApplicationContainerCluster)ancestor, spec, state);
+ case "bert-embedder" -> new BertEmbedder((ApplicationContainerCluster)ancestor, spec, state);
default -> throw new IllegalArgumentException("Unknown component type '%s'".formatted(type));
};
} else {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java
index 0795fdf41d6..762c670039c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.clients;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.handler.threadpool.ContainerThreadpoolConfig;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.model.container.ContainerCluster;
@@ -10,6 +11,7 @@ import com.yahoo.vespa.model.container.component.BindingPattern;
import com.yahoo.vespa.model.container.component.Handler;
import com.yahoo.vespa.model.container.component.SystemBindingPattern;
import com.yahoo.vespa.model.container.component.UserBindingPattern;
+import org.w3c.dom.Element;
import java.nio.file.Path;
import java.util.Collection;
@@ -29,10 +31,11 @@ public class ContainerDocumentApi {
private final boolean ignoreUndefinedFields;
- public ContainerDocumentApi(ContainerCluster<?> cluster, HandlerOptions handlerOptions, boolean ignoreUndefinedFields, Set<Integer> portOverride) {
+ public ContainerDocumentApi(DeployState ds, ContainerCluster<?> cluster, HandlerOptions handlerOptions,
+ boolean ignoreUndefinedFields, Set<Integer> portOverride) {
this.ignoreUndefinedFields = ignoreUndefinedFields;
addRestApiHandler(cluster, handlerOptions, portOverride);
- addFeedHandler(cluster, handlerOptions, portOverride);
+ addFeedHandler(ds, cluster, handlerOptions, portOverride);
addVespaClientContainerBundle(cluster);
}
@@ -40,9 +43,9 @@ public class ContainerDocumentApi {
c.addPlatformBundle(VESPACLIENT_CONTAINER_BUNDLE);
}
- private static void addFeedHandler(ContainerCluster<?> cluster, HandlerOptions handlerOptions, Set<Integer> portOverride) {
+ private static void addFeedHandler(DeployState ds, ContainerCluster<?> cluster, HandlerOptions handlerOptions, Set<Integer> portOverride) {
String bindingSuffix = ContainerCluster.RESERVED_URI_PREFIX + "/feedapi";
- var executor = new Threadpool("feedapi-handler", handlerOptions.feedApiThreadpoolOptions);
+ var executor = new Threadpool(ds, "feedapi-handler", handlerOptions.feedApiThreadpoolOptions);
var handler = newVespaClientHandler("com.yahoo.vespa.http.server.FeedHandler",
bindingSuffix, handlerOptions, executor, portOverride);
cluster.addComponent(handler);
@@ -104,9 +107,9 @@ public class ContainerDocumentApi {
public static final class HandlerOptions {
private final Collection<String> bindings;
- private final ContainerThreadpool.UserOptions feedApiThreadpoolOptions;
+ private final Element feedApiThreadpoolOptions;
- public HandlerOptions(Collection<String> bindings, ContainerThreadpool.UserOptions feedApiThreadpoolOptions) {
+ public HandlerOptions(Collection<String> bindings, Element feedApiThreadpoolOptions) {
this.bindings = Collections.unmodifiableCollection(bindings);
this.feedApiThreadpoolOptions = feedApiThreadpoolOptions;
}
@@ -114,9 +117,7 @@ public class ContainerDocumentApi {
private static class Threadpool extends ContainerThreadpool {
- Threadpool(String name, ContainerThreadpool.UserOptions threadpoolOptions) {
- super(name, threadpoolOptions);
- }
+ Threadpool(DeployState ds, String name, Element xml) { super(ds, name, xml); }
@Override
protected void setDefaultConfigValues(ContainerThreadpoolConfig.Builder builder) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
index b9021912244..ac679cc406c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
@@ -8,14 +8,14 @@ import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.ComponentInfo;
+import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ApplicationClusterInfo;
-import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.config.provision.AllocatedHosts;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.di.config.ApplicationBundlesConfig;
@@ -43,10 +43,12 @@ import com.yahoo.vespa.model.filedistribution.UserConfiguredFiles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.logging.Level;
import java.util.stream.Collectors;
import static com.yahoo.vespa.model.container.docproc.DocprocChains.DOCUMENT_TYPE_MANAGER_CLASS;
@@ -82,6 +84,8 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private final Set<FileReference> applicationBundles = new LinkedHashSet<>();
private final Set<String> previousHosts;
+ private final OnnxModelCost.Calculator onnxModelCost;
+ private final DeployLogger logger;
private ContainerModelEvaluation modelEvaluation;
@@ -92,6 +96,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private int zookeeperSessionTimeoutSeconds = 30;
private final int transport_events_before_wakeup;
private final int transport_connections_per_target;
+ private final boolean dynamicHeapSize;
/** The heap size % of total memory available to the JVM process. */
private final int heapSizePercentageOfAvailableMemory;
@@ -100,9 +105,12 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private List<ApplicationClusterEndpoint> endpoints = List.of();
+ private final UserConfiguredUrls userConfiguredUrls = new UserConfiguredUrls();
+
public ApplicationContainerCluster(TreeConfigProducer<?> parent, String configSubId, String clusterId, DeployState deployState) {
super(parent, configSubId, clusterId, deployState, true, 10);
this.tlsClientAuthority = deployState.tlsClientAuthority();
+ dynamicHeapSize = deployState.featureFlags().dynamicHeapSize();
previousHosts = Collections.unmodifiableSet(deployState.getPreviousModel().stream()
.map(Model::allocatedHosts)
.map(AllocatedHosts::getHosts)
@@ -125,8 +133,13 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
heapSizePercentageOfAvailableMemory = deployState.featureFlags().heapSizePercentage() > 0
? Math.min(99, deployState.featureFlags().heapSizePercentage())
: defaultHeapSizePercentageOfAvailableMemory;
+ onnxModelCost = deployState.onnxModelCost().newCalculator(
+ deployState.getApplicationPackage(), deployState.getDeployLogger());
+ logger = deployState.getDeployLogger();
}
+ public UserConfiguredUrls userConfiguredUrls() { return userConfiguredUrls; }
+
@Override
protected void doPrepare(DeployState deployState) {
super.doPrepare(deployState);
@@ -147,7 +160,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
if (containers.isEmpty()) return;
// Files referenced from user configs to all components.
- UserConfiguredFiles files = new UserConfiguredFiles(deployState.getFileRegistry(), deployState.getDeployLogger());
+ UserConfiguredFiles files = new UserConfiguredFiles(deployState.getFileRegistry(),
+ deployState.getDeployLogger(),
+ deployState.featureFlags(),
+ userConfiguredUrls);
for (Component<?, ?> component : getAllComponents()) {
files.register(component);
}
@@ -182,19 +198,25 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
public void setMemoryPercentage(Integer memoryPercentage) { this.memoryPercentage = memoryPercentage; }
@Override
- public Optional<Integer> getMemoryPercentage() {
- if (memoryPercentage != null) return Optional.of(memoryPercentage);
+ public Optional<JvmMemoryPercentage> getMemoryPercentage() {
+ if (memoryPercentage != null) return Optional.of(JvmMemoryPercentage.of(memoryPercentage));
if (isHostedVespa()) {
int availableMemoryPercentage = getHostClusterId().isPresent() ?
heapSizePercentageOfTotalAvailableMemoryWhenCombinedCluster :
heapSizePercentageOfAvailableMemory;
- if (getContainers().isEmpty()) return Optional.of(availableMemoryPercentage); // Node memory is not known
+ if (getContainers().isEmpty()) return Optional.of(JvmMemoryPercentage.of(availableMemoryPercentage)); // Node memory is not known
// Node memory is known so convert available memory percentage to node memory percentage
- double totalMemory = getContainers().get(0).getHostResource().realResources().memoryGb();
- double availableMemory = totalMemory - Host.memoryOverheadGb;
- return Optional.of((int) (availableMemory / totalMemory * availableMemoryPercentage));
+ double totalMemory = dynamicHeapSize
+ ? getContainers().stream().mapToDouble(c -> c.getHostResource().realResources().memoryGb()).min().orElseThrow()
+ : getContainers().get(0).getHostResource().realResources().memoryGb();
+ double jvmHeapDeductionGb = dynamicHeapSize ? onnxModelCost.aggregatedModelCostInBytes() / (1024D * 1024 * 1024) : 0;
+ double availableMemory = Math.max(0, totalMemory - Host.memoryOverheadGb - jvmHeapDeductionGb);
+ int memoryPercentage = (int) (availableMemory / totalMemory * availableMemoryPercentage);
+ logger.log(Level.FINE, () -> "memoryPercentage=%d, availableMemory=%f, totalMemory=%f, availableMemoryPercentage=%d, jvmHeapDeductionGb=%f"
+ .formatted(memoryPercentage, availableMemory, totalMemory, availableMemoryPercentage, jvmHeapDeductionGb));
+ return Optional.of(JvmMemoryPercentage.of(memoryPercentage, availableMemory));
}
return Optional.empty();
}
@@ -203,49 +225,23 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private void createEndpoints(DeployState deployState) {
if (!deployState.isHosted()) return;
if (deployState.getProperties().applicationId().instance().isTester()) return;
+ // Add endpoints provided by the controller
+ List<String> hosts = getContainers().stream().map(AbstractService::getHostName).sorted().toList();
List<ApplicationClusterEndpoint> endpoints = new ArrayList<>();
-
- List<String> hosts = getContainers().stream()
- .map(AbstractService::getHostName)
- .sorted()
- .toList();
-
- Set<ContainerEndpoint> endpointsFromController = deployState.getEndpoints();
- // Add zone-scoped endpoints if not provided by the controller
- // TODO(mpolden): Remove this when controller always includes zone-scope endpoints, and config models < 8.230 are gone
- if (endpointsFromController.stream().noneMatch(endpoint -> endpoint.scope() == ApplicationClusterEndpoint.Scope.zone)) {
- for (String suffix : deployState.getProperties().zoneDnsSuffixes()) {
- ApplicationClusterEndpoint.DnsName l4Name = ApplicationClusterEndpoint.DnsName.sharedL4NameFrom(
- deployState.zone().system(),
- ClusterSpec.Id.from(getName()),
- deployState.getProperties().applicationId(),
- suffix);
- endpoints.add(ApplicationClusterEndpoint.builder()
- .zoneScope()
- .sharedL4Routing()
- .dnsName(l4Name)
- .hosts(hosts)
- .clusterId(getName())
- .authMethod(ApplicationClusterEndpoint.AuthMethod.mtls)
- .build());
- }
- }
-
- // Include all endpoints provided by controller
- endpointsFromController.stream()
- .filter(ce -> ce.clusterId().equals(getName()))
- .forEach(ce -> ce.names().forEach(
- name -> endpoints.add(ApplicationClusterEndpoint.builder()
- .scope(ce.scope())
- .weight(ce.weight().orElse(1)) // Default to weight=1 if not set
- .routingMethod(ce.routingMethod())
- .dnsName(ApplicationClusterEndpoint.DnsName.from(name))
- .hosts(hosts)
- .clusterId(getName())
- .authMethod(ce.authMethod())
- .build())
- ));
- this.endpoints = List.copyOf(endpoints);
+ deployState.getEndpoints().stream()
+ .filter(ce -> ce.clusterId().equals(getName()))
+ .forEach(ce -> ce.names().forEach(
+ name -> endpoints.add(ApplicationClusterEndpoint.builder()
+ .scope(ce.scope())
+ .weight(ce.weight().orElse(1))
+ .routingMethod(ce.routingMethod())
+ .dnsName(ApplicationClusterEndpoint.DnsName.from(name))
+ .hosts(hosts)
+ .clusterId(getName())
+ .authMethod(ce.authMethod())
+ .build())
+ ));
+ this.endpoints = Collections.unmodifiableList(endpoints);
}
@Override
@@ -299,12 +295,15 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
@Override
public void getConfig(QrStartConfig.Builder builder) {
super.getConfig(builder);
+ var memoryPct = getMemoryPercentage().orElse(null);
+ int heapsize = memoryPct != null && memoryPct.availableMemoryGb().isPresent()
+ ? (int) (memoryPct.availableMemoryGb().getAsDouble() * 1024) : 1536;
builder.jvm.verbosegc(true)
.availableProcessors(0)
.compressedClassSpaceSize(0)
- .minHeapsize(1536)
- .heapsize(1536);
- getMemoryPercentage().ifPresent(percentage -> builder.jvm.heapSizeAsPercentageOfPhysicalMemory(percentage));
+ .minHeapsize(heapsize)
+ .heapsize(heapsize);
+ if (memoryPct != null) builder.jvm.heapSizeAsPercentageOfPhysicalMemory(memoryPct.percentage());
}
@Override
@@ -373,6 +372,8 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
@Override
public String name() { return getName(); }
+ public OnnxModelCost.Calculator onnxModelCost() { return onnxModelCost; }
+
public static class MbusParams {
// the amount of the maxpendingbytes to process concurrently, typically 0.2 (20%)
final Double maxConcurrentFactor;
@@ -390,4 +391,14 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
}
}
+ public static class UserConfiguredUrls {
+
+ private final Set<String> urls = new HashSet<>();
+
+ public void add(String url) { urls.add(url); }
+
+ public Set<String> all() { return urls; }
+
+ }
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
index 6bbc24e8739..3d4ec51c8d2 100755
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
@@ -62,6 +62,7 @@ import com.yahoo.vespa.model.container.search.ContainerSearch;
import com.yahoo.vespa.model.container.search.searchchain.SearchChains;
import com.yahoo.vespa.model.content.Content;
import com.yahoo.vespa.model.search.SearchCluster;
+
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
@@ -71,6 +72,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalDouble;
import java.util.Set;
import java.util.TreeSet;
@@ -142,7 +144,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
private ContainerDocproc containerDocproc;
private ContainerDocumentApi containerDocumentApi;
private SecretStore secretStore;
- private final ContainerThreadpool defaultHandlerThreadpool = new Handler.DefaultHandlerThreadpool();
+ private final ContainerThreadpool defaultHandlerThreadpool;
private boolean rpcServerEnabled = true;
private boolean httpServerEnabled = true;
@@ -185,6 +187,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
addCommonVespaBundles();
addSimpleComponent(VoidRequestLog.class);
addComponent(new DefaultThreadpoolProvider(this, defaultPoolNumThreads));
+ defaultHandlerThreadpool = new Handler.DefaultHandlerThreadpool(deployState, null);
addComponent(defaultHandlerThreadpool);
addSimpleComponent(com.yahoo.concurrent.classlock.ClassLocking.class);
addSimpleComponent("com.yahoo.container.jdisc.metric.MetricConsumerProviderProvider");
@@ -718,5 +721,11 @@ public abstract class ContainerCluster<CONTAINER extends Container>
* Returns the percentage of host physical memory this application has specified for nodes in this cluster,
* or empty if this is not specified by the application.
*/
- public Optional<Integer> getMemoryPercentage() { return Optional.empty(); }
+ public record JvmMemoryPercentage(int percentage, OptionalDouble availableMemoryGb) {
+ static JvmMemoryPercentage of(int percentage) { return new JvmMemoryPercentage(percentage, OptionalDouble.empty()); }
+ static JvmMemoryPercentage of(int percentage, double availableMemoryGb) {
+ return new JvmMemoryPercentage(percentage, OptionalDouble.of(availableMemoryGb));
+ }
+ }
+ public Optional<JvmMemoryPercentage> getMemoryPercentage() { return Optional.empty(); }
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java
index 906ef739ef1..1b47f59653e 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java
@@ -45,10 +45,6 @@ public class ContainerModelEvaluation implements
private final RankProfileList rankProfileList;
private final FileDistributedOnnxModels onnxModels; // For cluster specific ONNX model settings
- public ContainerModelEvaluation(ApplicationContainerCluster cluster, RankProfileList rankProfileList) {
- this(cluster, rankProfileList, null);
- }
-
public ContainerModelEvaluation(ApplicationContainerCluster cluster,
RankProfileList rankProfileList, FileDistributedOnnxModels onnxModels) {
this.rankProfileList = Objects.requireNonNull(rankProfileList, "rankProfileList cannot be null");
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerThreadpool.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerThreadpool.java
index fb4e62f5cd1..4b85c384951 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerThreadpool.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerThreadpool.java
@@ -1,16 +1,17 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container;
+import com.yahoo.config.model.builder.xml.XmlHelper;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.handler.threadpool.ContainerThreadPool;
import com.yahoo.container.handler.threadpool.ContainerThreadpoolConfig;
import com.yahoo.container.handler.threadpool.ContainerThreadpoolImpl;
import com.yahoo.osgi.provider.model.ComponentModel;
-import com.yahoo.text.XML;
import com.yahoo.vespa.model.container.component.SimpleComponent;
import org.w3c.dom.Element;
-import java.util.Optional;
+import java.util.logging.Level;
/**
* Component definition for a {@link java.util.concurrent.Executor} using {@link ContainerThreadPool}.
@@ -20,18 +21,50 @@ import java.util.Optional;
public abstract class ContainerThreadpool extends SimpleComponent implements ContainerThreadpoolConfig.Producer {
private final String name;
- private final UserOptions userOptions;
+ private final UserOptions options;
- public ContainerThreadpool(String name, UserOptions userOptions) {
+ record UserOptions(Double max, Double min, Double queue){}
+
+ protected ContainerThreadpool(DeployState ds, String name, Element parent) {
super(new ComponentModel(
BundleInstantiationSpecification.fromStrings(
"threadpool@" + name,
ContainerThreadpoolImpl.class.getName(),
null)));
this.name = name;
- this.userOptions = userOptions;
+ var threadpoolElem = XmlHelper.getOptionalChild(parent, "threadpool").orElse(null);
+ if (threadpoolElem == null) options = new UserOptions(null, null, null);
+ else {
+ // TODO Vespa 9 Remove min-threads, max-threads and queue-size
+ Double max = null;
+ Double min = null;
+ Double queue = null;
+ var minElem = XmlHelper.getOptionalChild(threadpoolElem, "min-threads").orElse(null);
+ if (minElem != null) ds.getDeployLogger().logApplicationPackage(Level.WARNING, "For <threadpool>: <min-threads> is deprecated, use <threads> instead");
+ var maxElem = XmlHelper.getOptionalChild(threadpoolElem, "max-threads").orElse(null);
+ if (maxElem != null) ds.getDeployLogger().logApplicationPackage(Level.WARNING, "For <threadpool>: <max-threads> is deprecated, use <threads> with 'boost' instead");
+ var queueElem = XmlHelper.getOptionalChild(threadpoolElem, "queue").orElse(null);
+ var queueSizeElem = XmlHelper.getOptionalChild(threadpoolElem, "queue-size").orElse(null);
+ if (queueSizeElem != null) ds.getDeployLogger().logApplicationPackage(Level.WARNING, "For <threadpool>: <queue-size> is deprecated, use <queue> instead");
+ var threadsElem = XmlHelper.getOptionalChild(threadpoolElem, "threads").orElse(null);
+ if (threadsElem != null) {
+ min = parseMultiplier(threadsElem.getTextContent());
+ max = threadsElem.hasAttribute("boost") ? parseMultiplier(threadsElem.getAttribute("boost")) : min;
+ } else if (minElem != null) {
+ min = parseFixed(minElem.getTextContent());
+ }
+ if (max == null && maxElem != null) {
+ max = parseFixed(maxElem.getTextContent());
+ }
+ if (queueElem != null) queue = parseMultiplier(queueElem.getTextContent());
+ else if (queueSizeElem != null) queue = parseFixed(queueSizeElem.getTextContent());
+ options = new UserOptions(max, min, queue);
+ }
}
+ private static Double parseMultiplier(String text) { return -parseFixed(text); }
+ private static Double parseFixed(String text) { return Double.parseDouble(text); }
+
// Must be implemented by subclasses to set values that may be overridden by user options.
protected abstract void setDefaultConfigValues(ContainerThreadpoolConfig.Builder builder);
@@ -40,35 +73,20 @@ public abstract class ContainerThreadpool extends SimpleComponent implements Con
setDefaultConfigValues(builder);
builder.name(this.name);
- if (userOptions != null) {
- builder.maxThreads(userOptions.maxThreads);
- builder.minThreads(userOptions.minThreads);
- builder.queueSize(userOptions.queueSize);
+ if (options.max() != null) {
+ int max = (int) Math.round(options.max());
+ if (options.max() != 0 && max == 0) max = options.max() > 0 ? 1 : -1;
+ builder.maxThreads(max);
}
- }
-
- public static class UserOptions {
- private final int maxThreads;
- private final int minThreads;
- private final int queueSize;
-
- private UserOptions(int maxThreads, int minThreads, int queueSize) {
- this.maxThreads = maxThreads;
- this.minThreads = minThreads;
- this.queueSize = queueSize;
- }
-
- public static Optional<UserOptions> fromXml(Element xml) {
- Element element = XML.getChild(xml, "threadpool");
- if (element == null) return Optional.empty();
- return Optional.of(new UserOptions(
- intOption(element, "max-threads"),
- intOption(element, "min-threads"),
- intOption(element, "queue-size")));
+ if (options.min() != null) {
+ int min = (int) Math.round(options.min());
+ if (options.min() != 0 && min == 0) min = options.min() > 0 ? 1 : -1;
+ builder.minThreads(min);
}
-
- private static int intOption(Element element, String name) {
- return Integer.parseInt(XML.getChild(element, name).getTextContent());
+ if (options.queue() != null) {
+ int queue = (int) Math.round(options.queue());
+ if (options.queue() != 0 && queue == 0) queue = options.queue() > 0 ? 1 : -1;
+ builder.queueSize(queue);
}
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/BertEmbedder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/BertEmbedder.java
index 205848e1b67..d02b7d0de5f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/BertEmbedder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/BertEmbedder.java
@@ -5,10 +5,9 @@ package com.yahoo.vespa.model.container.component;
import com.yahoo.config.ModelReference;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.embedding.BertBaseEmbedderConfig;
-import com.yahoo.vespa.model.container.xml.ModelIdResolver;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import org.w3c.dom.Element;
-import static com.yahoo.text.XML.getChild;
import static com.yahoo.text.XML.getChildValue;
import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATION_BUNDLE_NAME;
@@ -17,8 +16,8 @@ import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATI
*/
public class BertEmbedder extends TypedComponent implements BertBaseEmbedderConfig.Producer {
- private final ModelReference model;
- private final ModelReference vocab;
+ private final ModelReference modelRef;
+ private final ModelReference vocabRef;
private final Integer maxTokens;
private final String transformerInputIds;
private final String transformerAttentionMask;
@@ -33,10 +32,11 @@ public class BertEmbedder extends TypedComponent implements BertBaseEmbedderConf
private final Integer onnxGpuDevice;
- public BertEmbedder(Element xml, DeployState state) {
+ public BertEmbedder(ApplicationContainerCluster cluster, Element xml, DeployState state) {
super("ai.vespa.embedding.BertBaseEmbedder", INTEGRATION_BUNDLE_NAME, xml);
- model = ModelIdResolver.resolveToModelReference(getChild(xml, "transformer-model"), state);
- vocab = ModelIdResolver.resolveToModelReference(getChild(xml, "tokenizer-vocab"), state);
+ var model = Model.fromXml(state, xml, "transformer-model").orElseThrow();
+ modelRef = model.modelReference();
+ vocabRef = Model.fromXml(state, xml, "tokenizer-vocab").orElseThrow().modelReference();
maxTokens = getChildValue(xml, "max-tokens").map(Integer::parseInt).orElse(null);
transformerInputIds = getChildValue(xml, "transformer-input-ids").orElse(null);
transformerAttentionMask = getChildValue(xml, "transformer-attention-mask").orElse(null);
@@ -49,11 +49,12 @@ public class BertEmbedder extends TypedComponent implements BertBaseEmbedderConf
onnxInteropThreads = getChildValue(xml, "onnx-interop-threads").map(Integer::parseInt).orElse(null);
onnxIntraopThreads = getChildValue(xml, "onnx-intraop-threads").map(Integer::parseInt).orElse(null);
onnxGpuDevice = getChildValue(xml, "onnx-gpu-device").map(Integer::parseInt).orElse(null);
+ model.registerOnnxModelCost(cluster);
}
@Override
public void getConfig(BertBaseEmbedderConfig.Builder b) {
- b.transformerModel(model).tokenizerVocab(vocab);
+ b.transformerModel(modelRef).tokenizerVocab(vocabRef);
if (maxTokens != null) b.transformerMaxTokens(maxTokens);
if (transformerInputIds != null) b.transformerInputIds(transformerInputIds);
if (transformerAttentionMask != null) b.transformerAttentionMask(transformerAttentionMask);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ColBertEmbedder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ColBertEmbedder.java
index c0fdfe3dc64..66e3b1c9dfd 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ColBertEmbedder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ColBertEmbedder.java
@@ -5,13 +5,9 @@ package com.yahoo.vespa.model.container.component;
import com.yahoo.config.ModelReference;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.embedding.ColBertEmbedderConfig;
-import com.yahoo.embedding.huggingface.HuggingFaceEmbedderConfig;
-import com.yahoo.vespa.model.container.xml.ModelIdResolver;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import org.w3c.dom.Element;
-import java.util.Optional;
-
-import static com.yahoo.config.model.builder.xml.XmlHelper.getOptionalChild;
import static com.yahoo.text.XML.getChildValue;
import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATION_BUNDLE_NAME;
@@ -20,8 +16,8 @@ import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATI
* @author bergum
*/
public class ColBertEmbedder extends TypedComponent implements ColBertEmbedderConfig.Producer {
- private final ModelReference model;
- private final ModelReference vocab;
+ private final ModelReference modelRef;
+ private final ModelReference vocabRef;
private final Integer maxQueryTokens;
@@ -40,13 +36,13 @@ public class ColBertEmbedder extends TypedComponent implements ColBertEmbedderCo
private final Integer onnxIntraopThreads;
private final Integer onnxGpuDevice;
- public ColBertEmbedder(Element xml, DeployState state) {
+ public ColBertEmbedder(ApplicationContainerCluster cluster, Element xml, DeployState state) {
super("ai.vespa.embedding.ColBertEmbedder", INTEGRATION_BUNDLE_NAME, xml);
- var transformerModelElem = getOptionalChild(xml, "transformer-model").orElseThrow();
- model = ModelIdResolver.resolveToModelReference(transformerModelElem, state);
- vocab = getOptionalChild(xml, "tokenizer-model")
- .map(elem -> ModelIdResolver.resolveToModelReference(elem, state))
- .orElseGet(() -> resolveDefaultVocab(transformerModelElem, state));
+ var model = Model.fromXml(state, xml, "transformer-model").orElseThrow();
+ modelRef = model.modelReference();
+ vocabRef = Model.fromXml(state, xml, "tokenizer-model")
+ .map(Model::modelReference)
+ .orElseGet(() -> resolveDefaultVocab(model, state));
maxTokens = getChildValue(xml, "max-tokens").map(Integer::parseInt).orElse(null);
maxQueryTokens = getChildValue(xml, "max-query-tokens").map(Integer::parseInt).orElse(null);
maxDocumentTokens = getChildValue(xml, "max-document-tokens").map(Integer::parseInt).orElse(null);
@@ -60,21 +56,20 @@ public class ColBertEmbedder extends TypedComponent implements ColBertEmbedderCo
onnxInteropThreads = getChildValue(xml, "onnx-interop-threads").map(Integer::parseInt).orElse(null);
onnxIntraopThreads = getChildValue(xml, "onnx-intraop-threads").map(Integer::parseInt).orElse(null);
onnxGpuDevice = getChildValue(xml, "onnx-gpu-device").map(Integer::parseInt).orElse(null);
-
+ model.registerOnnxModelCost(cluster);
}
- private static ModelReference resolveDefaultVocab(Element model, DeployState state) {
- if (state.isHosted() && model.hasAttribute("model-id")) {
- var implicitVocabId = model.getAttribute("model-id") + "-vocab";
- return ModelIdResolver.resolveToModelReference(
- "tokenizer-model", Optional.of(implicitVocabId), Optional.empty(), Optional.empty(), state);
+ private static ModelReference resolveDefaultVocab(Model model, DeployState state) {
+ var modelId = model.modelId().orElse(null);
+ if (state.isHosted() && modelId != null) {
+ return Model.fromParams(state, model.name(), modelId + "-vocab", null, null).modelReference();
}
throw new IllegalArgumentException("'tokenizer-model' must be specified");
}
@Override
public void getConfig(ColBertEmbedderConfig.Builder b) {
- b.transformerModel(model).tokenizerPath(vocab);
+ b.transformerModel(modelRef).tokenizerPath(vocabRef);
if (maxTokens != null) b.transformerMaxTokens(maxTokens);
if (transformerInputIds != null) b.transformerInputIds(transformerInputIds);
if (transformerAttentionMask != null) b.transformerAttentionMask(transformerAttentionMask);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java
index 31031aa5bf2..969db6553e6 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Handler.java
@@ -1,9 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.component;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.handler.threadpool.ContainerThreadpoolConfig;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.model.container.ContainerThreadpool;
+import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.Arrays;
@@ -76,8 +78,8 @@ public class Handler extends Component<Component<?, ?>, ComponentModel> {
*/
public static class DefaultHandlerThreadpool extends ContainerThreadpool {
- public DefaultHandlerThreadpool() {
- super("default-handler-common", null);
+ public DefaultHandlerThreadpool(DeployState ds, Element options) {
+ super(ds, "default-handler-common", options);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceEmbedder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceEmbedder.java
index f4017339699..af47bee137a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceEmbedder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceEmbedder.java
@@ -5,12 +5,9 @@ package com.yahoo.vespa.model.container.component;
import com.yahoo.config.ModelReference;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.embedding.huggingface.HuggingFaceEmbedderConfig;
-import com.yahoo.vespa.model.container.xml.ModelIdResolver;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import org.w3c.dom.Element;
-import java.util.Optional;
-
-import static com.yahoo.config.model.builder.xml.XmlHelper.getOptionalChild;
import static com.yahoo.text.XML.getChildValue;
import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATION_BUNDLE_NAME;
@@ -19,8 +16,8 @@ import static com.yahoo.vespa.model.container.ContainerModelEvaluation.INTEGRATI
* @author bjorncs
*/
public class HuggingFaceEmbedder extends TypedComponent implements HuggingFaceEmbedderConfig.Producer {
- private final ModelReference model;
- private final ModelReference vocab;
+ private final ModelReference modelRef;
+ private final ModelReference vocabRef;
private final Integer maxTokens;
private final String transformerInputIds;
private final String transformerAttentionMask;
@@ -33,13 +30,13 @@ public class HuggingFaceEmbedder extends TypedComponent implements HuggingFaceEm
private final Integer onnxGpuDevice;
private final String poolingStrategy;
- public HuggingFaceEmbedder(Element xml, DeployState state) {
+ public HuggingFaceEmbedder(ApplicationContainerCluster cluster, Element xml, DeployState state) {
super("ai.vespa.embedding.huggingface.HuggingFaceEmbedder", INTEGRATION_BUNDLE_NAME, xml);
- var transformerModelElem = getOptionalChild(xml, "transformer-model").orElseThrow();
- model = ModelIdResolver.resolveToModelReference(transformerModelElem, state);
- vocab = getOptionalChild(xml, "tokenizer-model")
- .map(elem -> ModelIdResolver.resolveToModelReference(elem, state))
- .orElseGet(() -> resolveDefaultVocab(transformerModelElem, state));
+ var model = Model.fromXml(state, xml, "transformer-model").orElseThrow();
+ modelRef = model.modelReference();
+ vocabRef = Model.fromXml(state, xml, "tokenizer-model")
+ .map(Model::modelReference)
+ .orElseGet(() -> resolveDefaultVocab(model, state));
maxTokens = getChildValue(xml, "max-tokens").map(Integer::parseInt).orElse(null);
transformerInputIds = getChildValue(xml, "transformer-input-ids").orElse(null);
transformerAttentionMask = getChildValue(xml, "transformer-attention-mask").orElse(null);
@@ -51,20 +48,20 @@ public class HuggingFaceEmbedder extends TypedComponent implements HuggingFaceEm
onnxIntraopThreads = getChildValue(xml, "onnx-intraop-threads").map(Integer::parseInt).orElse(null);
onnxGpuDevice = getChildValue(xml, "onnx-gpu-device").map(Integer::parseInt).orElse(null);
poolingStrategy = getChildValue(xml, "pooling-strategy").orElse(null);
+ model.registerOnnxModelCost(cluster);
}
- private static ModelReference resolveDefaultVocab(Element model, DeployState state) {
- if (state.isHosted() && model.hasAttribute("model-id")) {
- var implicitVocabId = model.getAttribute("model-id") + "-vocab";
- return ModelIdResolver.resolveToModelReference(
- "tokenizer-model", Optional.of(implicitVocabId), Optional.empty(), Optional.empty(), state);
+ private static ModelReference resolveDefaultVocab(Model model, DeployState state) {
+ var modelId = model.modelId().orElse(null);
+ if (state.isHosted() && modelId != null) {
+ return Model.fromParams(state, model.name(), modelId + "-vocab", null, null).modelReference();
}
throw new IllegalArgumentException("'tokenizer-model' must be specified");
}
@Override
public void getConfig(HuggingFaceEmbedderConfig.Builder b) {
- b.transformerModel(model).tokenizerPath(vocab);
+ b.transformerModel(modelRef).tokenizerPath(vocabRef);
if (maxTokens != null) b.transformerMaxTokens(maxTokens);
if (transformerInputIds != null) b.transformerInputIds(transformerInputIds);
if (transformerAttentionMask != null) b.transformerAttentionMask(transformerAttentionMask);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceTokenizer.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceTokenizer.java
index 0bf5491e872..e9ac93caa68 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceTokenizer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/HuggingFaceTokenizer.java
@@ -7,7 +7,6 @@ import com.yahoo.language.huggingface.config.HuggingFaceTokenizerConfig;
import com.yahoo.language.huggingface.config.HuggingFaceTokenizerConfig.Padding;
import com.yahoo.language.huggingface.config.HuggingFaceTokenizerConfig.Truncation;
import com.yahoo.text.XML;
-import com.yahoo.vespa.model.container.xml.ModelIdResolver;
import org.w3c.dom.Element;
import java.util.Map;
@@ -26,7 +25,7 @@ public class HuggingFaceTokenizer extends TypedComponent implements HuggingFaceT
super("com.yahoo.language.huggingface.HuggingFaceTokenizer", LINGUISTICS_BUNDLE_NAME, xml);
for (Element element : XML.getChildren(xml, "model")) {
var lang = element.hasAttribute("language") ? element.getAttribute("language") : "unknown";
- langToModel.put(lang, ModelIdResolver.resolveToModelReference(element, state));
+ langToModel.put(lang, Model.fromXml(state, element).modelReference());
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Model.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Model.java
new file mode 100644
index 00000000000..76d93c38aee
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Model.java
@@ -0,0 +1,69 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.model.container.component;
+
+import com.yahoo.config.ModelReference;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.model.builder.xml.XmlHelper;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
+import com.yahoo.vespa.model.container.xml.ModelIdResolver;
+import org.w3c.dom.Element;
+
+import java.net.URI;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Represents a model, e.g ONNX model for an embedder.
+ *
+ * @author bjorncs
+ */
+class Model {
+ private final String paramName;
+ private final String modelId;
+ private final URI url;
+ private final ApplicationFile file;
+ private final ModelReference ref;
+
+ private Model(DeployState ds, String paramName, String modelId, URI url, Path file) {
+ this.paramName = Objects.requireNonNull(paramName);
+ if (modelId == null && url == null && file == null)
+ throw new IllegalArgumentException("At least one of 'model-id', 'url' or 'path' must be specified");
+ this.modelId = modelId;
+ this.url = url;
+ this.file = file != null ? ds.getApplicationPackage().getFile(file) : null;
+ this.ref = ModelIdResolver.resolveToModelReference(
+ paramName, Optional.ofNullable(modelId), Optional.ofNullable(url).map(URI::toString),
+ Optional.ofNullable(file).map(Path::toString), ds);
+ }
+
+ static Model fromParams(DeployState ds, String paramName, String modelId, URI url, Path file) {
+ return new Model(ds, paramName, modelId, url, file);
+ }
+
+ static Optional<Model> fromXml(DeployState ds, Element parent, String name) {
+ return XmlHelper.getOptionalChild(parent, name).map(e -> fromXml(ds, e));
+ }
+
+ static Model fromXml(DeployState ds, Element model) {
+ var modelId = XmlHelper.getOptionalAttribute(model, "model-id").orElse(null);
+ var url = XmlHelper.getOptionalAttribute(model, "url").map(URI::create).orElse(null);
+ var path = XmlHelper.getOptionalAttribute(model, "path").map(Path::fromString).orElse(null);
+ return new Model(ds, model.getTagName(), modelId, url, path);
+ }
+
+ void registerOnnxModelCost(ApplicationContainerCluster c) {
+ var resolvedUrl = resolvedUrl().orElse(null);
+ if (file != null) c.onnxModelCost().registerModel(file);
+ else if (resolvedUrl != null) c.onnxModelCost().registerModel(resolvedUrl);
+ }
+
+ String name() { return paramName; }
+ Optional<String> modelId() { return Optional.ofNullable(modelId); }
+ Optional<URI> url() { return Optional.ofNullable(url); }
+ Optional<URI> resolvedUrl() { return ref.url().map(u -> URI.create(u.value())); }
+ Optional<ApplicationFile> file() { return Optional.ofNullable(file); }
+ ModelReference modelReference() { return ref; }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/chain/ChainedComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/chain/ChainedComponent.java
index c0431d01784..2354298779d 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/chain/ChainedComponent.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/chain/ChainedComponent.java
@@ -29,8 +29,6 @@ public class ChainedComponent<T extends ChainedComponentModel> extends Component
private ComponentId namespace() {
var owner = getParent().getParent();
- return (owner instanceof Chain) ?
- ((Chain) owner).getGlobalComponentId() :
- null;
+ return (owner instanceof Chain<?> chain) ? chain.getGlobalComponentId() : null;
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
index f0296d49472..3261d454b4f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.search;
+import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.prelude.semantics.SemanticRulesConfig;
@@ -56,12 +57,14 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains>
private QueryProfiles queryProfiles;
private SemanticRules semanticRules;
private PageTemplates pageTemplates;
+ private ApplicationPackage app;
public ContainerSearch(DeployState deployState, ApplicationContainerCluster cluster, SearchChains chains) {
super(chains);
this.globalPhase = deployState.featureFlags().enableGlobalPhase();
this.useReconfigurableDispatcher = deployState.featureFlags().useReconfigurableDispatcher();
this.schemasWithGlobalPhase = getSchemasWithGlobalPhase(deployState);
+ this.app = deployState.getApplicationPackage();
this.owningCluster = cluster;
owningCluster.addComponent(Component.fromClassAndBundle(CompiledQueryProfileRegistry.class, SEARCH_AND_DOCPROC_BUNDLE));
@@ -96,6 +99,9 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains>
if ( ! schemasWithGlobalPhase.contains(documentDb.getSchemaName())) continue;
var factory = new RankProfilesEvaluatorComponent(documentDb);
if ( ! owningCluster.getComponentsMap().containsKey(factory.getComponentId())) {
+ var onnxModels = documentDb.getDerivedConfiguration().getRankProfileList().getOnnxModels();
+ onnxModels.asMap().forEach(
+ (__, model) -> owningCluster.onnxModelCost().registerModel(app.getFile(model.getFilePath())));
owningCluster.addComponent(factory);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index 35b0213bf59..1874b5fa19a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -39,6 +39,8 @@ import com.yahoo.container.jdisc.DataplaneProxyService;
import com.yahoo.container.logging.AccessLog;
import com.yahoo.container.logging.FileConnectionLog;
import com.yahoo.io.IOUtils;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Builder;
import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials;
import com.yahoo.jdisc.http.server.jetty.VoidRequestLog;
import com.yahoo.osgi.provider.model.ComponentModel;
@@ -68,7 +70,6 @@ import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.ContainerModel;
import com.yahoo.vespa.model.container.ContainerModelEvaluation;
-import com.yahoo.vespa.model.container.ContainerThreadpool;
import com.yahoo.vespa.model.container.DataplaneProxy;
import com.yahoo.vespa.model.container.IdentityProvider;
import com.yahoo.vespa.model.container.PlatformBundles;
@@ -240,10 +241,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
private void addParameterStoreValidationHandler(ApplicationContainerCluster cluster, DeployState deployState) {
+ if ( ! deployState.isHosted()) return;
// Always add platform bundle. Cannot be controlled by a feature flag as platform bundle cannot change.
- if(deployState.isHosted()) {
- cluster.addPlatformBundle(PlatformBundles.absoluteBundlePath("jdisc-cloud-aws"));
- }
+ cluster.addPlatformBundle(PlatformBundles.absoluteBundlePath("jdisc-cloud-aws"));
if (deployState.zone().system().isPublic()) {
BindingPattern bindingPattern = SystemBindingPattern.fromHttpPath("/validate-secret-store");
Handler handler = new Handler(
@@ -459,7 +459,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
private static void addCloudDataPlaneFilter(DeployState deployState, ApplicationContainerCluster cluster) {
if (!deployState.isHosted() || !deployState.zone().system().isPublic()) return;
- var dataplanePort = getMtlsDataplanePort(deployState, cluster);
+ var dataplanePort = getMtlsDataplanePort(deployState);
// Setup secure filter chain
var secureChain = new HttpFilterChain("cloud-data-plane-secure", HttpFilterChain.Type.SYSTEM);
secureChain.addInnerComponent(new CloudDataPlaneFilter(cluster, deployState));
@@ -594,7 +594,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
String serverName = server.getComponentId().getName();
// If the deployment contains certificate/private key reference, setup TLS port
- var builder = HostedSslConnectorFactory.builder(serverName, getMtlsDataplanePort(state, cluster))
+ var builder = HostedSslConnectorFactory.builder(serverName, getMtlsDataplanePort(state))
.proxyProtocol(true, state.getProperties().featureFlags().enableProxyProtocolMixedMode())
.tlsCiphersOverride(state.getProperties().tlsCiphersOverride())
.endpointConnectionTtl(state.getProperties().endpointConnectionTtl());
@@ -627,19 +627,19 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
private void addCloudTokenSupport(DeployState state, ApplicationContainerCluster cluster) {
var server = cluster.getHttp().getHttpServer().get();
- if (!enableTokenSupport(state, cluster)) return;
+ if (!enableTokenSupport(state)) return;
Set<String> tokenEndpoints = tokenEndpoints(state).stream()
.map(ContainerEndpoint::names)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
var endpointCert = state.endpointCertificateSecrets().orElseThrow();
- int tokenPort = getTokenDataplanePort(state, cluster).orElseThrow();
+ int tokenPort = getTokenDataplanePort(state).orElseThrow();
// Set up component to generate proxy cert if token support is enabled
cluster.addSimpleComponent(DataplaneProxyCredentials.class);
cluster.addSimpleComponent(DataplaneProxyService.class);
var dataplaneProxy = new DataplaneProxy(
- getMtlsDataplanePort(state, cluster),
+ getMtlsDataplanePort(state),
tokenPort,
endpointCert.certificate(),
endpointCert.key(),
@@ -659,13 +659,24 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
// Setup token filter chain
var tokenChain = new HttpFilterChain("cloud-token-data-plane-secure", HttpFilterChain.Type.SYSTEM);
- tokenChain.addInnerComponent(new CloudTokenDataPlaneFilter(cluster, state));
+ var tokenFilter = new CloudTokenDataPlaneFilter(cluster, state);
+ tokenChain.addInnerComponent(tokenFilter);
cluster.getHttp().getFilterChains().add(tokenChain);
// Set as default filter for token port
cluster.getHttp().getHttpServer().orElseThrow().getConnectorFactories().stream()
.filter(c -> c.getListenPort() == tokenPort).findAny().orElseThrow()
.setDefaultRequestFilterChain(tokenChain.getComponentId());
+
+ // Set up handler that tells what fingerprints are known to the container
+ class CloudTokenDataPlaneHandler extends Handler implements CloudTokenDataPlaneFilterConfig.Producer {
+ CloudTokenDataPlaneHandler() {
+ super(new ComponentModel("com.yahoo.jdisc.http.filter.security.cloud.CloudTokenDataPlaneHandler", null, "jdisc-security-filters", null));
+ addServerBindings(SystemBindingPattern.fromHttpPortAndPath(Defaults.getDefaults().vespaWebServicePort(), "/data-plane-tokens/v1"));
+ }
+ @Override public void getConfig(Builder builder) { tokenFilter.getConfig(builder); }
+ }
+ cluster.addComponent(new CloudTokenDataPlaneHandler());
}
// Returns the client certificates of the clients defined for an application cluster
@@ -710,7 +721,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
private Http buildHttp(DeployState deployState, ApplicationContainerCluster cluster, Element httpElement, ConfigModelContext context) {
- Http http = new HttpBuilder(portBindingOverride(deployState, context, cluster)).build(deployState, cluster, httpElement);
+ Http http = new HttpBuilder(portBindingOverride(deployState, context)).build(deployState, cluster, httpElement);
if (networking == Networking.disable)
http.removeAllServers();
@@ -778,6 +789,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
!container.getHostResource().realResources().gpuResources().isZero());
onnxModel.setGpuDevice(gpuDevice, hasGpu);
}
+ cluster.onnxModelCost().registerModel(context.getApplicationPackage().getFile(onnxModel.getFilePath()));
}
cluster.setModelEvaluation(new ContainerModelEvaluation(cluster, profiles, models));
@@ -815,7 +827,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
cluster.addSearchAndDocprocBundles();
addIncludes(processingElement);
cluster.setProcessingChains(new DomProcessingBuilder(null).build(deployState, cluster, processingElement),
- serverBindings(deployState, context, processingElement, ProcessingChains.defaultBindings, cluster).toArray(BindingPattern[]::new));
+ serverBindings(deployState, context, processingElement, ProcessingChains.defaultBindings).toArray(BindingPattern[]::new));
validateAndAddConfiguredComponents(deployState, cluster, processingElement, "renderer", ContainerModelBuilder::validateRendererElement);
}
@@ -840,7 +852,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
private void addUserHandlers(DeployState deployState, ApplicationContainerCluster cluster, Element spec, ConfigModelContext context) {
for (Element component: XML.getChildren(spec, "handler")) {
cluster.addComponent(
- new DomHandlerBuilder(cluster, portBindingOverride(deployState, context, cluster)).build(deployState, cluster, component));
+ new DomHandlerBuilder(cluster, portBindingOverride(deployState, context)).build(deployState, cluster, component));
}
}
@@ -1128,28 +1140,28 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
private void addSearchHandler(DeployState deployState, ApplicationContainerCluster cluster, Element searchElement, ConfigModelContext context) {
var bindingPatterns = List.<BindingPattern>of(SearchHandler.DEFAULT_BINDING);
if (isHostedTenantApplication(context)) {
- bindingPatterns = SearchHandler.bindingPattern(getDataplanePorts(deployState, cluster));
+ bindingPatterns = SearchHandler.bindingPattern(getDataplanePorts(deployState));
}
- SearchHandler searchHandler = new SearchHandler(cluster,
- serverBindings(deployState, context, searchElement, bindingPatterns, cluster),
- ContainerThreadpool.UserOptions.fromXml(searchElement).orElse(null));
+ SearchHandler searchHandler = new SearchHandler(deployState, cluster,
+ serverBindings(deployState, context, searchElement, bindingPatterns),
+ searchElement);
cluster.addComponent(searchHandler);
// Add as child to SearchHandler to get the correct chains config.
searchHandler.addComponent(Component.fromClassAndBundle(SearchHandler.EXECUTION_FACTORY, PlatformBundles.SEARCH_AND_DOCPROC_BUNDLE));
}
- private List<BindingPattern> serverBindings(DeployState deployState, ConfigModelContext context, Element searchElement, Collection<BindingPattern> defaultBindings, ApplicationContainerCluster cluster) {
+ private List<BindingPattern> serverBindings(DeployState deployState, ConfigModelContext context, Element searchElement, Collection<BindingPattern> defaultBindings) {
List<Element> bindings = XML.getChildren(searchElement, "binding");
if (bindings.isEmpty())
return List.copyOf(defaultBindings);
- return toBindingList(deployState, context, bindings, cluster);
+ return toBindingList(deployState, context, bindings);
}
- private List<BindingPattern> toBindingList(DeployState deployState, ConfigModelContext context, List<Element> bindingElements, ApplicationContainerCluster cluster) {
+ private List<BindingPattern> toBindingList(DeployState deployState, ConfigModelContext context, List<Element> bindingElements) {
List<BindingPattern> result = new ArrayList<>();
- var portOverride = isHostedTenantApplication(context) ? getDataplanePorts(deployState, cluster) : Set.<Integer>of();
+ var portOverride = isHostedTenantApplication(context) ? getDataplanePorts(deployState) : Set.<Integer>of();
for (Element element: bindingElements) {
String text = element.getTextContent().trim();
if (!text.isEmpty())
@@ -1173,13 +1185,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
ContainerDocumentApi.HandlerOptions documentApiOptions = DocumentApiOptionsBuilder.build(documentApiElement);
Element ignoreUndefinedFields = XML.getChild(documentApiElement, "ignore-undefined-fields");
- return new ContainerDocumentApi(cluster, documentApiOptions,
- "true".equals(XML.getValue(ignoreUndefinedFields)), portBindingOverride(deployState, context, cluster));
+ return new ContainerDocumentApi(deployState, cluster, documentApiOptions,
+ "true".equals(XML.getValue(ignoreUndefinedFields)), portBindingOverride(deployState, context));
}
- private Set<Integer> portBindingOverride(DeployState deployState, ConfigModelContext context, ApplicationContainerCluster cluster) {
+ private Set<Integer> portBindingOverride(DeployState deployState, ConfigModelContext context) {
return isHostedTenantApplication(context)
- ? getDataplanePorts(deployState, cluster)
+ ? getDataplanePorts(deployState)
: Set.<Integer>of();
}
@@ -1438,18 +1450,18 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
- private static Set<Integer> getDataplanePorts(DeployState ds, ApplicationContainerCluster cluster) {
- var tokenPort = getTokenDataplanePort(ds, cluster);
- var mtlsPort = getMtlsDataplanePort(ds, cluster);
+ private static Set<Integer> getDataplanePorts(DeployState ds) {
+ var tokenPort = getTokenDataplanePort(ds);
+ var mtlsPort = getMtlsDataplanePort(ds);
return tokenPort.isPresent() ? Set.of(mtlsPort, tokenPort.getAsInt()) : Set.of(mtlsPort);
}
- private static int getMtlsDataplanePort(DeployState ds, ApplicationContainerCluster cluster) {
- return enableTokenSupport(ds, cluster) ? 8443 : 4443;
+ private static int getMtlsDataplanePort(DeployState ds) {
+ return enableTokenSupport(ds) ? 8443 : 4443;
}
- private static OptionalInt getTokenDataplanePort(DeployState ds, ApplicationContainerCluster cluster) {
- return enableTokenSupport(ds, cluster) ? OptionalInt.of(8444) : OptionalInt.empty();
+ private static OptionalInt getTokenDataplanePort(DeployState ds) {
+ return enableTokenSupport(ds) ? OptionalInt.of(8444) : OptionalInt.empty();
}
private static Set<ContainerEndpoint> tokenEndpoints(DeployState deployState) {
@@ -1458,7 +1470,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.collect(Collectors.toSet());
}
- private static boolean enableTokenSupport(DeployState state, ApplicationContainerCluster cluster) {
+ private static boolean enableTokenSupport(DeployState state) {
Set<ContainerEndpoint> tokenEndpoints = tokenEndpoints(state);
return state.isHosted() && state.zone().system().isPublic() && ! tokenEndpoints.isEmpty();
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java
index bb1d0af1db9..cdbe62720b9 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.model.container.xml;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.clients.ContainerDocumentApi;
-import com.yahoo.vespa.model.container.ContainerThreadpool;
import org.w3c.dom.Element;
import java.util.ArrayList;
@@ -19,13 +18,7 @@ public class DocumentApiOptionsBuilder {
private static final Logger log = Logger.getLogger(DocumentApiOptionsBuilder.class.getName());
public static ContainerDocumentApi.HandlerOptions build(Element spec) {
- return new ContainerDocumentApi.HandlerOptions(getBindings(spec), threadpoolOptions(spec, "http-client-api"));
- }
-
- private static ContainerThreadpool.UserOptions threadpoolOptions(Element spec, String elementName) {
- Element element = XML.getChild(spec, elementName);
- if (element == null) return null;
- return ContainerThreadpool.UserOptions.fromXml(element).orElse(null);
+ return new ContainerDocumentApi.HandlerOptions(getBindings(spec), XML.getChild(spec, "http-client-api"));
}
private static List<String> getBindings(Element spec) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ModelIdResolver.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ModelIdResolver.java
index be3ca0b8aa9..14216dd8855 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ModelIdResolver.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ModelIdResolver.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.model.container.xml;
import com.yahoo.config.ModelReference;
import com.yahoo.config.UrlReference;
-import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.text.XML;
import org.w3c.dom.Element;
@@ -88,13 +87,6 @@ public class ModelIdResolver {
}
}
-
- public static ModelReference resolveToModelReference(Element elem, DeployState state) {
- return resolveToModelReference(
- elem.getTagName(), XmlHelper.getOptionalAttribute(elem, "model-id"),
- XmlHelper.getOptionalAttribute(elem, "url"), XmlHelper.getOptionalAttribute(elem, "path"), state);
- }
-
public static ModelReference resolveToModelReference(
String paramName, Optional<String> id, Optional<String> url, Optional<String> path, DeployState state) {
if (id.isEmpty()) return createModelReference(Optional.empty(), url, path, state);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java
index 6cfef153fee..3cd296c1469 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.xml;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.handler.threadpool.ContainerThreadpoolConfig;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
@@ -9,6 +10,7 @@ import com.yahoo.vespa.model.container.component.BindingPattern;
import com.yahoo.vespa.model.container.component.SystemBindingPattern;
import com.yahoo.vespa.model.container.component.chain.ProcessingHandler;
import com.yahoo.vespa.model.container.search.searchchain.SearchChains;
+import org.w3c.dom.Element;
import java.util.Collection;
import java.util.List;
@@ -30,10 +32,11 @@ class SearchHandler extends ProcessingHandler<SearchChains> {
static final BundleInstantiationSpecification HANDLER_SPEC = fromSearchAndDocproc(HANDLER_CLASSNAME);
static final BindingPattern DEFAULT_BINDING = SystemBindingPattern.fromHttpPath("/search/*");
- SearchHandler(ApplicationContainerCluster cluster,
+ SearchHandler(DeployState ds,
+ ApplicationContainerCluster cluster,
List<BindingPattern> bindings,
- ContainerThreadpool.UserOptions threadpoolOptions) {
- super(cluster.getSearchChains(), HANDLER_SPEC, new Threadpool(threadpoolOptions));
+ Element threadpoolOptions) {
+ super(cluster.getSearchChains(), HANDLER_SPEC, new Threadpool(ds, threadpoolOptions));
bindings.forEach(this::addServerBindings);
}
@@ -46,8 +49,8 @@ class SearchHandler extends ProcessingHandler<SearchChains> {
private static class Threadpool extends ContainerThreadpool {
- Threadpool(UserOptions options) {
- super("search-handler", options);
+ Threadpool(DeployState ds, Element options) {
+ super(ds, "search-handler", options);
}
@Override
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 bb72eda7d04..d18309ef0af 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
@@ -256,7 +256,7 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
for (ContainerModel containerModel : containers) {
Optional<String> hostClusterId = containerModel.getCluster().getHostClusterId();
if (hostClusterId.isPresent() && hostClusterId.get().equals(clusterId) && containerModel.getCluster().getMemoryPercentage().isPresent()) {
- return containerModel.getCluster().getMemoryPercentage().get() * 0.01;
+ return containerModel.getCluster().getMemoryPercentage().get().percentage() * 0.01;
}
}
return 0.0;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFiles.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFiles.java
index 8bed5e64bf5..8352a011b88 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFiles.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFiles.java
@@ -5,12 +5,14 @@ import com.yahoo.config.FileReference;
import com.yahoo.config.ModelReference;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.application.api.FileRegistry;
+import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.UserConfigRepo;
import com.yahoo.path.Path;
import com.yahoo.vespa.config.ConfigDefinition;
import com.yahoo.vespa.config.ConfigDefinitionKey;
import com.yahoo.vespa.config.ConfigPayloadBuilder;
+
import com.yahoo.yolean.Exceptions;
import java.io.File;
@@ -21,19 +23,28 @@ import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
+import static com.yahoo.vespa.model.container.ApplicationContainerCluster.UserConfiguredUrls;
+
/**
* Utility methods for registering file distribution of files/paths/urls/models defined by the user.
*
* @author gjoranv
+ * @author hmusum
*/
public class UserConfiguredFiles implements Serializable {
private final FileRegistry fileRegistry;
private final DeployLogger logger;
+ private final UserConfiguredUrls userConfiguredUrls;
+ private final String unknownConfigDefinition;
- public UserConfiguredFiles(FileRegistry fileRegistry, DeployLogger logger) {
+ public UserConfiguredFiles(FileRegistry fileRegistry, DeployLogger logger,
+ ModelContext.FeatureFlags featureFlags,
+ UserConfiguredUrls userConfiguredUrls) {
this.fileRegistry = fileRegistry;
this.logger = logger;
+ this.userConfiguredUrls = userConfiguredUrls;
+ this.unknownConfigDefinition = featureFlags.unknownConfigDefinition();
}
/**
@@ -47,7 +58,7 @@ public class UserConfiguredFiles implements Serializable {
try {
register(builder, registeredFiles, key);
} catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Unable to register file specified in services.xml for config '" + key + "': " +
+ throw new IllegalArgumentException("Invalid config in services.xml for '" + key + "': " +
Exceptions.toMessageString(e));
}
}
@@ -56,9 +67,12 @@ public class UserConfiguredFiles implements Serializable {
private void register(ConfigPayloadBuilder builder, Map<Path, FileReference> registeredFiles, ConfigDefinitionKey key) {
ConfigDefinition configDefinition = builder.getConfigDefinition();
if (configDefinition == null) {
- // TODO: throw new IllegalArgumentException("Unable to find config definition for " + builder);
- logger.logApplicationPackage(Level.INFO, "Unable to find config definition " + key +
- ". Will not register files for file distribution for this config");
+ String message = "Unable to find config definition " + key + ". Will not register files for file distribution for this config";
+ switch (unknownConfigDefinition) {
+ case "log" -> logger.logApplicationPackage(Level.INFO, message);
+ case "warning" -> logger.logApplicationPackage(Level.WARNING, message);
+ case "fail" -> throw new IllegalArgumentException("Unable to find config definition for " + key);
+ }
return;
}
@@ -113,8 +127,7 @@ public class UserConfiguredFiles implements Serializable {
ConfigPayloadBuilder fileEntry = builder.getObject(name);
if (isEmptyOptionalPath(entry, fileEntry)) continue;
if (fileEntry.getValue() == null || fileEntry.getValue().equals("."))
- throw new IllegalArgumentException("Unable to register file for field '" + name +
- "': Invalid config value '" + fileEntry.getValue() + "'");
+ throw new IllegalArgumentException("Invalid config value '" + fileEntry.getValue() + "' for field '" + name);
registerFileEntry(fileEntry, registeredFiles, isModelType);
}
}
@@ -133,7 +146,10 @@ public class UserConfiguredFiles implements Serializable {
Path path;
if (isModelType) {
var modelReference = ModelReference.valueOf(builder.getValue());
- if (modelReference.path().isEmpty()) return;
+ if (modelReference.path().isEmpty()) {
+ modelReference.url().ifPresent(url -> userConfiguredUrls.add(url.value()));
+ return;
+ }
path = Path.fromString(modelReference.path().get().value());
}
else {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelProbe.java b/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelProbe.java
index 7c86267c1b6..39a8e16fad5 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelProbe.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelProbe.java
@@ -29,6 +29,7 @@ import java.util.Map;
public class OnnxModelProbe {
private static final String binary = "vespa-analyze-onnx-model";
+ private static final ObjectMapper jsonParser = new ObjectMapper();
static TensorType probeModel(ApplicationPackage app, Path modelPath, String outputName, Map<String, TensorType> inputTypes) {
TensorType outputType = TensorType.empty;
@@ -41,8 +42,9 @@ public class OnnxModelProbe {
// Otherwise, run vespa-analyze-onnx-model if the model is available
if (outputType.equals(TensorType.empty) && app.getFile(modelPath).exists()) {
String jsonInput = createJsonInput(app.getFileReference(modelPath).getAbsolutePath(), inputTypes);
- String jsonOutput = callVespaAnalyzeOnnxModel(jsonInput);
+ var jsonOutput = callVespaAnalyzeOnnxModel(jsonInput);
outputType = outputTypeFromJson(jsonOutput, outputName);
+ writeMemoryStats(app, modelPath, MemoryStats.fromJson(jsonOutput));
if ( ! outputType.equals(TensorType.empty)) {
writeProbedOutputType(app, modelPath, contextKey, outputType);
}
@@ -53,6 +55,16 @@ public class OnnxModelProbe {
return outputType;
}
+ private static void writeMemoryStats(ApplicationPackage app, Path modelPath, MemoryStats memoryStats) throws IOException {
+ String path = app.getFileReference(memoryStatsPath(modelPath)).getAbsolutePath();
+ IOUtils.writeFile(path, memoryStats.toJson().toPrettyString(), false);
+ }
+
+ private static Path memoryStatsPath(Path modelPath) {
+ var fileName = OnnxModelInfo.asValidIdentifier(modelPath.getRelative()) + ".memory_stats";
+ return ApplicationPackage.MODELS_GENERATED_REPLICATED_DIR.append(fileName);
+ }
+
private static String createContextKey(String onnxName, Map<String, TensorType> inputTypes) {
StringBuilder key = new StringBuilder().append(onnxName).append(":");
inputTypes.entrySet().stream().sorted(Map.Entry.comparingByKey())
@@ -95,9 +107,7 @@ public class OnnxModelProbe {
return TensorType.empty;
}
- private static TensorType outputTypeFromJson(String json, String outputName) throws IOException {
- ObjectMapper m = new ObjectMapper();
- JsonNode root = m.readTree(json);
+ private static TensorType outputTypeFromJson(JsonNode root, String outputName) throws IOException {
if ( ! root.isObject() || ! root.has("outputs")) {
return TensorType.empty;
}
@@ -123,7 +133,7 @@ public class OnnxModelProbe {
return out.toString();
}
- private static String callVespaAnalyzeOnnxModel(String jsonInput) throws IOException, InterruptedException {
+ private static JsonNode callVespaAnalyzeOnnxModel(String jsonInput) throws IOException, InterruptedException {
StringBuilder output = new StringBuilder();
ProcessBuilder processBuilder = new ProcessBuilder(binary, "--probe-types");
@@ -148,7 +158,16 @@ public class OnnxModelProbe {
throw new IllegalArgumentException("Error from '" + binary + "'. Return code: " + returnCode + ". " +
"Output: '" + output + "'");
}
- return output.toString();
+ return jsonParser.readTree(output.toString());
+ }
+
+ public record MemoryStats(long vmSize, long vmRss) {
+ static MemoryStats fromJson(JsonNode json) {
+ return new MemoryStats(json.get("vm_size").asLong(), json.get("vm_rss").asLong());
+ }
+ JsonNode toJson() {
+ return jsonParser.createObjectNode().put("vm_size", vmSize).put("vm_rss", vmRss);
+ }
}
}
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 74f6b5b003c..c10b5a66e06 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -126,9 +126,15 @@ SslProvider = element ssl-provider {
}
Threadpool = element threadpool {
- element max-threads { xsd:nonNegativeInteger } &
- element min-threads { xsd:nonNegativeInteger } &
- element queue-size { xsd:nonNegativeInteger }
+ ((
+ # TODO Vespa 9 Remove max-threads / min-threads / queue-size
+ element max-threads { xsd:nonNegativeInteger } &
+ element min-threads { xsd:nonNegativeInteger } &
+ element queue-size { xsd:nonNegativeInteger }
+ )|(
+ element threads { xsd:double { minExclusive = "0.0" } & attribute boost { xsd:double { minExclusive = "0.0" } }? }? &
+ element queue { xsd:double { minInclusive = "0.0" } }?
+ ))
}
Clients = element clients {
diff --git a/config-model/src/test/derived/ngram/chunk.sd b/config-model/src/test/derived/ngram/chunk.sd
new file mode 100644
index 00000000000..7c2a7465327
--- /dev/null
+++ b/config-model/src/test/derived/ngram/chunk.sd
@@ -0,0 +1,20 @@
+schema chunk {
+
+ document chunk {
+ field content type string {
+ indexing: summary | index
+ match {
+ gram
+ gram-size: 3
+ }
+ }
+ }
+
+ document-summary content-summary inherits default {
+ summary content_dynamic type string {
+ source: content
+ dynamic
+ }
+ }
+
+}
diff --git a/config-model/src/test/derived/ngram/index-info.cfg b/config-model/src/test/derived/ngram/index-info.cfg
new file mode 100644
index 00000000000..72b6760ceb5
--- /dev/null
+++ b/config-model/src/test/derived/ngram/index-info.cfg
@@ -0,0 +1,21 @@
+indexinfo[].name "chunk"
+indexinfo[].command[].indexname "sddocname"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "sddocname"
+indexinfo[].command[].command "word"
+indexinfo[].command[].indexname "content"
+indexinfo[].command[].command "lowercase"
+indexinfo[].command[].indexname "content"
+indexinfo[].command[].command "string"
+indexinfo[].command[].indexname "content"
+indexinfo[].command[].command "type string"
+indexinfo[].command[].indexname "content"
+indexinfo[].command[].command "ngram 3"
+indexinfo[].command[].indexname "content_dynamic"
+indexinfo[].command[].command "string"
+indexinfo[].command[].indexname "content_dynamic"
+indexinfo[].command[].command "type string"
+indexinfo[].command[].indexname "content_dynamic"
+indexinfo[].command[].command "ngram 3"
+indexinfo[].command[].indexname "content_dynamic"
+indexinfo[].command[].command "dynteaser"
diff --git a/config-model/src/test/examples/indexing_attribute_changed.sd b/config-model/src/test/examples/indexing_attribute_changed.sd
deleted file mode 100644
index bab878d09ab..00000000000
--- a/config-model/src/test/examples/indexing_attribute_changed.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_attribute_changed {
- document indexing_attribute_changed {
- field foo type string {
- indexing: summary | lowercase | attribute
- }
- }
-}
diff --git a/config-model/src/test/examples/indexing_attribute_other.sd b/config-model/src/test/examples/indexing_attribute_other.sd
deleted file mode 100644
index e3f58f20910..00000000000
--- a/config-model/src/test/examples/indexing_attribute_other.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_attribute_other {
- document indexing_attribute_other {
- field foo type string {
- indexing: attribute bar
- }
- }
-}
diff --git a/config-model/src/test/examples/indexing_extra_field_input_extra_field.sd b/config-model/src/test/examples/indexing_extra_field_input_extra_field.sd
deleted file mode 100644
index 315d6c2a677..00000000000
--- a/config-model/src/test/examples/indexing_extra_field_input_extra_field.sd
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_extra_field_input_extra_field {
- document indexing_extra_field_input_extra_field {
-
- }
- field foo type string {
-
- }
- field bar type string {
- indexing: input bar | index
- }
-}
diff --git a/config-model/src/test/examples/indexing_extra_field_input_implicit.sd b/config-model/src/test/examples/indexing_extra_field_input_implicit.sd
deleted file mode 100644
index 8aff3284ce3..00000000000
--- a/config-model/src/test/examples/indexing_extra_field_input_implicit.sd
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_extra_field_input_implicit {
- document indexing_extra_field_input_implicit {
-
- }
- field foo type string {
- indexing: index
- }
-}
diff --git a/config-model/src/test/examples/indexing_extra_field_input_null.sd b/config-model/src/test/examples/indexing_extra_field_input_null.sd
deleted file mode 100644
index c4600fa680a..00000000000
--- a/config-model/src/test/examples/indexing_extra_field_input_null.sd
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_extra_field_input_null {
- document indexing_extra_field_input_null {
-
- }
- field foo type string {
- indexing: input foo | index
- }
-}
diff --git a/config-model/src/test/examples/indexing_extra_field_input_self.sd b/config-model/src/test/examples/indexing_extra_field_input_self.sd
deleted file mode 100644
index 36dbae21449..00000000000
--- a/config-model/src/test/examples/indexing_extra_field_input_self.sd
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_extra_field_input_self {
- document indexing_extra_field_input_self {
-
- }
- field foo type string {
- indexing: input foo | index
- }
-}
diff --git a/config-model/src/test/examples/indexing_index_changed.sd b/config-model/src/test/examples/indexing_index_changed.sd
deleted file mode 100644
index 194a9bd3177..00000000000
--- a/config-model/src/test/examples/indexing_index_changed.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_index_changed {
- document indexing_index_changed {
- field foo type string {
- indexing: attribute | lowercase | index
- }
- }
-}
diff --git a/config-model/src/test/examples/indexing_index_other.sd b/config-model/src/test/examples/indexing_index_other.sd
deleted file mode 100644
index 40b8b150a5b..00000000000
--- a/config-model/src/test/examples/indexing_index_other.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_index_other {
- document indexing_index_other {
- field foo type string {
- indexing: index bar
- }
- }
-}
diff --git a/config-model/src/test/examples/indexing_modify_field_no_output.sd b/config-model/src/test/examples/indexing_modify_field_no_output.sd
deleted file mode 100644
index ac2bed520e8..00000000000
--- a/config-model/src/test/examples/indexing_modify_field_no_output.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_modify_field_no_output {
- document indexing_modify_field_no_output {
- field foo type string {
- indexing: lowercase | echo
- }
- }
-}
diff --git a/config-model/src/test/examples/indexing_output_conflict.sd b/config-model/src/test/examples/indexing_output_conflict.sd
deleted file mode 100644
index 9cf1cbc0823..00000000000
--- a/config-model/src/test/examples/indexing_output_conflict.sd
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_output_confict {
- document indexing_output_confict {
- field foo type string {
-
- }
- }
- field bar type string {
- indexing: input foo | attribute | lowercase | index
- }
-}
diff --git a/config-model/src/test/examples/indexing_output_other_field.sd b/config-model/src/test/examples/indexing_output_other_field.sd
deleted file mode 100644
index 40b08bb15b2..00000000000
--- a/config-model/src/test/examples/indexing_output_other_field.sd
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_output_other_field {
- document indexing_output_other_field {
- field foo type string {
- indexing: index bar
- }
- }
- field bar type string {
-
- }
-}
diff --git a/config-model/src/test/examples/indexing_summary_other.sd b/config-model/src/test/examples/indexing_summary_other.sd
deleted file mode 100644
index 871ab854c51..00000000000
--- a/config-model/src/test/examples/indexing_summary_other.sd
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search indexing_summary_other {
- document indexing_summary_other {
- field foo type string {
- indexing: summary bar
- }
- }
-}
diff --git a/config-model/src/test/examples/matchphase/non_existing_attribute.sd b/config-model/src/test/examples/matchphase/non_existing_attribute.sd
deleted file mode 100644
index cd3842fde8a..00000000000
--- a/config-model/src/test/examples/matchphase/non_existing_attribute.sd
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search test {
- document test {
- field foo type int {
- indexing: summary
- }
- }
- rank-profile default {
- match-phase {
- attribute: foo
- max-hits: 100
- }
- }
-}
diff --git a/config-model/src/test/examples/matchphase/non_fast_search_attribute.sd b/config-model/src/test/examples/matchphase/non_fast_search_attribute.sd
deleted file mode 100644
index 5fde096cf61..00000000000
--- a/config-model/src/test/examples/matchphase/non_fast_search_attribute.sd
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search test {
- document test {
- field foo type int {
- indexing: attribute
- }
- }
- rank-profile default {
- match-phase {
- attribute: foo
- max-hits: 100
- }
- }
-}
diff --git a/config-model/src/test/examples/matchphase/wrong_collection_type_attribute.sd b/config-model/src/test/examples/matchphase/wrong_collection_type_attribute.sd
deleted file mode 100644
index 8a9166c94f7..00000000000
--- a/config-model/src/test/examples/matchphase/wrong_collection_type_attribute.sd
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search test {
- document test {
- field foo type array<int> {
- indexing: attribute
- }
- }
- rank-profile default {
- match-phase {
- attribute: foo
- max-hits: 100
- }
- }
-}
diff --git a/config-model/src/test/examples/matchphase/wrong_data_type_attribute.sd b/config-model/src/test/examples/matchphase/wrong_data_type_attribute.sd
deleted file mode 100644
index d4f526569ea..00000000000
--- a/config-model/src/test/examples/matchphase/wrong_data_type_attribute.sd
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-search test {
- document test {
- field foo type string {
- indexing: attribute
- }
- }
- rank-profile default {
- match-phase {
- attribute: foo
- max-hits: 100
- }
- }
-}
diff --git a/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java b/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java
index af05a144b79..3f5173a3ae9 100644
--- a/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java
+++ b/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java
@@ -10,6 +10,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.MockFileRegistry;
@@ -84,4 +85,6 @@ public class MockModelContext implements ModelContext {
public ExecutorService getExecutor() {
return new InThreadExecutorService();
}
+
+ @Override public OnnxModelCost onnxModelCost() { return OnnxModelCost.disabled(); }
}
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 2f8a8bddf20..38f51323ee2 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
@@ -148,7 +148,7 @@ public class ModelProvisioningTest {
assertEquals("-Xlog:gc", mydisc2.getContainers().get(1).getJvmOptions());
assertEquals("lib/blablamalloc.so", mydisc2.getContainers().get(0).getPreLoad());
assertEquals("lib/blablamalloc.so", mydisc2.getContainers().get(1).getPreLoad());
- assertEquals(Optional.of(45), mydisc2.getMemoryPercentage());
+ assertEquals(45, mydisc2.getMemoryPercentage().get().percentage());
assertEquals(Optional.of("-XX:+UseParNewGC"), mydisc2.getJvmGCOptions());
QrStartConfig.Builder qrStartBuilder = new QrStartConfig.Builder();
mydisc2.getConfig(qrStartBuilder);
@@ -288,10 +288,11 @@ public class ModelProvisioningTest {
assertEquals(2025077080L, protonMemorySize(model.getContentClusters().get("content1")), "Memory for proton is lowered to account for the jvm heap");
assertProvisioned(0, ClusterSpec.Id.from("container1"), ClusterSpec.Type.container, model);
assertProvisioned(2, ClusterSpec.Id.from("content1"), ClusterSpec.Id.from("container1"), ClusterSpec.Type.combined, model);
- assertEquals(1, logger.msgs().size());
+ var msgs = logger.msgs().stream().filter(m -> m.level().equals(Level.WARNING)).toList();
+ assertEquals(1, msgs.size());
assertEquals("Declaring combined cluster with <nodes of=\"...\"> is deprecated without replacement, " +
"and the feature will be removed in Vespa 9. Use separate container and content clusters instead",
- logger.msgs().get(0).message);
+ msgs.get(0).message);
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/schema/AttributeSettingsTestCase.java b/config-model/src/test/java/com/yahoo/schema/AttributeSettingsTestCase.java
index 3ca182e18c2..db862dd388e 100644
--- a/config-model/src/test/java/com/yahoo/schema/AttributeSettingsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/AttributeSettingsTestCase.java
@@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
/**
* Attribute settings
*
- * @author bratseth
+ * @author bratseth
*/
public class AttributeSettingsTestCase extends AbstractSchemaTestCase {
diff --git a/config-model/src/test/java/com/yahoo/schema/derived/NGramTestCase.java b/config-model/src/test/java/com/yahoo/schema/derived/NGramTestCase.java
new file mode 100644
index 00000000000..4481445858a
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/schema/derived/NGramTestCase.java
@@ -0,0 +1,15 @@
+package com.yahoo.schema.derived;
+
+import com.yahoo.schema.parser.ParseException;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+public class NGramTestCase extends AbstractExportingTestCase {
+
+ @Test
+ void testNGram() throws IOException, ParseException {
+ assertCorrectDeriving("ngram");
+ }
+
+} \ No newline at end of file
diff --git a/config-model/src/test/java/com/yahoo/schema/parser/SchemaParserTestCase.java b/config-model/src/test/java/com/yahoo/schema/parser/SchemaParserTestCase.java
index e69f26a31c9..89eff4ec464 100644
--- a/config-model/src/test/java/com/yahoo/schema/parser/SchemaParserTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/parser/SchemaParserTestCase.java
@@ -269,7 +269,6 @@ public class SchemaParserTestCase {
checkFileParses("src/test/examples/implicitsummaryfields.sd");
checkFileParses("src/test/examples/incorrectrankingexpressionfileref.sd");
checkFileParses("src/test/examples/indexing_extra.sd");
- checkFileParses("src/test/examples/indexing_modify_field_no_output.sd");
checkFileParses("src/test/examples/indexing.sd");
checkFileParses("src/test/examples/indexrewrite.sd");
checkFileParses("src/test/examples/indexsettings.sd");
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/AssertIndexingScript.java b/config-model/src/test/java/com/yahoo/schema/processing/AssertIndexingScript.java
index f9c1e992347..36f20c18588 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/AssertIndexingScript.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/AssertIndexingScript.java
@@ -38,6 +38,6 @@ public abstract class AssertIndexingScript {
String str = actualExp.toString();
assertTrue(parsedExpected.remove(str), "Unexpected: " + str);
}
- assertTrue(parsedExpected.isEmpty(), "Missing: " + parsedExpected.toString());
+ assertTrue(parsedExpected.isEmpty(), "Missing: " + parsedExpected);
}
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/AssertSearchBuilder.java b/config-model/src/test/java/com/yahoo/schema/processing/AssertSearchBuilder.java
deleted file mode 100644
index 12da3f0797b..00000000000
--- a/config-model/src/test/java/com/yahoo/schema/processing/AssertSearchBuilder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.schema.processing;
-
-import com.yahoo.schema.ApplicationBuilder;
-import com.yahoo.schema.parser.ParseException;
-
-import java.io.IOException;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * @author Simon Thoresen Hult
- */
-public abstract class AssertSearchBuilder {
-
- public static void assertBuilds(String searchDefinitionFileName) throws IOException, ParseException {
- assertNotNull(ApplicationBuilder.buildFromFile(searchDefinitionFileName));
- }
-
- public static void assertBuildFails(String searchDefinitionFileName, String expectedException)
- throws IOException, ParseException {
- try {
- ApplicationBuilder.buildFromFile(searchDefinitionFileName);
- fail(searchDefinitionFileName);
- } catch (IllegalArgumentException e) {
- assertEquals(expectedException, e.getMessage());
- }
- }
-}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/IndexingInputsTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/IndexingInputsTestCase.java
index d420623f233..675168ca6c2 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/IndexingInputsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/IndexingInputsTestCase.java
@@ -1,45 +1,165 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.processing;
+import com.yahoo.schema.ApplicationBuilder;
import com.yahoo.schema.parser.ParseException;
+import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
-import java.io.IOException;
-
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Simon Thoresen Hult
+ * @author bratseth
*/
public class IndexingInputsTestCase {
@Test
- void requireThatExtraFieldInputExtraFieldThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_extra_field_input_extra_field.sd",
- "For schema 'indexing_extra_field_input_extra_field', field 'bar': Indexing script refers " +
- "to field 'bar' which is neither a field in document type " +
- "'indexing_extra_field_input_extra_field' nor a mutable attribute");
+ void requireThatExtraFieldInputExtraFieldThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_extra_field_input_extra_field {
+ document indexing_extra_field_input_extra_field {
+ }
+ field foo type string {
+ }
+ field bar type string {
+ indexing: input bar | index
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_extra_field_input_extra_field', field 'bar': Indexing script refers " +
+ "to field 'bar' which is neither a field in document type " +
+ "'indexing_extra_field_input_extra_field' nor a mutable attribute",
+ Exceptions.toMessageString(e));
+ }
+ }
+
+ @Test
+ void requireThatExtraFieldInputImplicitThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_extra_field_input_implicit {
+ document indexing_extra_field_input_implicit {
+ }
+ field foo type string {
+ indexing: index
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_extra_field_input_implicit', field 'foo': " +
+ "For expression '{ tokenize normalize stem:\"BEST\" | index foo; }': Expected string input, but no input is specified",
+ Exceptions.toMessageString(e));
+ }
+ }
+
+ @Test
+ void requireThatExtraFieldInputNullThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_extra_field_input_null {
+ document indexing_extra_field_input_null {
+ }
+ field foo type string {
+ indexing: input foo | index
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_extra_field_input_null', field 'foo': Indexing script refers to field " +
+ "'foo' which is neither a field in document type 'indexing_extra_field_input_null' nor a mutable attribute",
+ Exceptions.toMessageString(e));
+ }
+ }
+
+ @Test
+ void requireThatExtraFieldInputSelfThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_extra_field_input_self {
+ document indexing_extra_field_input_self {
+ }
+ field foo type string {
+ indexing: input foo | index
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_extra_field_input_self', field 'foo': Indexing script refers to field " +
+ "'foo' which is neither a field in document type 'indexing_extra_field_input_self' nor a mutable attribute",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void requireThatExtraFieldInputImplicitThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_extra_field_input_implicit.sd",
- "For schema 'indexing_extra_field_input_implicit', field 'foo': Indexing script refers to " +
- "field 'foo' which is neither a field in document type 'indexing_extra_field_input_implicit' nor a mutable attribute");
+ void testPlainInputInDerivedField() throws ParseException {
+ var schema = """
+ schema test {
+ document test {
+ field field1 type int {
+ }
+ }
+ field derived1 type int {
+ indexing: input field1 | attribute
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
}
@Test
- void requireThatExtraFieldInputNullThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_extra_field_input_null.sd",
- "For schema 'indexing_extra_field_input_null', field 'foo': Indexing script refers to field " +
- "'foo' which is neither a field in document type 'indexing_extra_field_input_null' nor a mutable attribute");
+ void testWrappedInputInDerivedField() throws ParseException {
+ var schema = """
+ schema test {
+ document test {
+ field field1 type int {
+ }
+ }
+ field derived1 type int {
+ indexing: if (input field1 == 0) { 0 } else { 1 } | attribute
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
}
@Test
- void requireThatExtraFieldInputSelfThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_extra_field_input_self.sd",
- "For schema 'indexing_extra_field_input_self', field 'foo': Indexing script refers to field " +
- "'foo' which is neither a field in document type 'indexing_extra_field_input_self' nor a mutable attribute");
+ void testNoInputInDerivedField() throws ParseException {
+ try {
+ var schema = """
+ schema test {
+ document test {
+ field field1 type int {
+ }
+ }
+ field derived1 type int {
+ indexing: attribute
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'test', field 'derived1': For expression '{ attribute derived1; }': " +
+ "Expected any input, but no input is specified",
+ Exceptions.toMessageString(e));
+ }
}
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/IndexingOutputsTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/IndexingOutputsTestCase.java
index e707d203381..7557ca5b725 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/IndexingOutputsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/IndexingOutputsTestCase.java
@@ -1,13 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.processing;
+import com.yahoo.schema.ApplicationBuilder;
import com.yahoo.schema.parser.ParseException;
+import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
-import java.io.IOException;
-
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
-
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Simon Thoresen Hult
@@ -15,16 +15,51 @@ import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
public class IndexingOutputsTestCase {
@Test
- void requireThatOutputOtherFieldThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_output_other_field.sd",
- "For schema 'indexing_output_other_field', field 'foo': Indexing expression 'index bar' " +
- "attempts to write to a field other than 'foo'.");
+ void requireThatOutputOtherFieldThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_output_other_field {
+ document indexing_output_other_field {
+ field foo type string {
+ indexing: index bar
+ }
+ }
+ field bar type string {
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_output_other_field', field 'foo': Indexing expression 'index bar' " +
+ "attempts to write to a field other than 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void requireThatOutputConflictThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_output_conflict.sd",
- "For schema 'indexing_output_confict', field 'bar': For expression 'index bar': Attempting " +
- "to assign conflicting values to field 'bar'.");
+ void requireThatOutputConflictThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_output_confict {
+ document indexing_output_confict {
+ field foo type string {
+ }
+ }
+ field bar type string {
+ indexing: input foo | attribute | lowercase | index
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_output_confict', field 'bar': For expression 'index bar': Attempting " +
+ "to assign conflicting values to field 'bar'.",
+ Exceptions.toMessageString(e));
+ }
}
+
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/IndexingValidationTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/IndexingValidationTestCase.java
index aa8a2922e8f..4343abbf548 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/IndexingValidationTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/IndexingValidationTestCase.java
@@ -4,13 +4,15 @@ package com.yahoo.schema.processing;
import com.yahoo.schema.ApplicationBuilder;
import com.yahoo.schema.derived.AbstractExportingTestCase;
import com.yahoo.schema.parser.ParseException;
+import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Arrays;
import static com.yahoo.schema.processing.AssertIndexingScript.assertIndexing;
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Simon Thoresen Hult
@@ -18,45 +20,135 @@ import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
public class IndexingValidationTestCase extends AbstractExportingTestCase {
@Test
- void testAttributeChanged() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_attribute_changed.sd",
- "For schema 'indexing_attribute_changed', field 'foo': For expression 'attribute foo': " +
- "Attempting to assign conflicting values to field 'foo'.");
+ void testAttributeChanged() throws ParseException {
+ try {
+ var schema = """
+ search indexing_attribute_changed {
+ document indexing_attribute_changed {
+ field foo type string {
+ indexing: summary | lowercase | attribute
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_attribute_changed', field 'foo': For expression 'attribute foo': " +
+ "Attempting to assign conflicting values to field 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void testAttributeOther() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_attribute_other.sd",
- "For schema 'indexing_attribute_other', field 'foo': Indexing expression 'attribute bar' " +
- "attempts to write to a field other than 'foo'.");
+ void testAttributeOther() throws ParseException {
+ try {
+ var schema = """
+ search indexing_attribute_other {
+ document indexing_attribute_other {
+ field foo type string {
+ indexing: attribute bar
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_attribute_other', field 'foo': Indexing expression 'attribute bar' " +
+ "attempts to write to a field other than 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void testIndexChanged() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_index_changed.sd",
- "For schema 'indexing_index_changed', field 'foo': For expression 'index foo': " +
- "Attempting to assign conflicting values to field 'foo'.");
+ void testIndexChanged() throws ParseException {
+ try {
+ var schema = """
+ search indexing_index_changed {
+ document indexing_index_changed {
+ field foo type string {
+ indexing: attribute | lowercase | index
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_index_changed', field 'foo': For expression 'index foo': " +
+ "Attempting to assign conflicting values to field 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void testIndexOther() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_index_other.sd",
- "For schema 'indexing_index_other', field 'foo': Indexing expression 'index bar' " +
- "attempts to write to a field other than 'foo'.");
+ void testIndexOther() throws ParseException {
+ try {
+ var schema = """
+ search indexing_index_other {
+ document indexing_index_other {
+ field foo type string {
+ indexing: index bar\s
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_index_other', field 'foo': Indexing expression 'index bar' " +
+ "attempts to write to a field other than 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void testSummaryChanged() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_summary_changed.sd",
- "For schema 'indexing_summary_fail', field 'foo': For expression 'summary foo': Attempting " +
- "to assign conflicting values to field 'foo'.");
+ void testSummaryChanged() throws ParseException {
+ try {
+ var schema = """
+ search indexing_summary_fail {
+ document indexing_summary_fail {
+ field foo type string {
+ indexing: index | lowercase | summary\s
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_summary_fail', field 'foo': For expression 'summary foo': Attempting " +
+ "to assign conflicting values to field 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
- void testSummaryOther() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_summary_other.sd",
- "For schema 'indexing_summary_other', field 'foo': Indexing expression 'summary bar' " +
- "attempts to write to a field other than 'foo'.");
+ void testSummaryOther() throws ParseException {
+ try {
+ var schema = """
+ search indexing_summary_other {
+ document indexing_summary_other {
+ field foo type string {
+ indexing: summary bar
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_summary_other', field 'foo': Indexing expression 'summary bar' " +
+ "attempts to write to a field other than 'foo'.",
+ Exceptions.toMessageString(e));
+ }
}
@Test
@@ -68,9 +160,35 @@ public class IndexingValidationTestCase extends AbstractExportingTestCase {
}
@Test
- void requireThatMultilineOutputConflictThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_multiline_output_conflict.sd",
- "For schema 'indexing_multiline_output_confict', field 'cox': For expression 'index cox': " +
- "Attempting to assign conflicting values to field 'cox'.");
+ void requireThatMultilineOutputConflictThrows() throws ParseException {
+ try {
+ var schema = """
+ search indexing_multiline_output_confict {
+ document indexing_multiline_output_confict {
+ field foo type string {
+ }
+ field bar type string {
+ }
+ field baz type string {
+ }
+ }
+ field cox type string {
+ indexing {
+ input foo | attribute;
+ input bar | index;
+ input baz | summary;
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_multiline_output_confict', field 'cox': For expression 'index cox': " +
+ "Attempting to assign conflicting values to field 'cox'.",
+ Exceptions.toMessageString(e));
+ }
}
+
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/IndexingValuesTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/IndexingValuesTestCase.java
index 1f723924db6..c46b4fc2c7d 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/IndexingValuesTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/IndexingValuesTestCase.java
@@ -1,13 +1,15 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.processing;
+import com.yahoo.schema.ApplicationBuilder;
import com.yahoo.schema.parser.ParseException;
+import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuilds;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Simon Thoresen Hult
@@ -15,16 +17,43 @@ import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuilds;
public class IndexingValuesTestCase {
@Test
- void requireThatModifyFieldNoOutputDoesNotThrow() throws IOException, ParseException {
- assertBuilds("src/test/examples/indexing_modify_field_no_output.sd");
+ void requireThatModifyFieldNoOutputDoesNotThrow() throws ParseException {
+ var schema = """
+ search indexing_modify_field_no_output {
+ document indexing_modify_field_no_output {
+ field foo type string {
+ indexing: lowercase | echo
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
}
@Test
void requireThatInputOtherFieldThrows() throws IOException, ParseException {
- assertBuildFails("src/test/examples/indexing_input_other_field.sd",
- "For schema 'indexing_input_other_field', field 'bar': Indexing expression 'input foo' " +
- "attempts to modify the value of the document field 'bar'. " +
- "Use a field outside the document block instead.");
+ try {
+ var schema = """
+ search indexing_input_other_field {
+ document indexing_input_other_field {
+ field foo type string {
+
+ }
+ field bar type string {
+ indexing: input foo | attribute | index | summary
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("For schema 'indexing_input_other_field', field 'bar': Indexing expression 'input foo' " +
+ "attempts to modify the value of the document field 'bar'. " +
+ "Use a field outside the document block instead.",
+ Exceptions.toMessageString(e));
+ }
}
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/MatchPhaseSettingsValidatorTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/MatchPhaseSettingsValidatorTestCase.java
index cbddea8ea6a..85ec80d2610 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/MatchPhaseSettingsValidatorTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/MatchPhaseSettingsValidatorTestCase.java
@@ -1,9 +1,12 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.processing;
+import com.yahoo.schema.ApplicationBuilder;
+import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
-import static com.yahoo.schema.processing.AssertSearchBuilder.assertBuildFails;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
public class MatchPhaseSettingsValidatorTestCase {
@@ -13,25 +16,109 @@ public class MatchPhaseSettingsValidatorTestCase {
@Test
void requireThatAttributeMustExists() throws Exception {
- assertBuildFails("src/test/examples/matchphase/non_existing_attribute.sd",
- getMessagePrefix() + "does not exists");
+ try {
+ var schema = """
+ search test {
+ document test {
+ field foo type int {
+ indexing: summary
+ }
+ }
+ rank-profile default {
+ match-phase {
+ attribute: foo
+ max-hits: 100
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals(getMessagePrefix() + "does not exists", Exceptions.toMessageString(e));
+ }
}
@Test
void requireThatAttributeMustBeNumeric() throws Exception {
- assertBuildFails("src/test/examples/matchphase/wrong_data_type_attribute.sd",
- getMessagePrefix() + "must be single value numeric, but it is 'string'");
+ try {
+ var schema = """
+ search test {
+ document test {
+ field foo type string {
+ indexing: attribute
+ }
+ }
+ rank-profile default {
+ match-phase {
+ attribute: foo
+ max-hits: 100
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals(getMessagePrefix() + "must be single value numeric, but it is 'string'",
+ Exceptions.toMessageString(e));
+ }
}
@Test
void requireThatAttributeMustBeSingleValue() throws Exception {
- assertBuildFails("src/test/examples/matchphase/wrong_collection_type_attribute.sd",
- getMessagePrefix() + "must be single value numeric, but it is 'Array<int>'");
+ try {
+ var schema = """
+ search test {
+ document test {
+ field foo type array<int> {
+ indexing: attribute
+ }
+ }
+ rank-profile default {
+ match-phase {
+ attribute: foo
+ max-hits: 100
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals(getMessagePrefix() + "must be single value numeric, but it is 'Array<int>'",
+ Exceptions.toMessageString(e));
+ }
}
@Test
void requireThatAttributeMustHaveFastSearch() throws Exception {
- assertBuildFails("src/test/examples/matchphase/non_fast_search_attribute.sd",
- getMessagePrefix() + "must be fast-search, but it is not");
+ try {
+ var schema = """
+ search test {
+ document test {
+ field foo type int {
+ indexing: attribute
+ }
+ }
+ rank-profile default {
+ match-phase {
+ attribute: foo
+ max-hits: 100
+ }
+ }
+ }
+ """;
+ ApplicationBuilder.createFromString(schema);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals(getMessagePrefix() + "must be fast-search, but it is not",
+ Exceptions.toMessageString(e));
+ }
}
+
}
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/NGramTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/NGramTestCase.java
index 06ea202b9c3..551542a84ba 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/NGramTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/NGramTestCase.java
@@ -27,15 +27,15 @@ public class NGramTestCase extends AbstractSchemaTestCase {
SDField gram1 = schema.getConcreteField("gram_1");
assertEquals(MatchType.GRAM, gram1.getMatching().getType());
- assertEquals(1, gram1.getMatching().getGramSize());
+ assertEquals(1, gram1.getMatching().getGramSize().getAsInt());
SDField gram2 = schema.getConcreteField("gram_2");
assertEquals(MatchType.GRAM, gram2.getMatching().getType());
- assertEquals(-1, gram2.getMatching().getGramSize()); // Not set explicitly
+ assertTrue(gram2.getMatching().getGramSize().isEmpty());
SDField gram3 = schema.getConcreteField("gram_3");
assertEquals(MatchType.GRAM, gram3.getMatching().getType());
- assertEquals(3, gram3.getMatching().getGramSize());
+ assertEquals(3, gram3.getMatching().getGramSize().getAsInt());
assertEquals("input gram_1 | ngram 1 | index gram_1 | summary gram_1", gram1.getIndexingScript().iterator().next().toString());
assertEquals("input gram_2 | ngram 2 | attribute gram_2 | index gram_2", gram2.getIndexingScript().iterator().next().toString());
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsConsumersTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsConsumersTest.java
index 49019e47bc2..eae4f12f62c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsConsumersTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsConsumersTest.java
@@ -52,11 +52,12 @@ public class MetricsConsumersTest {
@Test
void consumers_are_set_up_for_hosted() {
ConsumersConfig config = consumersConfigFromXml(servicesWithAdminOnly(), hosted);
- assertEquals(4, config.consumer().size());
+ assertEquals(5, config.consumer().size());
assertEquals(MetricsConsumer.vespa.id(), config.consumer(0).name());
assertEquals(MetricsConsumer.autoscaling.id(), config.consumer(1).name());
assertEquals(MetricsConsumer.defaultConsumer.id(), config.consumer(2).name());
assertEquals(MetricsProxyContainerCluster.NEW_DEFAULT_CONSUMER_ID, config.consumer(3).name());
+ assertEquals(MetricsConsumer.vespa9.id(), config.consumer(4).name());
}
@Test
@@ -124,7 +125,7 @@ public class MetricsConsumersTest {
);
VespaModel hostedModel = getModel(services, hosted);
ConsumersConfig config = consumersConfigFromModel(hostedModel);
- assertEquals(4, config.consumer().size());
+ assertEquals(5, config.consumer().size());
// All default metrics are retained
ConsumersConfig.Consumer vespaConsumer = config.consumer(0);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java
new file mode 100644
index 00000000000..9b3b659c252
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java
@@ -0,0 +1,130 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.ModelReference;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.OnnxModelCost;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.provision.InMemoryProvisioner;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.NodeResources;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author bjorncs
+ */
+class JvmHeapSizeValidatorTest {
+
+ @Test
+ void fails_on_too_low_jvm_percentage() throws IOException, SAXException {
+ var deployState = createDeployState(8, 7L * 1024 * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ var e = assertThrows(IllegalArgumentException.class, () -> new JvmHeapSizeValidator().validate(model, deployState));
+ String expectedMessage = "Allocated percentage of memory of JVM in cluster 'container' is too low (3% < 15%). Estimated cost of ONNX models is 7.00GB";
+ assertTrue(e.getMessage().contains(expectedMessage), e.getMessage());
+ }
+
+ @Test
+ void fails_on_too_low_heap_size() throws IOException, SAXException {
+ var deployState = createDeployState(2.2, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ var e = assertThrows(IllegalArgumentException.class, () -> new JvmHeapSizeValidator().validate(model, deployState));
+ String expectedMessage = "Allocated memory to JVM in cluster 'container' is too low (0.50GB < 0.60GB). Estimated cost of ONNX models is 1.00GB.";
+ assertTrue(e.getMessage().contains(expectedMessage), e.getMessage());
+ }
+
+ @Test
+ void accepts_adequate_heap_size() throws IOException, SAXException {
+ var deployState = createDeployState(8, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ assertDoesNotThrow(() -> new JvmHeapSizeValidator().validate(model, deployState));
+ }
+
+ @Test
+ void accepts_services_with_explicit_jvm_size() throws IOException, SAXException {
+ String servicesXml =
+ """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <services version='1.0'>
+ <container version='1.0'>
+ <nodes count="2">
+ <jvm allocated-memory='5%'/>
+ <resources vcpu="4" memory="2Gb" disk="125Gb"/>
+ </nodes>
+ <component id="hf-embedder" type="hugging-face-embedder">
+ <transformer-model url="https://my/url/model.onnx"/>
+ <tokenizer-model path="app/tokenizer.json"/>
+ </component>
+ </container>
+ </services>""";
+ var deployState = createDeployState(servicesXml, 2, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ assertDoesNotThrow(() -> new JvmHeapSizeValidator().validate(model, deployState));
+ }
+
+ private static DeployState createDeployState(String servicesXml, double nodeGb, long modelCostBytes) {
+ return new DeployState.Builder()
+ .applicationPackage(
+ new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build())
+ .modelHostProvisioner(new InMemoryProvisioner(5, new NodeResources(4, nodeGb, 125, 0.3), true))
+ .properties(new TestProperties().setHostedVespa(true).setDynamicHeapSize(true))
+ .onnxModelCost(new ModelCostDummy(modelCostBytes))
+ .build();
+ }
+
+ private static DeployState createDeployState(double nodeGb, long modelCostBytes) {
+ String servicesXml =
+ """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <services version='1.0'>
+ <container version='1.0'>
+ <nodes count="2">
+ <resources vcpu="4" memory="%fGb" disk="125Gb"/>
+ </nodes>
+ <component id="hf-embedder" type="hugging-face-embedder">
+ <transformer-model url="https://my/url/model.onnx"/>
+ <tokenizer-model path="app/tokenizer.json"/>
+ </component>
+ </container>
+ </services>""".formatted(nodeGb);
+ return createDeployState(servicesXml, nodeGb, modelCostBytes);
+ }
+
+ private static class ModelCostDummy implements OnnxModelCost, OnnxModelCost.Calculator {
+ final AtomicLong totalCost = new AtomicLong();
+ final long modelCost;
+
+ ModelCostDummy(long modelCost) { this.modelCost = modelCost; }
+
+ @Override public Calculator newCalculator(ApplicationPackage appPkg, DeployLogger logger) { return this; }
+ @Override public long aggregatedModelCostInBytes() { return totalCost.get(); }
+ @Override public void registerModel(ApplicationFile path) {}
+
+ @SuppressWarnings("removal") @Override public void registerModel(ModelReference ref) {}
+
+ @Override
+ public void registerModel(URI uri) {
+ assertEquals("https://my/url/model.onnx", uri.toString());
+ totalCost.addAndGet(modelCost);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java
new file mode 100644
index 00000000000..cef4d8c27dd
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java
@@ -0,0 +1,107 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.embedding.BertBaseEmbedderConfig;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yahoo.config.provision.Environment.prod;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class UrlConfigValidatorTest {
+
+ @Test
+ void failsWhenContainerNodesNotExclusive() throws IOException, SAXException {
+ runValidatorOnApp(true, SystemName.Public); // Exclusive nodes in public => success
+
+ assertEquals("Found s3:// urls in config for container cluster default. This is only supported in public systems",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(false, SystemName.main))
+ .getMessage());
+
+ assertEquals("Found s3:// urls in config for container cluster default. This is only supported in public systems",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(true, SystemName.main))
+ .getMessage());
+
+ assertEquals("Found s3:// urls in config for container cluster default. Nodes in the cluster need to be 'exclusive',"
+ + " see https://cloud.vespa.ai/en/reference/services#nodes",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(false, SystemName.Public))
+ .getMessage());
+ }
+
+ private static String containerXml(boolean isExclusive) {
+ return """
+ <container version='1.0' id='default'>
+ <component id='transformer' class='ai.vespa.embedding.BertBaseEmbedder' bundle='model-integration'>
+ <config name='embedding.bert-base-embedder'>
+ <transformerModel url='s3://models/minilm-l6-v2/sentence_all_MiniLM_L6_v2.onnx' path='foo'/>
+ <tokenizerVocab url='s3://models/bert-base-uncased.txt'/>
+ </config>
+ </component>
+ <search/>
+ <document-api/>
+ <nodes count='2' exclusive='%s' />
+ </container>
+ """.formatted(Boolean.toString(isExclusive));
+ }
+
+ private static void runValidatorOnApp(boolean isExclusive, SystemName systemName) throws IOException, SAXException {
+ String container = containerXml(isExclusive);
+ String servicesXml = """
+ <services version='1.0'>
+ %s
+ </services>
+ """.formatted(container);
+ ApplicationPackage app = new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build();
+ DeployState deployState = createDeployState(app, systemName);
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ new UrlConfigValidator().validate(model, deployState);
+ }
+
+ private static DeployState createDeployState(ApplicationPackage app, SystemName systemName) {
+ boolean isHosted = true;
+ var builder = new DeployState.Builder()
+ .applicationPackage(app)
+ .zone(new Zone(systemName, prod, RegionName.from("us-east-3")))
+ .properties(new TestProperties().setHostedVespa(isHosted))
+ .fileRegistry(new MockFileRegistry());
+
+ Map<ConfigDefinitionKey, ConfigDefinition> defs = new HashMap<>();
+ defs.put(new ConfigDefinitionKey(BertBaseEmbedderConfig.CONFIG_DEF_NAME, BertBaseEmbedderConfig.CONFIG_DEF_NAMESPACE),
+ new ConfigDefinition(BertBaseEmbedderConfig.CONFIG_DEF_NAME, BertBaseEmbedderConfig.CONFIG_DEF_SCHEMA));
+ builder.configDefinitionRepo(new ConfigDefinitionRepo() {
+ @Override
+ public Map<ConfigDefinitionKey, com.yahoo.vespa.config.buildergen.ConfigDefinition> getConfigDefinitions() {
+ return defs;
+ }
+
+ @Override
+ public com.yahoo.vespa.config.buildergen.ConfigDefinition get(ConfigDefinitionKey key) {
+ return defs.get(key);
+ }
+ });
+ return builder.build();
+ }
+
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
index 894fc55c014..3bdb60a0a8d 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
@@ -42,14 +42,14 @@ import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
-import static com.yahoo.config.model.api.ApplicationClusterEndpoint.RoutingMethod.exclusive;
-import static com.yahoo.config.model.api.ApplicationClusterEndpoint.RoutingMethod.shared;
import static com.yahoo.config.model.api.ApplicationClusterEndpoint.RoutingMethod.sharedLayer4;
import static com.yahoo.config.model.api.ApplicationClusterEndpoint.Scope.application;
import static com.yahoo.config.model.api.ApplicationClusterEndpoint.Scope.global;
-import static com.yahoo.config.provision.SystemName.cd;
+import static com.yahoo.config.model.api.ApplicationClusterEndpoint.Scope.zone;
import static com.yahoo.config.provision.SystemName.main;
-import static org.junit.jupiter.api.Assertions.*;
+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 Simon Thoresen Hult
@@ -365,62 +365,23 @@ public class ContainerClusterTest {
@Test
void generatesCorrectRoutingInfo() {
- // main system:
assertNames(main,
ApplicationId.from("t1", "a1", "i1"),
- Set.of(),
+ Set.of(new ContainerEndpoint("search-cluster", zone, List.of("search-cluster.i1.a1.t1.endpoint.suffix"), OptionalInt.empty(), sharedLayer4)),
List.of("search-cluster.i1.a1.t1.endpoint.suffix"));
assertNames(main,
ApplicationId.from("t1", "a1", "default"),
- Set.of(),
- List.of("search-cluster.a1.t1.endpoint.suffix"));
-
- assertNames(main,
- ApplicationId.from("t1", "default", "default"),
- Set.of(),
- List.of("search-cluster.default.t1.endpoint.suffix"));
-
- assertNames(main,
- ApplicationId.from("t1", "a1", "default"),
- Set.of(new ContainerEndpoint("not-in-this-cluster", global, List.of("foo", "bar"))),
+ Set.of(new ContainerEndpoint("not-in-this-cluster", global, List.of("foo", "bar")),
+ new ContainerEndpoint("search-cluster", zone, List.of("search-cluster.a1.t1.endpoint.suffix"), OptionalInt.empty(), sharedLayer4)),
List.of("search-cluster.a1.t1.endpoint.suffix"));
assertNames(main,
ApplicationId.from("t1", "a1", "default"),
- Set.of(new ContainerEndpoint("search-cluster", global, List.of("rotation-1.x.y.z", "rotation-2.x.y.z"), OptionalInt.empty(), sharedLayer4),
- new ContainerEndpoint("search-cluster", application, List.of("app-rotation.x.y.z"), OptionalInt.of(3), sharedLayer4)),
+ Set.of(new ContainerEndpoint("search-cluster", global, List.of("rotation-1.x.y.z", "rotation-2.x.y.z"), OptionalInt.empty(), sharedLayer4),
+ new ContainerEndpoint("search-cluster", application, List.of("app-rotation.x.y.z"), OptionalInt.of(3), sharedLayer4),
+ new ContainerEndpoint("search-cluster", zone, List.of("search-cluster.a1.t1.endpoint.suffix"), OptionalInt.empty(), sharedLayer4)),
List.of("search-cluster.a1.t1.endpoint.suffix", "rotation-1.x.y.z", "rotation-2.x.y.z", "app-rotation.x.y.z"));
-
- // cd system:
- assertNames(cd,
- ApplicationId.from("t1", "a1", "i1"),
- Set.of(),
- List.of("search-cluster.cd.i1.a1.t1.endpoint.suffix"));
-
- assertNames(cd,
- ApplicationId.from("t1", "a1", "default"),
- Set.of(),
- List.of("search-cluster.cd.a1.t1.endpoint.suffix"));
-
- assertNames(cd,
- ApplicationId.from("t1", "default", "default"),
- Set.of(),
- List.of("search-cluster.cd.default.t1.endpoint.suffix"));
-
- assertNames(cd,
- ApplicationId.from("t1", "a1", "default"),
- Set.of(new ContainerEndpoint("not-in-this-cluster", global, List.of("foo", "bar"))),
- List.of("search-cluster.cd.a1.t1.endpoint.suffix"));
-
- assertNames(cd,
- ApplicationId.from("t1", "a1", "default"),
- Set.of(new ContainerEndpoint("search-cluster", global, List.of("rotation-1.x.y.z", "rotation-2.x.y.z"), OptionalInt.empty(), sharedLayer4),
- new ContainerEndpoint("search-cluster", global, List.of("a.b.x.y.z", "rotation-2.x.y.z"), OptionalInt.empty(), shared),
- new ContainerEndpoint("search-cluster", application, List.of("app-rotation.x.y.z"), OptionalInt.of(3), sharedLayer4),
- new ContainerEndpoint("not-supported", global, List.of("not.supported"), OptionalInt.empty(), exclusive)),
- List.of("search-cluster.cd.a1.t1.endpoint.suffix", "rotation-1.x.y.z", "rotation-2.x.y.z", "app-rotation.x.y.z"));
-
}
private void assertNames(SystemName systemName, ApplicationId appId, Set<ContainerEndpoint> globalEndpoints, List<String> expectedSharedL4Names) {
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index 94d98f526a0..2dbf49ba61c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -59,7 +59,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
-import java.util.logging.Level;
import static com.yahoo.config.model.test.TestUtil.joinLines;
import static com.yahoo.test.LinePatternMatcher.containsLineWithPattern;
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java
index 25c6c0ccb1c..9e8e6ff9c71 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java
@@ -220,6 +220,26 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase {
}
@Test
+ void threadpool_configuration_allow_new_syntax() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container id='default' version='1.0'>",
+ " <search>",
+ " <threadpool>",
+ " <threads boost=\"10.2\">0.4</threads>",
+ " <queue>50</queue>",
+ " </threadpool>",
+ " </search>",
+ nodesXml,
+ "</container>");
+ createModel(root, clusterElem);
+ ContainerThreadpoolConfig config = root.getConfig(
+ ContainerThreadpoolConfig.class, "default/component/" + SearchHandler.HANDLER_CLASSNAME + "/threadpool@search-handler");
+ assertEquals(-10, config.maxThreads());
+ assertEquals(-1, config.minThreads());
+ assertEquals(-50, config.queueSize());
+ }
+
+ @Test
void ExecutionFactory_gets_same_chains_config_as_SearchHandler() {
createBasicSearchModel();
Component<?, ?> executionFactory = ((SearchHandler) getComponent("default", SearchHandler.HANDLER_CLASSNAME))
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFilesTest.java b/config-model/src/test/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFilesTest.java
index bb5ba840c2c..deea1a820d9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFilesTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/filedistribution/UserConfiguredFilesTest.java
@@ -7,12 +7,14 @@ import com.yahoo.config.ModelReference;
import com.yahoo.config.UrlReference;
import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.producer.UserConfigRepo;
import com.yahoo.config.model.test.MockRoot;
import com.yahoo.vespa.config.ConfigDefinition;
import com.yahoo.vespa.config.ConfigDefinitionKey;
import com.yahoo.vespa.config.ConfigPayloadBuilder;
import com.yahoo.vespa.model.SimpleConfigProducer;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -30,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Ulf Lilleengen
+ * @author hmusum
*/
public class UserConfiguredFilesTest {
@@ -68,7 +71,10 @@ public class UserConfiguredFilesTest {
}
private UserConfiguredFiles userConfiguredFiles() {
- return new UserConfiguredFiles(fileRegistry, new BaseDeployLogger());
+ return new UserConfiguredFiles(fileRegistry,
+ new BaseDeployLogger(),
+ new TestProperties(),
+ new ApplicationContainerCluster.UserConfiguredUrls());
}
@BeforeEach
@@ -263,7 +269,8 @@ public class UserConfiguredFilesTest {
userConfiguredFiles().register(producer);
fail("Should have thrown exception");
} catch (IllegalArgumentException e) {
- assertEquals("Unable to register file specified in services.xml for config 'mynamespace.myname': No such file or directory 'foo.txt'", e.getMessage());
+ assertEquals("Invalid config in services.xml for 'mynamespace.myname': No such file or directory 'foo.txt'",
+ e.getMessage());
}
}
@@ -276,7 +283,7 @@ public class UserConfiguredFilesTest {
userConfiguredFiles().register(producer);
fail("Should have thrown exception");
} catch (IllegalArgumentException e) {
- assertEquals("Unable to register file specified in services.xml for config 'mynamespace.myname': Unable to register file for field 'fileVal': Invalid config value '.'",
+ assertEquals("Invalid config in services.xml for 'mynamespace.myname': Invalid config value '.' for field 'fileVal",
e.getMessage());
}
}
@@ -291,8 +298,7 @@ public class UserConfiguredFilesTest {
userConfiguredFiles().register(producer);
fail("Should have thrown exception");
} catch (IllegalArgumentException e) {
- assertEquals("Unable to register file specified in services.xml for config 'mynamespace.myname': Directory '" +
- relativeTempDir + "' is empty",
+ assertEquals("Invalid config in services.xml for 'mynamespace.myname': Directory '" + relativeTempDir + "' is empty",
e.getMessage());
}
}
diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml
index e5cb7e8ef54..8a80c485b9d 100644
--- a/config-model/src/test/schema-test-files/services.xml
+++ b/config-model/src/test/schema-test-files/services.xml
@@ -252,5 +252,11 @@
<aws-parameter-store account="foo" aws-region="us-east-1"/>
</store>
</secret-store>
+ <search>
+ <threadpool>
+ <threads boost="32.0">8.0</threads>
+ <queue>40.0</queue>
+ </threadpool>
+ </search>
</container>
</services>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
index 49e0b0f478d..dd971ec5108 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
@@ -102,6 +102,10 @@ public class ApplicationId implements Comparable<ApplicationId> {
return tenant.value() + ":" + application.value() + ":" + instance.value();
}
+ public String toSerializedFormWithoutInstance() {
+ return tenant.value() + ":" + application.value();
+ }
+
@Override
public String toString() { return toShortString(); }
@@ -119,6 +123,11 @@ public class ApplicationId implements Comparable<ApplicationId> {
return new ApplicationId(TenantName.defaultName(), ApplicationName.defaultName(), InstanceName.defaultName());
}
+ /** Returns a serialized form of tenant:application to be used with e.g Flags */
+ public static String toSerializedForm(TenantName tenant, ApplicationName application) {
+ return tenant.value() + ":" + application.value();
+ }
+
// TODO: kill this
/** Returns a very special application id, which is not equal to any other id. */
public static ApplicationId global() {
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
index 830e47aa549..51fe16fb232 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
@@ -19,7 +19,7 @@ public final class ClusterSpec {
private final Type type;
private final Id id;
- /** The group id of these hosts, or empty if this is represents a request for hosts */
+ /** The group id of these hosts, or empty if this represents a request for hosts */
private final Optional<Group> groupId;
private final Version vespaVersion;
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/WireguardKeyWithTimestamp.java b/config-provisioning/src/main/java/com/yahoo/config/provision/WireguardKeyWithTimestamp.java
new file mode 100644
index 00000000000..ecc1cf71113
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/WireguardKeyWithTimestamp.java
@@ -0,0 +1,39 @@
+package com.yahoo.config.provision;
+
+import com.yahoo.jdisc.Timer;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Random;
+
+/**
+ * @author gjoranv
+ */
+public record WireguardKeyWithTimestamp(WireguardKey key, Instant timestamp) {
+
+ public static final int KEY_ROTATION_BASE = 60;
+ public static final int KEY_ROTATION_VARIANCE = 10;
+ public static final int KEY_EXPIRY = KEY_ROTATION_BASE + KEY_ROTATION_VARIANCE + 5;
+
+ public WireguardKeyWithTimestamp {
+ if (key == null) throw new IllegalArgumentException("Wireguard key cannot be null");
+ if (timestamp == null) timestamp = Instant.EPOCH;
+ }
+
+ public static WireguardKeyWithTimestamp from(String key, long msTimestamp) {
+ return new WireguardKeyWithTimestamp(WireguardKey.from(key), Instant.ofEpochMilli(msTimestamp));
+ }
+
+ public boolean isDueForRotation(Timer timer, ChronoUnit unit, Random random) {
+ return timer.currentTime().isAfter(keyRotationDueAt(unit, random));
+ }
+
+ public boolean hasExpired(Timer timer, ChronoUnit unit) {
+ return timer.currentTime().isAfter(timestamp.plus(KEY_EXPIRY, unit));
+ }
+
+ private Instant keyRotationDueAt(ChronoUnit unit, Random random) {
+ return timestamp.plus(KEY_ROTATION_BASE + random.nextInt(KEY_ROTATION_VARIANCE), unit);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java
index 5d5757ec79a..2959815dd28 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java
@@ -14,9 +14,16 @@ public class ZoneEndpoint {
/**
* Endpoint service generation.
- * Bump this to provision new services, whenever we change regional endpoint names.
- * This will cause new endpoint services to be provisioned, with new domain names.
- * TODO: wire multiple service IDs to and through the controller.
+ * <p>
+ * This is used to transition to a new set of endpoint services, with new domain names.
+ * The procedure is:
+ * <ol>
+ * <li>Start using new endpoint names (in controller code), for <em>all</em> applications.</li>
+ * <li>Bump the generation counter here; this causes new services to be provisioned.</li>
+ * <li>Controller configures the new services with the new endpoint names.</li>
+ * <li>Let users migrate to the new endpoint names.</li>
+ * <li>Currently missing: clean up obsolete, unused endpoint services.</li>
+ * </ol>
*/
public static final int generation = 0;
public static final ZoneEndpoint defaultEndpoint = new ZoneEndpoint(true, false, List.of());
diff --git a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java
index 3b24e1c1b8d..4f8d42e895b 100644
--- a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java
+++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java
@@ -111,7 +111,7 @@ public class FlagsHandlerTest {
},
{
"type": "blacklist",
- "dimension": "application",
+ "dimension": "instance",
"values": [ "app1", "app2" ]
}
],
@@ -127,7 +127,7 @@ public class FlagsHandlerTest {
// GET on id2 should now return what was put
verifySuccessfulRequest(Method.GET, "/data/" + FLAG2.id(), "",
- "{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}");
+ "{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"instance\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}");
// The list of flag data should return id1 and id2
verifySuccessfulRequest(Method.GET, "/data",
@@ -153,7 +153,7 @@ public class FlagsHandlerTest {
// Get all recursivelly displays all flag data
verifySuccessfulRequest(Method.GET, "/data?recursive=true", "",
- "{\"flags\":[{\"id\":\"id1\",\"rules\":[{\"value\":false}]},{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}]}");
+ "{\"flags\":[{\"id\":\"id1\",\"rules\":[{\"value\":false}]},{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"instance\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}]}");
// Deleting both flags
verifySuccessfulRequest(Method.DELETE, "/data/" + FLAG1.id(), "", "");
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index 9533f04107d..e675e00b642 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -41,16 +41,18 @@ import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
import com.yahoo.vespa.applicationmodel.InfrastructureApplication;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprintsClient;
import com.yahoo.vespa.config.server.application.Application;
import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase;
import com.yahoo.vespa.config.server.application.ApplicationData;
import com.yahoo.vespa.config.server.application.ApplicationReindexing;
-import com.yahoo.vespa.config.server.application.ApplicationReindexing.Status;
import com.yahoo.vespa.config.server.application.ApplicationVersions;
import com.yahoo.vespa.config.server.application.ClusterReindexing;
import com.yahoo.vespa.config.server.application.ClusterReindexingStatusClient;
import com.yahoo.vespa.config.server.application.CompressedApplicationInputStream;
import com.yahoo.vespa.config.server.application.ConfigConvergenceChecker;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints;
import com.yahoo.vespa.config.server.application.DefaultClusterReindexingStatusClient;
import com.yahoo.vespa.config.server.application.FileDistributionStatus;
import com.yahoo.vespa.config.server.application.HttpProxy;
@@ -129,7 +131,6 @@ import static com.yahoo.vespa.config.server.tenant.TenantRepository.HOSTED_VESPA
import static com.yahoo.vespa.curator.Curator.CompletionWaiter;
import static com.yahoo.yolean.Exceptions.uncheck;
import static java.nio.file.Files.readAttributes;
-import static java.util.Comparator.naturalOrder;
/**
* The API for managing applications.
@@ -159,6 +160,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private final Metric metric;
private final SecretStoreValidator secretStoreValidator;
private final ClusterReindexingStatusClient clusterReindexingStatusClient;
+ private final ActiveTokenFingerprints activeTokenFingerprints;
private final FlagSource flagSource;
@Inject
@@ -188,6 +190,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
metric,
new SecretStoreValidator(secretStore),
new DefaultClusterReindexingStatusClient(),
+ new ActiveTokenFingerprintsClient(),
flagSource);
}
@@ -205,6 +208,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
Metric metric,
SecretStoreValidator secretStoreValidator,
ClusterReindexingStatusClient clusterReindexingStatusClient,
+ ActiveTokenFingerprints activeTokenFingerprints,
FlagSource flagSource) {
this.tenantRepository = Objects.requireNonNull(tenantRepository);
this.hostProvisioner = Objects.requireNonNull(hostProvisioner);
@@ -219,7 +223,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
this.testerClient = Objects.requireNonNull(testerClient);
this.metric = Objects.requireNonNull(metric);
this.secretStoreValidator = Objects.requireNonNull(secretStoreValidator);
- this.clusterReindexingStatusClient = clusterReindexingStatusClient;
+ this.clusterReindexingStatusClient = Objects.requireNonNull(clusterReindexingStatusClient);
+ this.activeTokenFingerprints = Objects.requireNonNull(activeTokenFingerprints);
this.flagSource = flagSource;
}
@@ -237,6 +242,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private SecretStoreValidator secretStoreValidator = new SecretStoreValidator(new SecretStoreProvider().get());
private FlagSource flagSource = new InMemoryFlagSource();
private ConfigConvergenceChecker configConvergenceChecker = new ConfigConvergenceChecker();
+ private Map<String, List<Token>> activeTokens = Map.of();
public Builder withTenantRepository(TenantRepository tenantRepository) {
this.tenantRepository = tenantRepository;
@@ -298,6 +304,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return this;
}
+ public Builder withActiveTokens(Map<String, List<Token>> tokens) {
+ this.activeTokens = tokens;
+ return this;
+ }
+
public ApplicationRepository build() {
return new ApplicationRepository(tenantRepository,
tenantRepository.hostProvisionerProvider().getHostProvisioner(),
@@ -313,6 +324,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
metric,
secretStoreValidator,
ClusterReindexingStatusClient.DUMMY_INSTANCE,
+ __ -> activeTokens,
flagSource);
}
@@ -612,6 +624,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return uncheck(() -> clusterReindexingStatusClient.getReindexingStatus(getApplication(applicationId)));
}
+ public Map<String, List<Token>> activeTokenFingerprints(ApplicationId applicationId) {
+ return activeTokenFingerprints.get(getApplication(applicationId));
+ }
+
public Long getApplicationGeneration(ApplicationId applicationId) {
return getApplication(applicationId).getApplicationGeneration();
}
@@ -1030,21 +1046,21 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private Session validateThatLocalSessionIsNotActive(Tenant tenant, long sessionId) {
Session session = getLocalSession(tenant, sessionId);
if (Session.Status.ACTIVATE.equals(session.getStatus())) {
- throw new IllegalArgumentException("Session is active: " + sessionId);
+ throw new IllegalArgumentException("Session " + sessionId + " for '" + tenant.getName() + "' is active");
}
return session;
}
private Session getLocalSession(Tenant tenant, long sessionId) {
Session session = tenant.getSessionRepository().getLocalSession(sessionId);
- if (session == null) throw new NotFoundException("Session " + sessionId + " was not found");
+ if (session == null) throw new NotFoundException("Local session " + sessionId + " for '" + tenant.getName() + "' was not found");
return session;
}
private RemoteSession getRemoteSession(Tenant tenant, long sessionId) {
RemoteSession session = tenant.getSessionRepository().getRemoteSession(sessionId);
- if (session == null) throw new NotFoundException("Session " + sessionId + " was not found");
+ if (session == null) throw new NotFoundException("Remote session " + sessionId + " for '" + tenant.getName() + "' was not found");
return session;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/FallbackOnnxModelCostProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/FallbackOnnxModelCostProvider.java
new file mode 100644
index 00000000000..57cfb1cd43b
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/FallbackOnnxModelCostProvider.java
@@ -0,0 +1,16 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.model.api.OnnxModelCost;
+import com.yahoo.container.di.componentgraph.Provider;
+
+/**
+ * Default provider that provides a disabled {@link OnnxModelCost} instance.
+ *
+ * @author bjorncs
+ */
+public class FallbackOnnxModelCostProvider implements Provider<OnnxModelCost> {
+ @Override public OnnxModelCost get() { return OnnxModelCost.disabled(); }
+ @Override public void deconstruct() {}
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java
new file mode 100644
index 00000000000..9cde5e38302
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java
@@ -0,0 +1,18 @@
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author jonmv
+ */
+public interface ActiveTokenFingerprints {
+
+ /** Lists all active tokens and their fingerprints for each token-enabled container host in the application, that is currently up. */
+ Map<String, List<Token>> get(ModelResult application);
+
+ record Token(String id, List<String> fingerprints) { }
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java
new file mode 100644
index 00000000000..4e9eac7a9a6
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java
@@ -0,0 +1,123 @@
+package com.yahoo.vespa.config.server.application;
+
+import ai.vespa.http.DomainName;
+import ai.vespa.http.HttpURL;
+import ai.vespa.http.HttpURL.Path;
+import ai.vespa.http.HttpURL.Scheme;
+import ai.vespa.util.http.hc5.VespaAsyncHttpClientBuilder;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.AuthMethod;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.slime.Inspector;
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Phaser;
+
+import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
+import static com.yahoo.config.model.api.container.ContainerServiceType.QRSERVER;
+import static com.yahoo.slime.SlimeUtils.entriesStream;
+import static com.yahoo.slime.SlimeUtils.jsonToSlime;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * @author jonmv
+ */
+public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, AutoCloseable {
+
+ private final CloseableHttpAsyncClient httpClient = createHttpClient();
+
+ public ActiveTokenFingerprintsClient() {
+ httpClient.start();
+ }
+
+ @Override
+ public Map<String, List<Token>> get(ModelResult application) {
+ Set<String> containersWithTokenFilter = application.getModel().applicationClusterInfo().stream()
+ .flatMap(cluster -> cluster.endpoints().stream())
+ .filter(endpoint -> endpoint.authMethod() == AuthMethod.token)
+ .flatMap(endpoint -> endpoint.hostNames().stream())
+ .collect(toSet());
+ return getFingerprints(application.getModel().getHosts().stream()
+ .filter(host -> containersWithTokenFilter.contains(host.getHostname()))
+ .flatMap(host -> host.getServices().stream())
+ .filter(service -> service.getServiceType().equals(CONTAINER.serviceName)
+ || service.getServiceType().equals(QRSERVER.serviceName))
+ .toList());
+ }
+
+ private Map<String, List<Token>> getFingerprints(List<ServiceInfo> services) {
+ Map<String, List<Token>> tokens = new ConcurrentHashMap<>();
+ Phaser phaser = new Phaser(services.size() + 1);
+ for (ServiceInfo service : services) getFingerprints(tokens, service, phaser);
+ phaser.arriveAndAwaitAdvance();
+ return tokens;
+ }
+
+ // A container may be unable to provide its fingerprints for a number of reasons, which may be OK, so
+ // we only track those containers which return an OK response, but we do require at least one such response.
+ private void getFingerprints(Map<String, List<Token>> hostTokens, ServiceInfo service, Phaser phaser) {
+ URI uri = HttpURL.create(Scheme.http,
+ DomainName.of(service.getHostName()),
+ service.getPorts().stream().filter(port -> port.getTags().stream().anyMatch("http"::equals)).findAny().get().getPort(),
+ Path.parse("/data-plane-tokens/v1"))
+ .asURI();
+ httpClient.execute(SimpleRequestBuilder.get(uri).build(), new FutureCallback<>() {
+ @Override public void completed(SimpleHttpResponse result) {
+ if (result.getCode() == 200) hostTokens.put(service.getHostName(), parseTokens(result));
+ phaser.arrive();
+ }
+ @Override public void failed(Exception ex) { phaser.arrive(); }
+ @Override public void cancelled() { phaser.arrive(); }
+ });
+ }
+
+ private static List<Token> parseTokens(SimpleHttpResponse response) {
+ return entriesStream(jsonToSlime(response.getBodyBytes()).get().field("tokens"))
+ .map(entry -> new Token(entry.field("id").asString(),
+ entriesStream(entry.field("fingerprints")).map(Inspector::asString).toList()))
+ .toList();
+ }
+
+ private static CloseableHttpAsyncClient createHttpClient() {
+ return VespaAsyncHttpClientBuilder
+ .create(tlsStrategy -> PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(tlsStrategy)
+ .setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setConnectTimeout(Timeout.ofSeconds(2))
+ .build())
+ .build())
+ .setIOReactorConfig(IOReactorConfig.custom()
+ .setSoTimeout(Timeout.ofSeconds(2))
+ .build())
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+ .setConnectionRequestTimeout(Timeout.ofSeconds(2))
+ .setResponseTimeout(Timeout.ofSeconds(2))
+ .build())
+ .setUserAgent("data-plane-token-client")
+ .build();
+ }
+
+ @Override
+ public void close() throws Exception {
+ httpClient.close(CloseMode.GRACEFUL);
+ httpClient.awaitShutdown(TimeValue.ofSeconds(10));
+ }
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
index 693252da43a..ff2c137c11c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
@@ -23,7 +23,6 @@ import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
import com.yahoo.vespa.config.server.monitoring.Metrics;
import com.yahoo.vespa.config.server.rpc.ConfigResponseFactory;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
-import com.yahoo.vespa.curator.CompletionTimeoutException;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.curator.transaction.CuratorTransaction;
@@ -37,7 +36,6 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
-import java.time.Instant;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
@@ -62,6 +60,8 @@ import static java.util.stream.Collectors.toSet;
public class TenantApplications implements RequestHandler, HostValidator {
private static final Logger log = Logger.getLogger(TenantApplications.class.getName());
+ /* Time to wait for all config servers to get event when an application is removed */
+ private static final Duration waitForAll = Duration.ofSeconds(5);
private final Curator curator;
private final ApplicationCuratorDatabase database;
@@ -430,146 +430,17 @@ public class TenantApplications implements RequestHandler, HostValidator {
public TenantFileSystemDirs getTenantFileSystemDirs() { return tenantFileSystemDirs; }
public CompletionWaiter createRemoveApplicationWaiter(ApplicationId applicationId) {
- return RemoveApplicationWaiter.createAndInitialize(curator, applicationId, serverId);
+ return curator.createCompletionWaiter(barrierPath(applicationId), serverId, waitForAll);
}
public CompletionWaiter getRemoveApplicationWaiter(ApplicationId applicationId) {
- return RemoveApplicationWaiter.create(curator, applicationId, serverId);
+ return curator.getCompletionWaiter(barrierPath(applicationId), serverId, waitForAll);
}
- /**
- * Waiter for removing application. Will wait for some time for all servers to remove application,
- * but will accept the majority of servers to have removed app if it takes a long time.
- */
- // TODO: Merge with CuratorCompletionWaiter
- static class RemoveApplicationWaiter implements CompletionWaiter {
-
- private static final java.util.logging.Logger log = Logger.getLogger(RemoveApplicationWaiter.class.getName());
- private static final Duration waitForAllDefault = Duration.ofSeconds(5);
-
- private final Curator curator;
- private final Path barrierPath;
- private final Path waiterNode;
- private final Duration waitForAll;
- private final Clock clock = Clock.systemUTC();
-
- RemoveApplicationWaiter(Curator curator, ApplicationId applicationId, String serverId) {
- this(curator, applicationId, serverId, waitForAllDefault);
- }
-
- RemoveApplicationWaiter(Curator curator, ApplicationId applicationId, String serverId, Duration waitForAll) {
- this.barrierPath = TenantRepository.getBarriersPath().append(applicationId.tenant().value())
- .append("delete-application")
- .append(applicationId.serializedForm());
- this.waiterNode = barrierPath.append(serverId);
- this.curator = curator;
- this.waitForAll = waitForAll;
- }
-
- @Override
- public void awaitCompletion(Duration timeout) {
- List<String> respondents;
- try {
- respondents = awaitInternal(timeout);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- if (respondents.size() < barrierMemberCount()) {
- throw new CompletionTimeoutException("Timed out waiting for peer config servers to remove application " +
- "(waited for barrier " + barrierPath + ")." +
- "Got response from " + respondents + ", but need response from " +
- "at least " + barrierMemberCount() + " server(s). " +
- "Timeout passed as argument was " + timeout.toMillis() + " ms");
- }
- }
-
- private List<String> awaitInternal(Duration timeout) throws Exception {
- Instant startTime = clock.instant();
- Instant endTime = startTime.plus(timeout);
- Instant gotQuorumTime = Instant.EPOCH;
- List<String> respondents;
- do {
- respondents = curator.framework().getChildren().forPath(barrierPath.getAbsolute());
- if (log.isLoggable(Level.FINE)) {
- log.log(Level.FINE, respondents.size() + "/" + curator.zooKeeperEnsembleCount() + " responded: " +
- respondents + ", all participants: " + curator.zooKeeperEnsembleConnectionSpec());
- }
-
- // If all config servers responded, return
- if (respondents.size() == curator.zooKeeperEnsembleCount()) {
- logBarrierCompleted(respondents, startTime);
- break;
- }
-
- // If some are missing, quorum is enough, but wait for all up to 5 seconds before returning
- if (respondents.size() >= barrierMemberCount()) {
- if (gotQuorumTime.isBefore(startTime))
- gotQuorumTime = clock.instant();
-
- // Give up if more than some time has passed since we got quorum, otherwise continue
- if (Duration.between(clock.instant(), gotQuorumTime.plus(waitForAll)).isNegative()) {
- logBarrierCompleted(respondents, startTime);
- break;
- }
- }
-
- Thread.sleep(100);
- } while (clock.instant().isBefore(endTime));
-
- return respondents;
- }
-
- private void logBarrierCompleted(List<String> respondents, Instant startTime) {
- Duration duration = Duration.between(startTime, Instant.now());
- Level level = (duration.minus(Duration.ofSeconds(5))).isNegative() ? Level.FINE : Level.INFO;
- log.log(level, () -> barrierCompletedMessage(respondents, duration));
- }
-
- private String barrierCompletedMessage(List<String> respondents, Duration duration) {
- return barrierPath + " completed in " + duration.toString() +
- ", " + respondents.size() + "/" + curator.zooKeeperEnsembleCount() + " responded: " + respondents;
- }
-
- @Override
- public void notifyCompletion() {
- try {
- curator.framework().create().forPath(waiterNode.getAbsolute());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public String toString() { return "'" + barrierPath + "', " + barrierMemberCount() + " members"; }
-
- public static CompletionWaiter create(Curator curator, ApplicationId applicationId, String serverId) {
- return new RemoveApplicationWaiter(curator, applicationId, serverId);
- }
-
- public static CompletionWaiter create(Curator curator, ApplicationId applicationId, String serverId, Duration waitForAll) {
- return new RemoveApplicationWaiter(curator, applicationId, serverId, waitForAll);
- }
-
- public static CompletionWaiter createAndInitialize(Curator curator, ApplicationId applicationId, String serverId) {
- return createAndInitialize(curator, applicationId, serverId, waitForAllDefault);
- }
-
- public static CompletionWaiter createAndInitialize(Curator curator, ApplicationId applicationId, String serverId, Duration waitForAll) {
- RemoveApplicationWaiter waiter = new RemoveApplicationWaiter(curator, applicationId, serverId, waitForAll);
-
- // Cleanup and create a new barrier path
- Path barrierPath = waiter.barrierPath();
- curator.delete(barrierPath);
- curator.create(barrierPath.getParentPath());
- curator.createAtomically(barrierPath);
-
- return waiter;
- }
-
- private int barrierMemberCount() { return (curator.zooKeeperEnsembleCount() / 2) + 1; /* majority */ }
-
- private Path barrierPath() { return barrierPath; }
-
+ private static Path barrierPath(ApplicationId applicationId) {
+ return TenantRepository.getBarriersPath().append(applicationId.tenant().value())
+ .append("delete-application")
+ .append(applicationId.serializedForm());
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLogger.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLogger.java
index 154d2d0f2f0..042aa2423f3 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLogger.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLogger.java
@@ -11,6 +11,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -35,15 +36,17 @@ public class DeployHandlerLogger implements DeployLogger {
this.logroot = slime.setObject().setArray("log");
}
+ @Override public void log(Level level, String message) { log(level, () -> message); }
+ @Override public void log(Level level, Supplier<String> message) { log(level, message, null); }
+
@Override
@SuppressWarnings("deprecation")
- public void log(Level level, String message) {
- if (level.intValue() <= LogLevel.DEBUG.intValue() && !verbose)
- return;
+ public void log(Level level, Supplier<String> supplier, Throwable throwable) {
+ // Also tee to a normal log, Vespa log for example, but use level fine
+ log.log(Level.FINE, throwable, () -> prefix + supplier.get());
- logJson(level, message);
- // Also tee to a normal log, Vespa log for example, but use level fine
- log.log(Level.FINE, () -> prefix + message);
+ if (level.intValue() <= LogLevel.DEBUG.intValue() && !verbose) return;
+ logJson(level, supplier.get());
}
@Override
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index 142f98e13e3..96b0b03c832 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -13,6 +13,7 @@ import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.api.Quota;
import com.yahoo.config.model.api.Reindexing;
@@ -28,6 +29,7 @@ import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever;
import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.Flag;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.PermanentFlags;
@@ -66,6 +68,7 @@ public class ModelContextImpl implements ModelContext {
private final Optional<? extends Reindexing> reindexing;
private final ModelContext.Properties properties;
private final Optional<File> appDir;
+ private final OnnxModelCost onnxModelCost;
private final Optional<DockerImage> wantedDockerImageRepository;
@@ -92,6 +95,7 @@ public class ModelContextImpl implements ModelContext {
Provisioned provisioned,
ModelContext.Properties properties,
Optional<File> appDir,
+ OnnxModelCost onnxModelCost,
Optional<DockerImage> wantedDockerImageRepository,
Version modelVespaVersion,
Version wantedNodeVespaVersion) {
@@ -109,6 +113,7 @@ public class ModelContextImpl implements ModelContext {
this.wantedDockerImageRepository = wantedDockerImageRepository;
this.modelVespaVersion = modelVespaVersion;
this.wantedNodeVespaVersion = wantedNodeVespaVersion;
+ this.onnxModelCost = onnxModelCost;
}
@Override
@@ -150,6 +155,8 @@ public class ModelContextImpl implements ModelContext {
@Override
public Optional<File> appDir() { return appDir; }
+ @Override public OnnxModelCost onnxModelCost() { return onnxModelCost; }
+
@Override
public Optional<DockerImage> wantedDockerImageRepo() { return wantedDockerImageRepository; }
@@ -201,6 +208,8 @@ public class ModelContextImpl implements ModelContext {
private final boolean enableNestedMultivalueGrouping;
private final boolean useReconfigurableDispatcher;
private final int contentLayerMetadataFeatureLevel;
+ private final boolean dynamicHeapSize;
+ private final String unknownConfigDefinition;
public FeatureFlags(FlagSource source, ApplicationId appId, Version version) {
this.defaultTermwiseLimit = flagValue(source, appId, version, Flags.DEFAULT_TERM_WISE_LIMIT);
@@ -243,6 +252,8 @@ public class ModelContextImpl implements ModelContext {
this.enableNestedMultivalueGrouping = flagValue(source, appId, version, Flags.ENABLE_NESTED_MULTIVALUE_GROUPING);
this.useReconfigurableDispatcher = flagValue(source, appId, version, Flags.USE_RECONFIGURABLE_DISPATCHER);
this.contentLayerMetadataFeatureLevel = flagValue(source, appId, version, Flags.CONTENT_LAYER_METADATA_FEATURE_LEVEL);
+ this.dynamicHeapSize = flagValue(source, appId, version, Flags.DYNAMIC_HEAP_SIZE);
+ this.unknownConfigDefinition = flagValue(source, appId, version, Flags.UNKNOWN_CONFIG_DEFINITION);
}
@Override public int heapSizePercentage() { return heapPercentage; }
@@ -293,10 +304,13 @@ public class ModelContextImpl implements ModelContext {
@Override public boolean enableNestedMultivalueGrouping() { return enableNestedMultivalueGrouping; }
@Override public boolean useReconfigurableDispatcher() { return useReconfigurableDispatcher; }
@Override public int contentLayerMetadataFeatureLevel() { return contentLayerMetadataFeatureLevel; }
+ @Override public boolean dynamicHeapSize() { return dynamicHeapSize; }
+ @Override public String unknownConfigDefinition() { return unknownConfigDefinition; }
private static <V> V flagValue(FlagSource source, ApplicationId appId, Version vespaVersion, UnboundFlag<? extends V, ?, ?> flag) {
return flag.bindTo(source)
.with(FetchVector.Dimension.INSTANCE_ID, appId.serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.toSerializedFormWithoutInstance())
.with(FetchVector.Dimension.VESPA_VERSION, vespaVersion.toFullString())
.with(FetchVector.Dimension.TENANT_ID, appId.tenant().value())
.boxedValue();
@@ -309,6 +323,7 @@ public class ModelContextImpl implements ModelContext {
UnboundFlag<? extends V, ?, ?> flag) {
return flag.bindTo(source)
.with(FetchVector.Dimension.INSTANCE_ID, appId.serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.toSerializedFormWithoutInstance())
.with(FetchVector.Dimension.CLUSTER_TYPE, clusterType.name())
.with(FetchVector.Dimension.VESPA_VERSION, vespaVersion.toFullString())
.boxedValue();
@@ -321,6 +336,7 @@ public class ModelContextImpl implements ModelContext {
UnboundFlag<? extends V, ?, ?> flag) {
return flag.bindTo(source)
.with(FetchVector.Dimension.INSTANCE_ID, appId.serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.toSerializedFormWithoutInstance())
.with(FetchVector.Dimension.CLUSTER_ID, clusterId.value())
.with(FetchVector.Dimension.VESPA_VERSION, vespaVersion.toFullString())
.boxedValue();
@@ -397,21 +413,16 @@ public class ModelContextImpl implements ModelContext {
this.tenantSecretStores = tenantSecretStores;
this.secretStore = secretStore;
this.jvmGCOptionsFlag = PermanentFlags.JVM_GC_OPTIONS.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm());
- this.allowDisableMtls = PermanentFlags.ALLOW_DISABLE_MTLS.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value();
+ .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, applicationId.toSerializedFormWithoutInstance());
+ this.allowDisableMtls = flagValue(flagSource, applicationId, PermanentFlags.ALLOW_DISABLE_MTLS);
this.operatorCertificates = operatorCertificates;
- this.tlsCiphersOverride = PermanentFlags.TLS_CIPHERS_OVERRIDE.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value();
+ this.tlsCiphersOverride = flagValue(flagSource, applicationId, PermanentFlags.TLS_CIPHERS_OVERRIDE);
this.zoneDnsSuffixes = configserverConfig.zoneDnsSuffixes();
- this.environmentVariables = PermanentFlags.ENVIRONMENT_VARIABLES.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value();
+ this.environmentVariables = flagValue(flagSource, applicationId, PermanentFlags.ENVIRONMENT_VARIABLES);
this.cloudAccount = cloudAccount;
- this.allowUserFilters = PermanentFlags.ALLOW_USER_FILTERS.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value();
- this.endpointConnectionTtl = Duration.ofSeconds(
- PermanentFlags.ENDPOINT_CONNECTION_TTL.bindTo(flagSource)
- .with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value());
+ this.allowUserFilters = flagValue(flagSource, applicationId, PermanentFlags.ALLOW_USER_FILTERS);
+ this.endpointConnectionTtl = Duration.ofSeconds(flagValue(flagSource, applicationId, PermanentFlags.ENDPOINT_CONNECTION_TTL));
this.dataplaneTokens = dataplaneTokens;
}
@@ -512,4 +523,10 @@ public class ModelContextImpl implements ModelContext {
@Override public Duration endpointConnectionTtl() { return endpointConnectionTtl; }
}
+ private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) {
+ return flag.bindTo(source)
+ .with(FetchVector.Dimension.INSTANCE_ID, appId.serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.toSerializedFormWithoutInstance())
+ .boxedValue();
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index bd6e0f90b54..f39feceeeb1 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -95,9 +95,11 @@ public class ApplicationHandler extends HttpHandler {
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}")) return getApplicationResponse(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/content/{*}")) return content(applicationId(path), path.getRest(), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/filedistributionstatus")) return filedistributionStatus(applicationId(path), request);
+ if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/active-token-fingerprints")) return activeTokenFingerprints(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/logs")) return logs(applicationId(path), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/deployment")) return deploymentMetrics(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/searchnode")) return searchNodeMetrics(applicationId(path));
+ if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/reindexing")) return getReindexingStatus(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/status/{*}")) return serviceStatusPage(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/state/v1/{*}")) return serviceStateV1(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request);
@@ -105,7 +107,6 @@ public class ApplicationHandler extends HttpHandler {
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/serviceconverge/{hostAndPort}")) return checkServiceConverge(applicationId(path), path.get("hostAndPort"), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/suspended")) return isSuspended(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/tester/{command}")) return testerRequest(applicationId(path), path.get("command"), request);
- if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path));
return ErrorResponse.notFoundError("Nothing at " + path);
}
@@ -150,18 +151,11 @@ public class ApplicationHandler extends HttpHandler {
}
private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, HttpURL.Path pathSuffix, HttpRequest request) {
- HttpURL.Path pathPrefix = HttpURL.Path.empty();
- switch (service) {
- case "container-clustercontroller":
- pathPrefix = pathPrefix.append("clustercontroller-status").append("v1");
- break;
- case "distributor":
- case "storagenode":
- pathPrefix = pathPrefix.append("contentnode-status").append("v1");
- break;
- default:
- throw new com.yahoo.vespa.config.server.NotFoundException("No status page for service: " + service);
- }
+ HttpURL.Path pathPrefix = switch (service) {
+ case "container-clustercontroller" -> HttpURL.Path.empty().append("clustercontroller-status").append("v1");
+ case "distributor", "storagenode" -> HttpURL.Path.empty().append("contentnode-status").append("v1");
+ default -> throw new NotFoundException("No status page for service: " + service);
+ };
return applicationRepository.proxyServiceHostnameRequest(applicationId, hostname, service, pathPrefix.append(pathSuffix), Query.empty().add(request.getJDiscRequest().parameters()), null);
}
@@ -194,6 +188,22 @@ public class ApplicationHandler extends HttpHandler {
return applicationRepository.fileDistributionStatus(applicationId, getTimeoutFromRequest(request));
}
+ private HttpResponse activeTokenFingerprints(ApplicationId applicationId) {
+ Slime slime = new Slime();
+ Cursor hostsArray = slime.setObject().setArray("hosts");
+ applicationRepository.activeTokenFingerprints(applicationId).forEach((host, tokens) -> {
+ Cursor hostObject = hostsArray.addObject();
+ hostObject.setString("host", host);
+ Cursor tokensArray = hostObject.setArray("tokens");
+ tokens.forEach(token -> {
+ Cursor tokenObject = tokensArray.addObject();
+ tokenObject.setString("id", token.id());
+ token.fingerprints().forEach(tokenObject.setArray("fingerprints")::addString);
+ });
+ });
+ return new SlimeJsonResponse(slime);
+ }
+
private HttpResponse logs(ApplicationId applicationId, HttpRequest request) {
Optional<DomainName> hostname = Optional.ofNullable(request.getProperty("hostname")).map(DomainName::of);
String apiParams = Optional.ofNullable(request.getUri().getQuery()).map(q -> "?" + q).orElse("");
@@ -213,19 +223,13 @@ public class ApplicationHandler extends HttpHandler {
}
private HttpResponse testerRequest(ApplicationId applicationId, String command, HttpRequest request) {
- switch (command) {
- case "status":
- return applicationRepository.getTesterStatus(applicationId);
- case "log":
- Long after = Long.valueOf(request.getProperty("after"));
- return applicationRepository.getTesterLog(applicationId, after);
- case "ready":
- return applicationRepository.isTesterReady(applicationId);
- case "report":
- return applicationRepository.getTestReport(applicationId);
- default:
- throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString());
- }
+ return switch (command) {
+ case "status" -> applicationRepository.getTesterStatus(applicationId);
+ case "log" -> applicationRepository.getTesterLog(applicationId, Long.valueOf(request.getProperty("after")));
+ case "ready" -> applicationRepository.isTesterReady(applicationId);
+ case "report" -> applicationRepository.getTestReport(applicationId);
+ default -> throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString());
+ };
}
private HttpResponse quotaUsage(ApplicationId applicationId) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
index 328bd143d81..d302e0e8008 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
@@ -9,6 +9,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.api.ModelFactory;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.application.provider.MockFileRegistry;
import com.yahoo.config.provision.ApplicationId;
@@ -58,6 +59,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
private final FlagSource flagSource;
private final SecretStore secretStore;
private final ExecutorService executor;
+ private final OnnxModelCost onnxModelCost;
public ActivatedModelsBuilder(TenantName tenant,
long applicationGeneration,
@@ -72,7 +74,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
ConfigserverConfig configserverConfig,
Zone zone,
ModelFactoryRegistry modelFactoryRegistry,
- ConfigDefinitionRepo configDefinitionRepo) {
+ ConfigDefinitionRepo configDefinitionRepo,
+ OnnxModelCost onnxModelCost) {
super(modelFactoryRegistry, configserverConfig, zone, hostProvisionerProvider, new SilentDeployLogger());
this.tenant = tenant;
this.applicationGeneration = applicationGeneration;
@@ -84,6 +87,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
this.flagSource = flagSource;
this.secretStore = secretStore;
this.executor = executor;
+ this.onnxModelCost = onnxModelCost;
}
@Override
@@ -108,6 +112,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
provisioned,
modelContextProperties,
Optional.empty(),
+ onnxModelCost,
wantedDockerImageRepository,
modelFactory.version(),
wantedNodeVespaVersion);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
index 4faa475fa08..57c766bb9c2 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
@@ -207,11 +207,12 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> {
builtModelVersions.add(modelVersion);
} catch (RuntimeException e) {
// allow failure to create old config models if there is a validation override that allow skipping old
- // config models or we're manually deploying
+ // config models, or we're manually deploying
if (builtModelVersions.size() > 0 &&
( builtModelVersions.get(0).getModel().skipOldConfigModels(now) || zone().environment().isManuallyDeployed()))
- log.log(Level.INFO, applicationId + ": Failed to build version " + version +
- ", but allow failure due to validation override or manual deployment");
+ log.log(Level.WARNING, applicationId + ": Failed to build version " + version +
+ ", but allow failure due to validation override or manual deployment:"
+ + Exceptions.toMessageString(e));
else {
log.log(Level.SEVERE, applicationId + ": Failed to build version " + version);
throw e;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
index af611b131f6..a3f0284890c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
@@ -16,6 +16,7 @@ import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.api.ModelCreateResult;
import com.yahoo.config.model.api.ModelFactory;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.api.ValidationParameters;
import com.yahoo.config.model.api.ValidationParameters.IgnoreValidationErrors;
@@ -69,6 +70,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P
private final Optional<ApplicationVersions> activeApplicationVersions;
private final Curator curator;
private final ExecutorService executor;
+ private final OnnxModelCost onnxModelCost;
public PreparedModelsBuilder(ModelFactoryRegistry modelFactoryRegistry,
FlagSource flagSource,
@@ -85,7 +87,8 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P
PrepareParams params,
Optional<ApplicationVersions> activeApplicationVersions,
ConfigserverConfig configserverConfig,
- Zone zone) {
+ Zone zone,
+ OnnxModelCost onnxModelCost) {
super(modelFactoryRegistry, configserverConfig, zone, hostProvisionerProvider, deployLogger);
this.flagSource = flagSource;
this.secretStore = secretStore;
@@ -98,6 +101,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P
this.params = params;
this.activeApplicationVersions = activeApplicationVersions;
this.executor = executor;
+ this.onnxModelCost = onnxModelCost;
}
@Override
@@ -123,6 +127,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P
provisioned,
createModelContextProperties(modelFactory.version(), applicationPackage),
getAppDir(applicationPackage),
+ onnxModelCost,
wantedDockerImageRepository,
modelVersion,
wantedNodeVespaVersion);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
index aeff97169f4..67872865106 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
@@ -18,6 +18,7 @@ import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.FileDistribution;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Quota;
import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.AllocatedHosts;
@@ -93,6 +94,7 @@ public class SessionPreparer {
private final FlagSource flagSource;
private final ExecutorService executor;
private final BooleanFlag writeSessionData;
+ private final OnnxModelCost onnxModelCost;
public SessionPreparer(ModelFactoryRegistry modelFactoryRegistry,
FileDistributionFactory fileDistributionFactory,
@@ -103,7 +105,8 @@ public class SessionPreparer {
Curator curator,
Zone zone,
FlagSource flagSource,
- SecretStore secretStore) {
+ SecretStore secretStore,
+ OnnxModelCost onnxModelCost) {
this.modelFactoryRegistry = modelFactoryRegistry;
this.fileDistributionFactory = fileDistributionFactory;
this.hostProvisionerProvider = hostProvisionerProvider;
@@ -115,6 +118,7 @@ public class SessionPreparer {
this.flagSource = flagSource;
this.executor = executor;
this.writeSessionData = Flags.WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB.bindTo(flagSource);
+ this.onnxModelCost = onnxModelCost;
}
ExecutorService getExecutor() { return executor; }
@@ -134,7 +138,8 @@ public class SessionPreparer {
ApplicationId applicationId = params.getApplicationId();
Preparation preparation = new Preparation(hostValidator, logger, params, activeApplicationVersions,
TenantRepository.getTenantPath(applicationId.tenant()),
- serverDbSessionDir, applicationPackage, sessionZooKeeperClient);
+ serverDbSessionDir, applicationPackage, sessionZooKeeperClient,
+ onnxModelCost);
preparation.preprocess();
try {
AllocatedHosts allocatedHosts = preparation.buildModels(now);
@@ -186,7 +191,7 @@ public class SessionPreparer {
Preparation(HostValidator hostValidator, DeployLogger logger, PrepareParams params,
Optional<ApplicationVersions> activeApplicationVersions, Path tenantPath,
File serverDbSessionDir, ApplicationPackage applicationPackage,
- SessionZooKeeperClient sessionZooKeeperClient) {
+ SessionZooKeeperClient sessionZooKeeperClient, OnnxModelCost onnxModelCost) {
this.logger = logger;
this.params = params;
this.applicationPackage = applicationPackage;
@@ -219,7 +224,8 @@ public class SessionPreparer {
params,
activeApplicationVersions,
configserverConfig,
- zone);
+ zone,
+ onnxModelCost);
}
void checkTimeout(String step) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
index 3b57945b21d..eb07e3010c6 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
@@ -9,6 +9,7 @@ import com.yahoo.concurrent.StripedExecutor;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
@@ -118,6 +119,7 @@ public class SessionRepository {
private final SessionPreparer sessionPreparer;
private final Path sessionsPath;
private final TenantName tenantName;
+ private final OnnxModelCost onnxModelCost;
private final SessionCounter sessionCounter;
private final SecretStore secretStore;
private final HostProvisionerProvider hostProvisionerProvider;
@@ -147,8 +149,10 @@ public class SessionRepository {
Clock clock,
ModelFactoryRegistry modelFactoryRegistry,
ConfigDefinitionRepo configDefinitionRepo,
- int maxNodeSize) {
+ int maxNodeSize,
+ OnnxModelCost onnxModelCost) {
this.tenantName = tenantName;
+ this.onnxModelCost = onnxModelCost;
sessionCounter = new SessionCounter(curator, tenantName);
this.sessionsPath = TenantRepository.getSessionsPath(tenantName);
this.clock = clock;
@@ -553,7 +557,8 @@ public class SessionRepository {
configserverConfig,
zone,
modelFactoryRegistry,
- configDefinitionRepo);
+ configDefinitionRepo,
+ onnxModelCost);
return ApplicationVersions.fromList(builder.buildModels(session.getApplicationId(),
session.getDockerImageRepository(),
session.getVespaVersion(),
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
index 2bc8cb5bc0a..378cd9bdb8c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
@@ -118,21 +118,21 @@ public class SessionZooKeeperClient {
public long sessionId() { return sessionId; }
- public CompletionWaiter createActiveWaiter() { return createCompletionWaiter(getWaiterPath(ACTIVE_BARRIER)); }
+ public CompletionWaiter createActiveWaiter() { return createCompletionWaiter(barrierPath(ACTIVE_BARRIER)); }
- CompletionWaiter createPrepareWaiter() { return createCompletionWaiter(getWaiterPath(PREPARE_BARRIER)); }
+ CompletionWaiter createPrepareWaiter() { return createCompletionWaiter(barrierPath(PREPARE_BARRIER)); }
- CompletionWaiter getPrepareWaiter() { return getCompletionWaiter(getWaiterPath(PREPARE_BARRIER)); }
+ CompletionWaiter getPrepareWaiter() { return getCompletionWaiter(barrierPath(PREPARE_BARRIER)); }
- CompletionWaiter getActiveWaiter() { return getCompletionWaiter(getWaiterPath(ACTIVE_BARRIER)); }
+ CompletionWaiter getActiveWaiter() { return getCompletionWaiter(barrierPath(ACTIVE_BARRIER)); }
- CompletionWaiter getUploadWaiter() { return getCompletionWaiter(getWaiterPath(UPLOAD_BARRIER)); }
+ CompletionWaiter getUploadWaiter() { return getCompletionWaiter(barrierPath(UPLOAD_BARRIER)); }
private static final String PREPARE_BARRIER = "prepareBarrier";
private static final String ACTIVE_BARRIER = "activeBarrier";
private static final String UPLOAD_BARRIER = "uploadBarrier";
- private Path getWaiterPath(String barrierName) {
+ private Path barrierPath(String barrierName) {
return sessionPath.append(barrierName);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
index ba09b3de365..ea53c8aa2bb 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
@@ -11,6 +11,7 @@ import com.yahoo.concurrent.Locks;
import com.yahoo.concurrent.StripedExecutor;
import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
@@ -119,6 +120,7 @@ public class TenantRepository {
new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory("check for removed applications"));
private final Curator.DirectoryCache directoryCache;
private final ZookeeperServerConfig zookeeperServerConfig;
+ private final OnnxModelCost onnxModelCost;
/**
* Creates a new tenant repository
@@ -138,7 +140,8 @@ public class TenantRepository {
ConfigActivationListener configActivationListener,
TenantListener tenantListener,
ZookeeperServerConfig zookeeperServerConfig,
- FileDirectory fileDirectory) {
+ FileDirectory fileDirectory,
+ OnnxModelCost onnxModelCost) {
this(hostRegistry,
curator,
metrics,
@@ -157,7 +160,8 @@ public class TenantRepository {
configDefinitionRepo,
configActivationListener,
tenantListener,
- zookeeperServerConfig);
+ zookeeperServerConfig,
+ onnxModelCost);
}
public TenantRepository(HostRegistry hostRegistry,
@@ -178,7 +182,8 @@ public class TenantRepository {
ConfigDefinitionRepo configDefinitionRepo,
ConfigActivationListener configActivationListener,
TenantListener tenantListener,
- ZookeeperServerConfig zookeeperServerConfig) {
+ ZookeeperServerConfig zookeeperServerConfig,
+ OnnxModelCost onnxModelCost) {
this.hostRegistry = hostRegistry;
this.configserverConfig = configserverConfig;
this.curator = curator;
@@ -201,6 +206,7 @@ public class TenantRepository {
this.zookeeperServerConfig = zookeeperServerConfig;
// This we should control with a feature flag.
this.deployHelperExecutor = createModelBuilderExecutor();
+ this.onnxModelCost = onnxModelCost;
curator.framework().getConnectionStateListenable().addListener(this::stateChanged);
@@ -353,7 +359,8 @@ public class TenantRepository {
curator,
zone,
flagSource,
- secretStore);
+ secretStore,
+ onnxModelCost);
SessionRepository sessionRepository = new SessionRepository(tenantName,
applicationRepo,
sessionPreparer,
@@ -371,7 +378,8 @@ public class TenantRepository {
clock,
modelFactoryRegistry,
configDefinitionRepo,
- zookeeperServerConfig.juteMaxBuffer());
+ zookeeperServerConfig.juteMaxBuffer(),
+ onnxModelCost);
log.log(Level.FINE, "Adding tenant '" + tenantName + "'" + ", created " + created +
". Bootstrapping in " + Duration.between(start, clock.instant()));
Tenant tenant = new Tenant(tenantName, sessionRepository, applicationRepo, created);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplication.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplication.java
index 4c262379c35..1288b63cadd 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplication.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplication.java
@@ -111,6 +111,12 @@ public class ZKApplication {
return getBytesInternal(getFullPath(path));
}
+ public long getSize(Path path) {
+ return curator.getStat(path).map(stat -> (long)stat.getDataLength())
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Could not get size from '" + path + "' in zookeeper"));
+ }
+
void putData(Path path, String data) {
byte[] bytes = Utf8.toBytes(data);
ensureDataIsNotTooLarge(bytes, path);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFile.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFile.java
index 6bc29331efb..e51f8627de2 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFile.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFile.java
@@ -3,8 +3,9 @@ package com.yahoo.vespa.config.server.zookeeper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.config.application.api.ApplicationFile;
-import com.yahoo.path.Path;
import com.yahoo.io.IOUtils;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.util.ConfigUtils;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -13,11 +14,9 @@ import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
-import java.util.logging.Level;
-import com.yahoo.vespa.config.util.ConfigUtils;
-
import java.util.ArrayList;
import java.util.List;
+import java.util.logging.Level;
import java.util.logging.Logger;
import static com.yahoo.vespa.config.server.zookeeper.ZKApplication.USERAPP_ZK_SUBPATH;
@@ -184,6 +183,8 @@ class ZKApplicationFile extends ApplicationFile {
}
}
+ @Override public long getSize() { return zkApp.getSize(getZKPath(path)); }
+
@Override
public int compareTo(ApplicationFile other) {
if (other == this) return 0;
diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml
index 02481291213..a1e9bc3054b 100644
--- a/configserver/src/main/resources/configserver-app/services.xml
+++ b/configserver/src/main/resources/configserver-app/services.xml
@@ -26,6 +26,7 @@
<component id="com.yahoo.vespa.config.server.tenant.TenantRepository" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.host.HostRegistry" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.ApplicationRepository" bundle="configserver" />
+ <component id="com.yahoo.vespa.config.server.FallbackOnnxModelCostProvider" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.HealthCheckerProviderProvider" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.version.VersionState" bundle="configserver" />
<component id="com.yahoo.config.provision.Zone" bundle="config-provisioning" />
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index 104727cb4f3..333dae94769 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -605,7 +605,7 @@ public class ApplicationRepositoryTest {
long sessionId = result.sessionId();
exceptionRule.expect(IllegalArgumentException.class);
- exceptionRule.expectMessage("Session is active: 2");
+ exceptionRule.expectMessage("Session 2 for 'test1' is active");
applicationRepository.prepare(sessionId, prepareParams());
exceptionRule.expect(IllegalArgumentException.class);
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
index f5cd56707b3..fccb6785cb8 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
@@ -9,6 +9,7 @@ import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.MockFileRegistry;
@@ -78,6 +79,7 @@ public class ModelContextImplTest {
Optional.empty(),
List.of()),
Optional.empty(),
+ OnnxModelCost.disabled(),
Optional.empty(),
new Version(7),
new Version(8));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java
new file mode 100644
index 00000000000..03e379311cc
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java
@@ -0,0 +1,123 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.yahoo.config.ConfigInstance.Builder;
+import com.yahoo.config.FileReference;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.AuthMethod;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.DnsName;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.RoutingMethod;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.Scope;
+import com.yahoo.config.model.api.ApplicationClusterInfo;
+import com.yahoo.config.model.api.HostInfo;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.PortInfo;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.provision.AllocatedHosts;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token;
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.serverError;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
+import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author jonmv
+ */
+public class ActiveTokenFingerprintsClientTest {
+
+ @Rule public final WireMockRule server1 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server2 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server3 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server4 = new WireMockRule(options().dynamicPort(), true);
+
+ @Test
+ public void verifyMultipleResponsesCombine() throws Exception {
+ try (ActiveTokenFingerprintsClient client = new ActiveTokenFingerprintsClient()) {
+ ModelResult app = MockModel::new;
+ String uriPath = "/data-plane-tokens/v1";
+ server1.stubFor(get(urlEqualTo(uriPath)).willReturn(serverError()));
+ server2.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson("""
+ { "tokens": [ {"id": "t1", "fingerprints": [ "foo", "bar", "baz" ] } ] }
+ """)));
+ server3.stubFor(get(urlEqualTo(uriPath)).willReturn(aResponse().withStatus(503)));
+ server4.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson("""
+ { "tokens": [ {"id": "t2", "fingerprints": [ "quu" ] } ] }
+ """)));
+ Map<String, List<Token>> expected = Map.of("localhost",
+ List.of(new Token("t1", List.of("foo", "bar", "baz"))));
+ assertEquals(expected, client.get(app));
+ }
+ }
+
+ private class MockModel implements Model {
+
+ @Override
+ public Collection<HostInfo> getHosts() {
+ return List.of(host(server1.port(), "localhost"),
+ host(server2.port(), "localhost"),
+ host(server3.port(), "localhost"),
+ host(server4.port(), "127.0.0.1")); // Should not be included, see application cluster info below.
+
+ }
+
+ private HostInfo host(int port, String host) {
+ return new HostInfo(host,
+ List.of(new ServiceInfo("container",
+ CONTAINER.serviceName,
+ List.of(new PortInfo(port, List.of("http"))),
+ Map.of(),
+ "myconfigId",
+ host),
+ new ServiceInfo("logserver",
+ LOGSERVER_CONTAINER.serviceName,
+ List.of(new PortInfo(port, List.of("http"))),
+ Map.of(),
+ "myconfigId",
+ "127.0.0.1"))); // Don't hit this.
+ }
+
+ @Override
+ public Set<ApplicationClusterInfo> applicationClusterInfo() {
+ return Set.of(new ApplicationClusterInfo() {
+ @Override public List<ApplicationClusterEndpoint> endpoints() {
+ return List.of(ApplicationClusterEndpoint.builder()
+ .dnsName(DnsName.from("foo"))
+ .routingMethod(RoutingMethod.exclusive)
+ .authMethod(AuthMethod.token)
+ .scope(Scope.zone)
+ .clusterId("bar")
+ .hosts(List.of("localhost"))
+ .build());
+ }
+ @Override public boolean getDeferChangesUntilRestart() { throw new UnsupportedOperationException(); }
+ @Override public String name() { throw new UnsupportedOperationException(); }
+ });
+ }
+
+ @Override public Builder getConfigInstance(ConfigKey<?> configKey, ConfigDefinition configDefinition) { throw new UnsupportedOperationException(); }
+ @Override public Set<ConfigKey<?>> allConfigsProduced() { throw new UnsupportedOperationException(); }
+ @Override public Set<String> allConfigIds() { throw new UnsupportedOperationException(); }
+ @Override public Set<FileReference> fileReferences() { throw new UnsupportedOperationException(); }
+ @Override public AllocatedHosts allocatedHosts() { throw new UnsupportedOperationException(); }
+
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
index 6bd20a29cf8..72cfe466993 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
@@ -10,6 +10,8 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* Base class for session handler tests
*
@@ -52,7 +54,7 @@ public class SessionHandlerTest {
public static String getRenderedString(HttpResponse response) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
response.render(baos);
- return baos.toString(StandardCharsets.UTF_8);
+ return baos.toString(UTF_8);
}
public enum Cmd {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 951ef9df2f4..6fb5db70b68 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -28,6 +28,7 @@ import com.yahoo.vespa.config.server.MockLogRetriever;
import com.yahoo.vespa.config.server.MockProvisioner;
import com.yahoo.vespa.config.server.MockSecretStoreValidator;
import com.yahoo.vespa.config.server.MockTesterClient;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token;
import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase;
import com.yahoo.vespa.config.server.application.ApplicationReindexing;
import com.yahoo.vespa.config.server.application.ClusterReindexing;
@@ -117,6 +118,7 @@ public class ApplicationHandlerTest {
private ManualClock clock;
private List<Endpoint> expectedEndpoints;
private Availability availability;
+ private Map<String, List<Token>> activeTokenFingerprints;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -140,6 +142,7 @@ public class ApplicationHandlerTest {
.build();
tenantRepository.addTenant(mytenantName);
orchestrator = new OrchestratorMock();
+ activeTokenFingerprints = new HashMap<>();
applicationRepository = new ApplicationRepository.Builder()
.withTenantRepository(tenantRepository)
.withOrchestrator(orchestrator)
@@ -149,6 +152,7 @@ public class ApplicationHandlerTest {
.withConfigserverConfig(configserverConfig)
.withSecretStoreValidator(secretStoreValidator)
.withEndpointsChecker(endpoints -> { assertEquals(expectedEndpoints, endpoints); return availability; })
+ .withActiveTokens(activeTokenFingerprints)
.build();
}
@@ -238,6 +242,19 @@ public class ApplicationHandlerTest {
}
@Test
+ public void testGetTokenFingerprints() throws IOException {
+ applicationRepository.deploy(testApp, prepareParams(applicationId));
+ activeTokenFingerprints.putAll(Map.of("host", List.of(new Token("t1", List.of("fingers", "toes")),
+ new Token("t2", List.of())),
+ "toast", List.of()));
+ HttpResponse response = createApplicationHandler().handleGET(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/active-token-fingerprints", GET));
+ assertEquals(200, response.getStatus());
+ assertEquals("""
+ {"hosts":[{"host":"host","tokens":[{"id":"t1","fingerprints":["fingers","toes"]},{"id":"t2","fingerprints":[]}]},{"host":"toast","tokens":[]}]}""",
+ getRenderedString(response));
+ }
+
+ @Test
public void testReindex() throws Exception {
ApplicationCuratorDatabase database = applicationRepository.getTenant(applicationId).getApplicationRepo().database();
reindexing(applicationId, GET, "{\"error-code\": \"NOT_FOUND\", \"message\": \"Application 'default.default' not found\"}", 404);
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
index 765523177a9..88aed6b058c 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
@@ -92,7 +92,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
public void require_error_when_session_id_does_not_exist() throws Exception {
// No session with this id exists
HttpResponse response = request(HttpRequest.Method.PUT, 9999L);
- assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.ErrorCode.NOT_FOUND, "Session 9999 was not found");
+ assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.ErrorCode.NOT_FOUND, "Local session 9999 for 'test' was not found");
}
@Test
@@ -180,7 +180,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
HttpResponse getResponse = request(HttpRequest.Method.GET, 9999L);
assertHttpStatusCodeErrorCodeAndMessage(getResponse, NOT_FOUND,
HttpErrorResponse.ErrorCode.NOT_FOUND,
- "Session 9999 was not found");
+ "Remote session 9999 for 'test' was not found");
}
@Test
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java
index ca2f9da3273..8d1b0fefbaf 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java
@@ -50,10 +50,11 @@ import static org.junit.Assert.assertTrue;
public class LbServicesProducerTest {
private static final Set<ContainerEndpoint> endpoints = Set.of(
+ new ContainerEndpoint("mydisc", ApplicationClusterEndpoint.Scope.zone, List.of("mydisc.foo.foo.endpoint1.suffix")),
+ new ContainerEndpoint("mydisc", ApplicationClusterEndpoint.Scope.zone, List.of("mydisc.foo.foo.endpoint2.suffix")),
new ContainerEndpoint("mydisc", ApplicationClusterEndpoint.Scope.global, List.of("rotation-1", "rotation-2")),
new ContainerEndpoint("mydisc", ApplicationClusterEndpoint.Scope.application, List.of("app-endpoint"))
);
- private static final List<String> zoneDnsSuffixes = List.of(".endpoint1.suffix", ".endpoint2.suffix");
private final InMemoryFlagSource flagSource = new InMemoryFlagSource();
@@ -228,7 +229,7 @@ public class LbServicesProducerTest {
private TestProperties getTestproperties(ApplicationId applicationId) {
return new TestProperties()
.setHostedVespa(true)
- .setZoneDnsSuffixes(zoneDnsSuffixes)
.setApplicationId(applicationId);
}
+
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
index 0158aa1961d..6dbb0d72c87 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
@@ -10,6 +10,7 @@ import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
@@ -132,7 +133,8 @@ public class SessionPreparerTest {
curator,
zone,
flagSource,
- secretStore);
+ secretStore,
+ OnnxModelCost.disabled());
}
@Test(expected = InvalidApplicationException.class)
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java
index 02ee3202475..1417df73cfc 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java
@@ -6,6 +6,7 @@ import com.yahoo.cloud.config.ZookeeperServerConfig;
import com.yahoo.component.Version;
import com.yahoo.concurrent.InThreadExecutorService;
import com.yahoo.concurrent.StripedExecutor;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
@@ -230,7 +231,8 @@ public class TenantRepositoryTest {
new TestConfigDefinitionRepo(),
new TenantApplicationsTest.MockConfigActivationListener(),
new MockTenantListener(),
- new ZookeeperServerConfig.Builder().myid(0).build());
+ new ZookeeperServerConfig.Builder().myid(0).build(),
+ OnnxModelCost.disabled());
}
@Override
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java
index dd982ccbd72..0419a313dea 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java
@@ -6,6 +6,7 @@ import com.yahoo.cloud.config.ZookeeperServerConfig;
import com.yahoo.concurrent.InThreadExecutorService;
import com.yahoo.concurrent.StripedExecutor;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.config.server.ConfigServerDB;
import com.yahoo.vespa.config.server.MockSecretStore;
@@ -64,7 +65,8 @@ public class TestTenantRepository extends TenantRepository {
configDefinitionRepo,
configActivationListener,
tenantListener,
- new ZookeeperServerConfig.Builder().myid(0).build());
+ new ZookeeperServerConfig.Builder().myid(0).build(),
+ OnnxModelCost.disabled());
}
public static class Builder {
diff --git a/container-disc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java b/container-disc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java
index 67862533259..c29a4c1d009 100644
--- a/container-disc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java
+++ b/container-disc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java
@@ -15,6 +15,8 @@ import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.container.Container;
import com.yahoo.container.core.ApplicationMetadataConfig;
import com.yahoo.container.jdisc.JdiscBindingsConfig;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.handler.AbstractRequestHandler;
import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
@@ -82,22 +84,18 @@ public class ApplicationStatusHandler extends AbstractRequestHandler {
}
@Override
- public ContentChannel handleRequest(com.yahoo.jdisc.Request request, ResponseHandler handler) {
- FastContentWriter writer = new FastContentWriter(new ResponseDispatch() {
- @Override
- protected com.yahoo.jdisc.Response newResponse() {
- com.yahoo.jdisc.Response response = new com.yahoo.jdisc.Response(com.yahoo.jdisc.Response.Status.OK);
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ try (FastContentWriter writer = new FastContentWriter(new ResponseDispatch() {
+ @Override protected Response newResponse() {
+ Response response = new Response(Response.Status.OK);
response.headers().add("Content-Type", List.of("application/json"));
return response;
}
- }.connect(handler));
-
- try {
+ }.connect(handler))) {
writer.write(jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(render()));
} catch (JsonProcessingException e) {
throw new RuntimeException("Invalid JSON: " + e.getMessage(), e);
}
- writer.close();
return new IgnoredContent();
}
diff --git a/container-search/src/main/java/com/yahoo/search/logging/LoggerEntry.java b/container-search/src/main/java/com/yahoo/search/logging/LoggerEntry.java
index 80ff967f779..d8e656d1b2b 100644
--- a/container-search/src/main/java/com/yahoo/search/logging/LoggerEntry.java
+++ b/container-search/src/main/java/com/yahoo/search/logging/LoggerEntry.java
@@ -135,7 +135,7 @@ public class LoggerEntry {
return logger.send(new LoggerEntry(this));
}
- LoggerEntry build() {
+ public LoggerEntry build() {
return new LoggerEntry(this);
}
diff --git a/container-search/src/main/java/com/yahoo/search/logging/Spooler.java b/container-search/src/main/java/com/yahoo/search/logging/Spooler.java
index 921b8f444f1..cf750fd9d8a 100644
--- a/container-search/src/main/java/com/yahoo/search/logging/Spooler.java
+++ b/container-search/src/main/java/com/yahoo/search/logging/Spooler.java
@@ -85,10 +85,10 @@ public class Spooler {
public void processFiles(Function<LoggerEntry, Boolean> transport) throws IOException {
List<Path> files = listFilesInPath(readyPath);
if (files.size() == 0) {
- log.log(Level.FINEST, "No files in ready path " + readyPath.toFile().getAbsolutePath());
+ log.log(Level.FINEST, () -> "No files in ready path " + readyPath.toFile().getAbsolutePath());
return;
}
- log.log(Level.FINE, "Files in ready path: " + files.size());
+ log.log(Level.FINE, () -> "Files in ready path: " + files.size());
List<File> fileList = getFiles(files);
if ( ! fileList.isEmpty()) {
@@ -116,7 +116,7 @@ public class Spooler {
List<String> lines = Files.readAllLines(f.toPath());
for (String line : lines) {
LoggerEntry entry = LoggerEntry.deserialize(line);
- log.log(Level.FINE, "Read entry " + entry + " from " + f);
+ log.log(Level.FINE, () -> "Read entry " + entry + " from " + f);
success = transport.apply(entry);
if (! success) {
throw new RuntimeException("Unable to process file " + f + ": unsuccessful call to transport() for " + entry);
@@ -190,7 +190,7 @@ public class Spooler {
String fileName = currentFileName();
Path file = spoolPath.resolve(processingPath).resolve(fileName);
try {
- log.log(Level.FINE, "Writing entry " + entryCounter.get() + " (" + entry.serialize() + ") to file " + fileName);
+ log.log(Level.FINE, () -> "Writing entry " + entryCounter.get() + " (" + entry.serialize() + ") to file " + fileName);
Files.writeString(file, entry.serialize() + "\n", StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
firstWriteTimestamp.compareAndExchange(Instant.EPOCH, clock.instant());
entryCounter.incrementAndGet();
@@ -242,7 +242,7 @@ public class Spooler {
if (file.exists() && file.canRead() && file.canWrite()) {
log.log(Level.INFO, "Directory " + path + " already exists");
} else if (file.mkdirs()) {
- log.log(Level.FINE, "Created " + path);
+ log.log(Level.FINE, () -> "Created " + path);
} else {
log.log(Level.WARNING, "Could not create " + path + ", please check permissions");
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
index 3edad64f9f2..3dfa278662d 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
@@ -135,14 +135,14 @@ public class NGramSearcher extends Searcher {
* Creates the root of the query subtree which will contain the grams to match,
* called by {@link #splitToGrams}. This hook is provided to make it easy to create a subclass which
* matches grams using a different composite item, e.g an OrItem.
- * <p>
+ *
* This default implementation returns createGramRoot(query).
*
* @param term the term item this gram root is replacing in the query tree,
* typically used to access the index name of the term when that is required by the new gram root
* (such as in PhraseItem)
* @param query the input query, to make it possible to return a different composite item type
- * depending on the query content
+ * depending on the query content
* @return the composite item to add the gram items to in {@link #splitToGrams}
*/
protected CompositeItem createGramRoot(HasIndexItem term, Query query) {
diff --git a/container-search/src/test/java/com/yahoo/prelude/semantics/test/EquivTestCase.java b/container-search/src/test/java/com/yahoo/prelude/semantics/test/EquivTestCase.java
new file mode 100644
index 00000000000..3e2c634b7f2
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/semantics/test/EquivTestCase.java
@@ -0,0 +1,35 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.semantics.test;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author bratseth
+ */
+public class EquivTestCase extends RuleBaseAbstractTestCase {
+
+ public EquivTestCase() {
+ super("equiv.sr");
+ }
+
+ @Test
+ void testEquiv() {
+ assertSemantics("EQUIV \"lord of the rings\" lotr", "lotr");
+ }
+
+ @Test
+ void testEquivWithFollowingQuery() {
+ assertSemantics("AND (EQUIV \"lord of the rings\" lotr) is a movie", "lotr is a movie");
+ }
+
+ @Test
+ void testEquivWithPrecedingQuery() {
+ assertSemantics("AND a movie is (EQUIV \"lord of the rings\" lotr)", "a movie is lotr");
+ }
+
+ @Test
+ void testEquivWithSurroundingQuery() {
+ assertSemantics("AND a movie is (EQUIV \"lord of the rings\" lotr) yes", "a movie is lotr yes");
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/querytransform/test/NGramSearcherTestCase.java b/container-search/src/test/java/com/yahoo/search/querytransform/test/NGramSearcherTestCase.java
index 49449153d1f..4d25ebdccff 100644
--- a/container-search/src/test/java/com/yahoo/search/querytransform/test/NGramSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/querytransform/test/NGramSearcherTestCase.java
@@ -334,15 +334,15 @@ public class NGramSearcherTestCase {
Result r = new Execution(new Chain<>(createSearcher(), new MockBackend1()), createContextStub(createIndexFacts())).search(q);
Hit h1 = r.hits().get("hit1");
assertEquals("Should be untouched,\u001feven if containing \u001f",
- h1.getField("test").toString());
+ h1.getField("test").toString());
assertTrue(h1.getField("test") instanceof String);
assertEquals("Blue red Ed A", h1.getField("gram2").toString());
assertTrue(h1.getField("gram2") instanceof XMLString);
assertEquals("Blue red ed a\u001f",
- h1.getField("gram3").toString(),
- "Separators on borders work");
+ h1.getField("gram3").toString(),
+ "Separators on borders work");
assertTrue(h1.getField("gram3") instanceof String);
Hit h2 = r.hits().get("hit2");
@@ -352,7 +352,7 @@ public class NGramSearcherTestCase {
Hit h3 = r.hits().get("hit3");
assertEquals("\u001ffin\u001f \u001fen\u001f \u001fa\u001f", h3.getField("gram2").toString());
assertEquals("#Logging in #Java is like that \"Judean P\u001fopul\u001far Front\" scene from \"Life of Brian\".",
- h3.getField("gram3").toString());
+ h3.getField("gram3").toString());
}
private Item parseQuery(String query, Query.Type type) {
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
index ed965f4331e..66cf3eef954 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.archive;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -10,6 +11,7 @@ import java.net.URI;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
/**
* Service that manages archive storage URIs for tenant nodes.
@@ -28,4 +30,19 @@ public interface ArchiveService {
Optional<String> findEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount);
URI bucketURI(ZoneId zoneId, String bucketName);
+
+ /**
+ * @return the version of the template that was used during the last apply for the given cloud account,
+ * or {@link Version#emptyVersion} if the version tag was not present or invalid,
+ * or {@link Optional#empty()} if the we have no access to the cloud account (template probably not applied yet)
+ */
+ Optional<Version> getEnclaveTemplateVersion(CloudAccount cloudAccount);
+
+ static Stream<Version> parseVersion(String versionString) {
+ try {
+ return Stream.of(Version.fromString(versionString));
+ } catch (IllegalArgumentException e) {
+ return Stream.empty();
+ }
+ }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
index 7461d3aa47e..4e6e71ca855 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.archive;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -59,6 +60,11 @@ public class MockArchiveService implements ArchiveService {
return URI.create(String.format("s3://%s/", bucketName));
}
+ @Override
+ public Optional<Version> getEnclaveTemplateVersion(CloudAccount cloudAccount) {
+ return Optional.of(new Version(1, 2, 3));
+ }
+
public void setEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount, String bucketName) {
removeEnclaveArchiveBucket(zoneId, cloudAccount);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
index 5c6e5c9542a..dfa0d4dccf2 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.billing;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -109,6 +110,9 @@ public interface BillingController {
/** Get all bills from the system */
List<Bill> getBills();
+ /** Get the bill with the given id */
+ Bill getBill(Bill.Id billId);
+
/** Get the bill collection method for the given tenant */
default CollectionMethod getCollectionMethod(TenantName tenant) {
return CollectionMethod.NONE;
@@ -125,4 +129,8 @@ public interface BillingController {
}
default void updateCache(List<TenantName> tenants) {}
-} \ No newline at end of file
+
+ default String exportBill(Bill bill, String exportMethod, CloudTenant tenant) {
+ return "NOT_IMPLEMENTED";
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
index a4b3abf3bf9..719d22429b8 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
@@ -1,5 +1,8 @@
package com.yahoo.vespa.hosted.controller.api.integration.billing;
+import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+
public interface BillingReporter {
- double maintain();
+ BillingReference maintainTenant(CloudTenant tenant);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java
new file mode 100644
index 00000000000..34599f83a8c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java
@@ -0,0 +1,21 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.billing;
+
+import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+
+import java.time.Clock;
+import java.util.UUID;
+
+public class BillingReporterMock implements BillingReporter {
+ private final Clock clock;
+
+ public BillingReporterMock(Clock clock) {
+ this.clock = clock;
+ }
+
+ @Override
+ public BillingReference maintainTenant(CloudTenant tenant) {
+ return new BillingReference(UUID.randomUUID().toString(), clock.instant());
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
index 671739bacab..eb20126304e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
@@ -171,6 +171,15 @@ public class MockBillingController implements BillingController {
}
@Override
+ public Bill getBill(Bill.Id billId) {
+ return committedBills.values().stream()
+ .flatMap(Collection::stream)
+ .filter(bill -> bill.id().equals(billId))
+ .findFirst()
+ .orElseThrow();
+ }
+
+ @Override
public CollectionMethod getCollectionMethod(TenantName tenant) {
return collectionMethod.getOrDefault(tenant, CollectionMethod.AUTO);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
index 19bfc84db7a..31fdc9d1b64 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.EndpointsChecker.Availability;
import com.yahoo.config.provision.EndpointsChecker.Endpoint;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
import ai.vespa.http.DomainName;
import ai.vespa.http.HttpURL.Path;
@@ -16,6 +17,8 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus
import com.yahoo.vespa.hosted.controller.api.application.v4.model.SearchNodeMetrics;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TestReport;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
@@ -158,4 +161,7 @@ public interface ConfigServer {
/** Validates secret store configuration. */
String validateSecretStore(DeploymentId deploymentId, TenantSecretStore tenantSecretStore, String region, String parameterName);
+ /** Fingerprints of active data plane tokens, per healthy host with token auth, in the given deployment. */
+ Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokenFingerprints(DeploymentId deploymentId);
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MockVpcEndpointService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MockVpcEndpointService.java
index 39975138140..48cdc6ee053 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MockVpcEndpointService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MockVpcEndpointService.java
@@ -29,7 +29,7 @@ public class MockVpcEndpointService implements VpcEndpointService {
}
@Override
- public synchronized Optional<DnsChallenge> setPrivateDns(DomainName privateDnsName, ClusterId clusterId, Optional<CloudAccount> account) {
+ public synchronized Optional<DnsChallenge> setPrivateDns(DomainName privateDnsName, ClusterId clusterId, Optional<CloudAccount> account, boolean isGenerated) {
DnsChallenge challenge = new DnsChallenge(RecordName.from("challenge--" + privateDnsName.value()),
RecordData.from(account.map(CloudAccount::value).orElse("system")),
clusterId,
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/VpcEndpointService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/VpcEndpointService.java
index a3ee7681e2a..97e1b88b25c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/VpcEndpointService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/VpcEndpointService.java
@@ -38,7 +38,7 @@ public interface VpcEndpointService {
enum ChallengeState { pending, ready, running, done }
/** Sets the private DNS name for any VPC endpoint for the given cluster, potentially guarded by a challenge. */
- Optional<DnsChallenge> setPrivateDns(DomainName privateDnsName, ClusterId clusterId, Optional<CloudAccount> account);
+ Optional<DnsChallenge> setPrivateDns(DomainName privateDnsName, ClusterId clusterId, Optional<CloudAccount> account, boolean isGenerated);
/** Attempts to complete the challenge, and returns the updated challenge state. */
ChallengeState process(DnsChallenge challenge);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
index e661c88e117..8f47ac68cda 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
@@ -191,8 +192,8 @@ public class SystemFlagsDataArchive {
flagData.rules().forEach(rule -> rule.conditions().forEach(condition -> {
int force_switch_expression_dummy = switch (condition.type()) {
case RELATIONAL -> switch (condition.dimension()) {
- case INSTANCE_ID, CLOUD, CLOUD_ACCOUNT, CLUSTER_ID, CLUSTER_TYPE, CONSOLE_USER_EMAIL,
- ENVIRONMENT, HOSTNAME, NODE_TYPE, SYSTEM, TENANT_ID, ZONE_ID ->
+ case APPLICATION_ID, CLOUD, CLOUD_ACCOUNT, CLUSTER_ID, CLUSTER_TYPE, CONSOLE_USER_EMAIL,
+ ENVIRONMENT, HOSTNAME, INSTANCE_ID, NODE_TYPE, SYSTEM, TENANT_ID, ZONE_ID ->
throw new FlagValidationException(condition.type().toWire() + " " +
DimensionHelper.toWire(condition.dimension()) +
" condition is not supported");
@@ -206,7 +207,7 @@ public class SystemFlagsDataArchive {
};
case WHITELIST, BLACKLIST -> switch (condition.dimension()) {
- case INSTANCE_ID -> validateConditionValues(condition, ApplicationId::fromSerializedForm);
+ case APPLICATION_ID -> validateConditionValues(condition, SystemFlagsDataArchive::validateTenantApplication);
case CONSOLE_USER_EMAIL -> validateConditionValues(condition, email -> {
if (!email.contains("@"))
throw new FlagValidationException("Invalid email address: " + email);
@@ -220,6 +221,7 @@ public class SystemFlagsDataArchive {
case CLUSTER_TYPE -> validateConditionValues(condition, ClusterSpec.Type::from);
case ENVIRONMENT -> validateConditionValues(condition, Environment::from);
case HOSTNAME -> validateConditionValues(condition, HostName::of);
+ case INSTANCE_ID -> validateConditionValues(condition, ApplicationId::fromSerializedForm);
case NODE_TYPE -> validateConditionValues(condition, NodeType::valueOf);
case SYSTEM -> throw new IllegalStateException("Flag data contains system dimension");
case TENANT_ID -> validateConditionValues(condition, TenantName::from);
@@ -250,23 +252,20 @@ public class SystemFlagsDataArchive {
return 0; // dummy to force switch expression
}
+ private static void validateTenantApplication(String application) {
+ String[] parts = application.split(":");
+ if (parts.length != 2)
+ throw new IllegalArgumentException("Applications must be on the form tenant:application, but was %s".formatted(application));
+ TenantName.from(parts[0]);
+ ApplicationName.from(parts[1]);
+ }
+
private static FlagData parseFlagData(FlagId flagId, String fileContent, ZoneRegistry zoneRegistry, boolean inController) {
if (fileContent.isBlank()) return new FlagData(flagId);
final JsonNode root;
try {
root = mapper.readTree(fileContent);
- // TODO (mortent): Remove this after completing migration of APPLICATION_ID dimension
- // replace "application" with "instance" for all dimension fields
-// List<JsonNode> dimensionParents = root.findParents("dimension");
-// for (JsonNode parentNode : dimensionParents) {
-// JsonNode dimension = parentNode.get("dimension");
-// if (dimension.isTextual() && "application".equals(dimension.textValue())) {
-// ObjectNode parent = (ObjectNode) parentNode;
-// parent.remove("dimension");
-// parent.put("dimension", "instance");
-// }
-// }
} catch (JsonProcessingException e) {
throw new FlagValidationException("Invalid JSON: " + e.getMessage());
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
index 53a3f431de7..0754a5ed49f 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
@@ -8,6 +8,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import java.time.Instant;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -27,8 +28,9 @@ public class AthenzTenant extends Tenant {
* Use {@link #create(TenantName, AthenzDomain, Property, Optional, Instant)}.
* */
public AthenzTenant(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId,
- Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained) {
- super(name, createdAt, lastLoginInfo, contact, tenantRolesLastMaintained);
+ Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained,
+ List<CloudAccountInfo> cloudAccounts) {
+ super(name, createdAt, lastLoginInfo, contact, tenantRolesLastMaintained, cloudAccounts);
this.domain = Objects.requireNonNull(domain, "domain must be non-null");
this.property = Objects.requireNonNull(property, "property must be non-null");
this.propertyId = Objects.requireNonNull(propertyId, "propertyId must be non-null");
@@ -62,7 +64,7 @@ public class AthenzTenant extends Tenant {
/** Create a new Athenz tenant */
public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property,
Optional<PropertyId> propertyId, Instant createdAt) {
- return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty(), createdAt, LastLoginInfo.EMPTY, Instant.EPOCH);
+ return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty(), createdAt, LastLoginInfo.EMPTY, Instant.EPOCH, List.of());
}
@Override
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudAccountInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudAccountInfo.java
new file mode 100644
index 00000000000..430f5770165
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudAccountInfo.java
@@ -0,0 +1,19 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.tenant;
+
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.CloudAccount;
+
+import java.util.Objects;
+
+/**
+ * @author freva
+ */
+public record CloudAccountInfo(CloudAccount cloudAccount, Version templateVersion) {
+
+ public CloudAccountInfo {
+ Objects.requireNonNull(cloudAccount, "cloudAccount must be non-null");
+ Objects.requireNonNull(templateVersion, "templateVersion must be non-null");
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 4d7aee7b604..173d3e1950e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -34,8 +34,8 @@ public class CloudTenant extends Tenant {
BiMap<PublicKey, SimplePrincipal> developerKeys, TenantInfo info,
List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess,
Optional<Instant> invalidateUserSessionsBefore, Instant tenantRoleLastMaintained,
- Optional<BillingReference> billingReference) {
- super(name, createdAt, lastLoginInfo, Optional.empty(), tenantRoleLastMaintained);
+ List<CloudAccountInfo> cloudAccounts, Optional<BillingReference> billingReference) {
+ super(name, createdAt, lastLoginInfo, Optional.empty(), tenantRoleLastMaintained, cloudAccounts);
this.creator = creator;
this.developerKeys = developerKeys;
this.info = Objects.requireNonNull(info);
@@ -51,7 +51,8 @@ public class CloudTenant extends Tenant {
createdAt,
LastLoginInfo.EMPTY,
Optional.ofNullable(creator).map(SimplePrincipal::of),
- ImmutableBiMap.of(), TenantInfo.empty(), List.of(), new ArchiveAccess(), Optional.empty(), Instant.EPOCH, Optional.empty());
+ ImmutableBiMap.of(), TenantInfo.empty(), List.of(), new ArchiveAccess(), Optional.empty(),
+ Instant.EPOCH, List.of(), Optional.empty());
}
/** The user that created the tenant */
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java
index b58fdf81278..30ce5d5a3b2 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.tenant;
import com.yahoo.config.provision.TenantName;
import java.time.Instant;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -17,7 +18,7 @@ public class DeletedTenant extends Tenant {
private final Instant deletedAt;
public DeletedTenant(TenantName name, Instant createdAt, Instant deletedAt) {
- super(name, createdAt, LastLoginInfo.EMPTY, Optional.empty(), Instant.EPOCH);
+ super(name, createdAt, LastLoginInfo.EMPTY, Optional.empty(), Instant.EPOCH, List.of());
this.deletedAt = Objects.requireNonNull(deletedAt, "deletedAt must be non-null");
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
index a4500991bf2..8b1c6b3ebde 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
@@ -5,6 +5,7 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import java.time.Instant;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -20,13 +21,15 @@ public abstract class Tenant {
private final LastLoginInfo lastLoginInfo;
private final Optional<Contact> contact;
private final Instant tenantRolesLastMaintained;
+ private final List<CloudAccountInfo> cloudAccounts;
- Tenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Contact> contact, Instant tenantRolesLastMaintained) {
+ Tenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Contact> contact, Instant tenantRolesLastMaintained, List<CloudAccountInfo> cloudAccounts) {
this.name = name;
this.createdAt = createdAt;
this.lastLoginInfo = lastLoginInfo;
this.contact = contact;
this.tenantRolesLastMaintained = tenantRolesLastMaintained;
+ this.cloudAccounts = cloudAccounts;
}
/** Name of this tenant */
@@ -53,6 +56,10 @@ public abstract class Tenant {
return tenantRolesLastMaintained;
}
+ public List<CloudAccountInfo> cloudAccounts() {
+ return cloudAccounts;
+ }
+
public abstract Type type();
@Override
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
index 0b54152d058..6d7dc3a179e 100644
--- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
@@ -246,11 +246,21 @@ public class SystemFlagsDataArchiveTest {
"conditions": [
{
"type": "whitelist",
- "dimension": "application",
+ "dimension": "instance",
"values": [ "f:o:o" ]
}
],
"value": true
+ },
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "application",
+ "values": [ "f:o" ]
+ }
+ ],
+ "value": true
}
]
}""",
@@ -288,11 +298,21 @@ public class SystemFlagsDataArchiveTest {
{
"comment": "bar",
"type": "whitelist",
- "dimension": "application",
+ "dimension": "instance",
"values": [ "f:o:o" ]
}
],
"value": true
+ },
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "application",
+ "values": [ "f:o" ]
+ }
+ ],
+ "value": true
}
]
}""")));
@@ -308,8 +328,7 @@ public class SystemFlagsDataArchiveTest {
@Test
void normalize_json_succeed_on_valid_values() {
- addFile(Condition.Type.WHITELIST, "application", "a:b:c");
-// addFile(Condition.Type.WHITELIST, "instance", "a:b:c");
+ addFile(Condition.Type.WHITELIST, "application", "a:b");
addFile(Condition.Type.WHITELIST, "cloud", "yahoo");
addFile(Condition.Type.WHITELIST, "cloud", "aws");
addFile(Condition.Type.WHITELIST, "cloud", "gcp");
@@ -322,6 +341,7 @@ public class SystemFlagsDataArchiveTest {
addFile(Condition.Type.WHITELIST, "environment", "staging");
addFile(Condition.Type.WHITELIST, "environment", "test");
addFile(Condition.Type.WHITELIST, "hostname", "2080046-v6-11.ostk.bm2.prod.gq1.yahoo.com");
+ addFile(Condition.Type.WHITELIST, "instance", "a:b:c");
addFile(Condition.Type.WHITELIST, "node-type", "tenant");
addFile(Condition.Type.WHITELIST, "node-type", "host");
addFile(Condition.Type.WHITELIST, "node-type", "config");
@@ -363,12 +383,13 @@ public class SystemFlagsDataArchiveTest {
@Test
void normalize_json_fail_on_invalid_values() {
- failAddFile(Condition.Type.WHITELIST, "application", "a.b.c", "In file flags/temporary/foo/default.json: Invalid application 'a.b.c' in whitelist condition: Application ids must be on the form tenant:application:instance, but was a.b.c");
+ failAddFile(Condition.Type.WHITELIST, "application", "a.b", "In file flags/temporary/foo/default.json: Invalid application 'a.b' in whitelist condition: Applications must be on the form tenant:application, but was a.b");
failAddFile(Condition.Type.WHITELIST, "cloud", "foo", "In file flags/temporary/foo/default.json: Unknown cloud: foo");
// cluster-id: any String is valid
failAddFile(Condition.Type.WHITELIST, "cluster-type", "foo", "In file flags/temporary/foo/default.json: Invalid cluster-type 'foo' in whitelist condition: Illegal cluster type 'foo'");
failAddFile(Condition.Type.WHITELIST, "console-user-email", "not-valid-email-address", "In file flags/temporary/foo/default.json: Invalid email address: not-valid-email-address");
failAddFile(Condition.Type.WHITELIST, "environment", "foo", "In file flags/temporary/foo/default.json: Invalid environment 'foo' in whitelist condition: 'foo' is not a valid environment identifier");
+ failAddFile(Condition.Type.WHITELIST, "instance", "a.b.c", "In file flags/temporary/foo/default.json: Invalid instance 'a.b.c' in whitelist condition: Application ids must be on the form tenant:application:instance, but was a.b.c");
failAddFile(Condition.Type.WHITELIST, "hostname", "not:a:hostname", "In file flags/temporary/foo/default.json: Invalid hostname 'not:a:hostname' in whitelist condition: hostname must match '(([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])\\.?', but got: 'not:a:hostname'");
failAddFile(Condition.Type.WHITELIST, "node-type", "footype", "In file flags/temporary/foo/default.json: Invalid node-type 'footype' in whitelist condition: No enum constant com.yahoo.config.provision.NodeType.footype");
failAddFile(Condition.Type.WHITELIST, "system", "bar", "In file flags/temporary/foo/default.json: Invalid system 'bar' in whitelist condition: 'bar' is not a valid system");
diff --git a/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-east-3.json b/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-east-3.json
index b79e0913c22..c4dca9aa2e1 100644
--- a/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-east-3.json
+++ b/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-east-3.json
@@ -5,7 +5,7 @@
"conditions": [
{
"type": "whitelist",
- "dimension": "application",
+ "dimension": "instance",
"values": ["a:b:c"]
}
]
diff --git a/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-west-1.json b/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-west-1.json
index 75cffdea009..283b09d5c0b 100644
--- a/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-west-1.json
+++ b/controller-api/src/test/resources/system-flags-with-null-value/flags/my-test-flag/main.prod.us-west-1.json
@@ -5,7 +5,7 @@
"conditions": [
{
"type": "whitelist",
- "dimension": "application",
+ "dimension": "instance",
"values": ["a:b:c"]
}
],
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
index 6ec732a3815..7d19acfce80 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
@@ -43,12 +44,14 @@ public abstract class LockedTenant {
final Instant createdAt;
final LastLoginInfo lastLoginInfo;
final Instant tenantRolesLastMaintained;
+ final List<CloudAccountInfo> cloudAccounts;
- private LockedTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained) {
+ private LockedTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained, List<CloudAccountInfo> cloudAccounts) {
this.name = requireNonNull(name);
this.createdAt = requireNonNull(createdAt);
this.lastLoginInfo = requireNonNull(lastLoginInfo);
this.tenantRolesLastMaintained = requireNonNull(tenantRolesLastMaintained);
+ this.cloudAccounts = requireNonNull(cloudAccounts);
}
static LockedTenant of(Tenant tenant, Mutex lock) {
@@ -66,6 +69,8 @@ public abstract class LockedTenant {
public abstract LockedTenant with(Instant tenantRolesLastMaintained);
+ public abstract LockedTenant withCloudAccounts(List<CloudAccountInfo> cloudAccounts);
+
public Deleted deleted(Instant deletedAt) {
return new Deleted(new DeletedTenant(name, createdAt, deletedAt));
}
@@ -85,8 +90,8 @@ public abstract class LockedTenant {
private final Optional<Contact> contact;
private Athenz(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId,
- Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained) {
- super(name, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo, Instant tenantRolesLastMaintained, List<CloudAccountInfo> cloudAccounts) {
+ super(name, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
this.domain = domain;
this.property = property;
this.propertyId = propertyId;
@@ -94,38 +99,43 @@ public abstract class LockedTenant {
}
private Athenz(AthenzTenant tenant) {
- this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.tenantRolesLastMaintained());
+ this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.tenantRolesLastMaintained(), tenant.cloudAccounts());
}
@Override
public AthenzTenant get() {
- return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
public Athenz with(AthenzDomain domain) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
public Athenz with(Property property) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
public Athenz with(PropertyId propertyId) {
- return new Athenz(name, domain, property, Optional.of(propertyId), contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, Optional.of(propertyId), contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
public Athenz with(Contact contact) {
- return new Athenz(name, domain, property, propertyId, Optional.of(contact), createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, propertyId, Optional.of(contact), createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
@Override
public LockedTenant with(Instant tenantRolesLastMaintained) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
+ }
+
+ @Override
+ public LockedTenant withCloudAccounts(List<CloudAccountInfo> cloudAccounts) {
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
}
}
@@ -146,8 +156,8 @@ public abstract class LockedTenant {
BiMap<PublicKey, SimplePrincipal> developerKeys, TenantInfo info,
List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess,
Optional<Instant> invalidateUserSessionsBefore, Instant tenantRolesLastMaintained,
- Optional<BillingReference> billingReference) {
- super(name, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ List<CloudAccountInfo> cloudAccounts, Optional<BillingReference> billingReference) {
+ super(name, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
this.info = info;
@@ -158,12 +168,12 @@ public abstract class LockedTenant {
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess(), tenant.invalidateUserSessionsBefore(), tenant.tenantRolesLastMaintained(), tenant.billingReference());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess(), tenant.invalidateUserSessionsBefore(), tenant.tenantRolesLastMaintained(), tenant.cloudAccounts(), tenant.billingReference());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -174,51 +184,56 @@ public abstract class LockedTenant {
if (keys.inverse().containsKey(simplePrincipal))
throw new IllegalArgumentException(principal + " is already associated with key " + KeyUtils.toPem(keys.inverse().get(simplePrincipal)));
keys.put(key, simplePrincipal);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withoutDeveloperKey(PublicKey key) {
BiMap<PublicKey, SimplePrincipal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.add(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withoutSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.remove(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withArchiveAccess(ArchiveAccess archiveAccess) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore,tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore,tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud withInvalidateUserSessionsBefore(Instant invalidateUserSessionsBefore) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, Optional.of(invalidateUserSessionsBefore), tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, Optional.of(invalidateUserSessionsBefore), tenantRolesLastMaintained, cloudAccounts, billingReference);
}
@Override
public LockedTenant with(Instant tenantRolesLastMaintained) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ }
+
+ @Override
+ public LockedTenant withCloudAccounts(List<CloudAccountInfo> cloudAccounts) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
}
public Cloud with(BillingReference billingReference) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, Optional.of(billingReference));
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, Optional.of(billingReference));
}
}
@@ -229,7 +244,7 @@ public abstract class LockedTenant {
private final Instant deletedAt;
private Deleted(DeletedTenant tenant) {
- super(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), Instant.EPOCH);
+ super(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), Instant.EPOCH, List.of());
this.deletedAt = tenant.deletedAt();
}
@@ -247,6 +262,11 @@ public abstract class LockedTenant {
public LockedTenant with(Instant tenantRolesLastMaintained) {
return this;
}
+
+ @Override
+ public LockedTenant withCloudAccounts(List<CloudAccountInfo> cloudAccounts) {
+ return this;
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
index 091836a1eea..b1ffce65852 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
@@ -82,7 +82,8 @@ public class RoutingController {
private final Controller controller;
private final RoutingPolicies routingPolicies;
private final RotationRepository rotationRepository;
- private final BooleanFlag randomizedEndpoints;
+ private final BooleanFlag generatedEndpoints;
+ private final BooleanFlag legacyEndpoints;
public RoutingController(Controller controller, RotationsConfig rotationsConfig) {
this.controller = Objects.requireNonNull(controller, "controller must be non-null");
@@ -90,7 +91,8 @@ public class RoutingController {
this.rotationRepository = new RotationRepository(Objects.requireNonNull(rotationsConfig, "rotationsConfig must be non-null"),
controller.applications(),
controller.curator());
- this.randomizedEndpoints = Flags.RANDOMIZED_ENDPOINT_NAMES.bindTo(controller.flagSource());
+ this.generatedEndpoints = Flags.RANDOMIZED_ENDPOINT_NAMES.bindTo(controller.flagSource());
+ this.legacyEndpoints = Flags.LEGACY_ENDPOINTS.bindTo(controller.flagSource());
}
/** Create a routing context for given deployment */
@@ -228,13 +230,14 @@ public class RoutingController {
.in(controller.system()));
// Only a single region endpoint is needed, not one per auth method
if (isProduction && generatedEndpoint.authMethod() == AuthMethod.mtls) {
- endpoints.add(regionEndpoint.generatedFrom(generatedEndpoint)
+ GeneratedEndpoint weightedGeneratedEndpoint = generatedEndpoint.withClusterPart(weightedClusterPart(cluster, deployment));
+ endpoints.add(regionEndpoint.generatedFrom(weightedGeneratedEndpoint)
.authMethod(AuthMethod.none)
.in(controller.system()));
}
}
}
- return EndpointList.copyOf(endpoints);
+ return filterEndpoints(deployment.applicationId(), EndpointList.copyOf(endpoints));
}
/** Read routing policies and return zone- and region-scoped endpoints for given deployment */
@@ -268,7 +271,7 @@ public class RoutingController {
endpoints.add(builder.generatedFrom(ge).authMethod(ge.authMethod()).in(controller.system()));
}
}
- return EndpointList.copyOf(endpoints);
+ return filterEndpoints(routingId.instance(), EndpointList.copyOf(endpoints));
}
/** Returns application endpoints pointing to given deployments */
@@ -424,6 +427,13 @@ public class RoutingController {
Optional.of(application.id())));
}
+ private EndpointList filterEndpoints(ApplicationId instance, EndpointList endpoints) {
+ if (generatedEndpointsEnabled(instance) && !legacyEndpointsEnabled(instance)) {
+ return endpoints.generated();
+ }
+ return endpoints;
+ }
+
private void registerRotationEndpointsInDns(PreparedEndpoints prepared) {
TenantAndApplicationId owner = TenantAndApplicationId.from(prepared.deployment().applicationId());
EndpointList globalEndpoints = prepared.endpoints().scope(Scope.global);
@@ -476,6 +486,22 @@ public class RoutingController {
.toList();
}
+ /** Generate the cluster part of a {@link GeneratedEndpoint} for use in a {@link Endpoint.Scope#weighted} endpoint */
+ private String weightedClusterPart(ClusterSpec.Id cluster, DeploymentId deployment) {
+ // This ID must be common for a given cluster in all deployments within the same cloud-native region
+ String cloudNativeRegion = controller.zoneRegistry().zones().all().get(deployment.zoneId()).get().getCloudNativeRegionName();
+ HashCode hash = Hashing.sha256().newHasher()
+ .putString(cluster.value(), StandardCharsets.UTF_8)
+ .putString(":", StandardCharsets.UTF_8)
+ .putString(cloudNativeRegion, StandardCharsets.UTF_8)
+ .putString(":", StandardCharsets.UTF_8)
+ .putString(deployment.applicationId().serializedForm(), StandardCharsets.UTF_8)
+ .hash();
+ String alphabet = "abcdef";
+ char letter = alphabet.charAt(Math.abs(hash.asInt()) % alphabet.length());
+ return letter + hash.toString().substring(0, 7);
+ }
+
/** Returns existing generated endpoints, grouped by their {@link Scope#multiDeployment()} endpoint */
private Map<EndpointId, GeneratedEndpointList> readDeclaredGeneratedEndpoints(TenantAndApplicationId application) {
Map<EndpointId, GeneratedEndpointList> endpoints = new HashMap<>();
@@ -525,7 +551,17 @@ public class RoutingController {
}
public boolean generatedEndpointsEnabled(ApplicationId instance) {
- return randomizedEndpoints.with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm()).value();
+ return generatedEndpoints.with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm())
+ .with(FetchVector.Dimension.TENANT_ID, instance.tenant().value())
+ .with(FetchVector.Dimension.APPLICATION_ID, TenantAndApplicationId.from(instance).serialized())
+ .value();
+ }
+
+ public boolean legacyEndpointsEnabled(ApplicationId instance) {
+ return legacyEndpoints.with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm())
+ .with(FetchVector.Dimension.TENANT_ID, instance.tenant().value())
+ .with(FetchVector.Dimension.APPLICATION_ID, TenantAndApplicationId.from(instance).serialized())
+ .value();
}
private static void requireGeneratedEndpoints(GeneratedEndpointList generatedEndpoints, boolean declared) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index bf2f2ab90eb..d11540b28dd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.security.AccessControl;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.security.TenantSpec;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
@@ -165,6 +166,14 @@ public class TenantController {
}
}
+ public void updateCloudAccounts(TenantName tenantName, List<CloudAccountInfo> cloudAccounts) {
+ try (Mutex lock = lock(tenantName)) {
+ var tenant = require(tenantName);
+ if (tenant.cloudAccounts().equals(cloudAccounts)) return; // no change
+ curator.writeTenant(LockedTenant.of(tenant, lock).withCloudAccounts(cloudAccounts).get());
+ }
+ }
+
/** Deletes the given tenant. */
public void delete(TenantName tenant, Optional<Credentials> credentials, boolean forget) {
try (Mutex lock = lock(tenant)) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
index 8db4492356a..28f9963f24c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
@@ -38,6 +38,11 @@ public record GeneratedEndpoint(String clusterPart, String applicationPart, Auth
return !declared();
}
+ /** Returns a copy of this with cluster part set to given value */
+ public GeneratedEndpoint withClusterPart(String clusterPart) {
+ return new GeneratedEndpoint(clusterPart, applicationPart, authMethod, endpoint);
+ }
+
/** Create a new endpoint part, using random as a source of randomness */
public static String createPart(RandomGenerator random) {
String alphabet = "abcdef0123456789";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
index e01da00a27e..33af58a9790 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
@@ -11,6 +11,7 @@ import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -57,6 +58,7 @@ public class EndpointCertificates {
private final EndpointCertificateValidator certificateValidator;
private final BooleanFlag useAlternateCertProvider;
private final StringFlag endpointCertificateAlgo;
+ private final BooleanFlag assignLegacyNames;
private final static Duration GCP_CERTIFICATE_EXPIRY_TIME = Duration.ofDays(100); // 100 days, 10 more than notAfter time
public EndpointCertificates(Controller controller, EndpointCertificateProvider certificateProvider,
@@ -64,6 +66,7 @@ public class EndpointCertificates {
this.controller = controller;
this.useAlternateCertProvider = PermanentFlags.USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER.bindTo(controller.flagSource());
this.endpointCertificateAlgo = PermanentFlags.ENDPOINT_CERTIFICATE_ALGORITHM.bindTo(controller.flagSource());
+ this.assignLegacyNames = Flags.LEGACY_ENDPOINTS.bindTo(controller.flagSource());
this.curator = controller.curator();
this.clock = controller.clock();
this.certificateProvider = certificateProvider;
@@ -140,10 +143,11 @@ public class EndpointCertificates {
}
try (NestedTransaction transaction = new NestedTransaction()) {
curator.removeUnassignedCertificate(candidate.get(), transaction);
- curator.writeAssignedCertificate(new AssignedCertificate(application, instanceName, candidate.get().certificate()),
+ EndpointCertificate certificate = candidate.get().certificate().withLastRequested(clock.instant().getEpochSecond());
+ curator.writeAssignedCertificate(new AssignedCertificate(application, instanceName, certificate),
transaction);
transaction.commit();
- return candidate.get().certificate();
+ return certificate;
}
}
}
@@ -174,9 +178,12 @@ public class EndpointCertificates {
}
// Re-provision certificate if it is missing SANs for the zone we are deploying to
- // Skip this validation for now if the cert has a randomized id
+ // Skip this validation for now if the cert has a randomized id and should not provision legacy names
Optional<EndpointCertificate> currentCertificate = assignedCertificate.map(AssignedCertificate::certificate);
- var requiredSansForZone = currentCertificate.get().randomizedId().isEmpty() ?
+ boolean legacyNames = assignLegacyNames.with(FetchVector.Dimension.INSTANCE_ID, instance.id().serializedForm())
+ .with(FetchVector.Dimension.APPLICATION_ID, instance.id().toSerializedFormWithoutInstance()).value();
+
+ var requiredSansForZone = legacyNames || currentCertificate.get().randomizedId().isEmpty() ?
controller.routing().certificateDnsNames(deployment, deploymentSpec) :
List.<String>of();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index e247d6baa09..1b40781fe0f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -276,7 +276,7 @@ public class DeploymentTrigger {
List<RetriggerEntry> retriggerEntries = controller.curator().readRetriggerEntries();
List<RetriggerEntry> newList = new ArrayList<>(retriggerEntries);
RetriggerEntry requiredEntry = new RetriggerEntry(new JobId(deployment.applicationId(), jobType), run.id().number() + 1);
- if(newList.stream().noneMatch(entry -> entry.jobId().equals(requiredEntry.jobId()) && entry.requiredRun()>=requiredEntry.requiredRun())) {
+ if (newList.stream().noneMatch(entry -> entry.jobId().equals(requiredEntry.jobId()) && entry.requiredRun() >= requiredEntry.requiredRun())) {
newList.add(requiredEntry);
}
newList = newList.stream()
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 919facee0c1..11c47d8f481 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -23,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateException;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.DeploymentResult;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
@@ -62,7 +63,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -362,21 +362,24 @@ public class InternalStepRunner implements StepRunner {
Version platform = setTheStage ? versions.sourcePlatform().orElse(versions.targetPlatform()) : versions.targetPlatform();
Run run = controller.jobController().run(id);
- Optional<ServiceConvergence> services = controller.serviceRegistry().configServer().serviceConvergence(new DeploymentId(id.application(), id.type().zone()),
- Optional.of(platform));
+ // In manually deployed zones it is allowed for some model versions not being built (e.g due to incompatibility)
+ // but deployment still succeeding, so we cannot use version when checking for config convergence
+ Optional<Version> platformVersion = id.type().environment().isManuallyDeployed() ? Optional.empty() : Optional.of(platform);
+ Optional<ServiceConvergence> services = configServer().serviceConvergence(new DeploymentId(id.application(), id.type().zone()),
+ platformVersion);
if (services.isEmpty()) {
logger.log("Config status not currently available -- will retry.");
return Optional.empty();
}
- List<Node> nodes = controller.serviceRegistry().configServer().nodeRepository().list(id.type().zone(),
- NodeFilter.all()
- .applications(id.application())
- .states(active));
+ List<Node> nodes = configServer().nodeRepository().list(id.type().zone(),
+ NodeFilter.all()
+ .applications(id.application())
+ .states(active));
Set<HostName> parentHostnames = nodes.stream().map(node -> node.parentHostname().get()).collect(toSet());
- List<Node> parents = controller.serviceRegistry().configServer().nodeRepository().list(id.type().zone(),
- NodeFilter.all()
- .hostnames(parentHostnames));
+ List<Node> parents = configServer().nodeRepository().list(id.type().zone(),
+ NodeFilter.all()
+ .hostnames(parentHostnames));
boolean firstTick = run.convergenceSummary().isEmpty();
NodeList nodeList = NodeList.of(nodes, parents, services.get());
ConvergenceSummary summary = nodeList.summary();
@@ -496,8 +499,8 @@ public class InternalStepRunner implements StepRunner {
ZoneId zone = id.type().zone();
ApplicationId testerId = id.tester().id();
- Optional<ServiceConvergence> services = controller.serviceRegistry().configServer().serviceConvergence(new DeploymentId(testerId, zone),
- Optional.of(platform));
+ Optional<ServiceConvergence> services = configServer().serviceConvergence(new DeploymentId(testerId, zone),
+ Optional.of(platform));
if (services.isEmpty()) {
if (run.stepInfo(installTester).get().startTime().get().isBefore(controller.clock().instant().minus(Duration.ofMinutes(30)))) {
logger.log(WARNING, "Config status not available after 30 minutes; giving up!");
@@ -508,14 +511,14 @@ public class InternalStepRunner implements StepRunner {
return Optional.empty();
}
}
- List<Node> nodes = controller.serviceRegistry().configServer().nodeRepository().list(zone,
- NodeFilter.all()
- .applications(testerId)
- .states(active, reserved));
+ List<Node> nodes = configServer().nodeRepository().list(zone,
+ NodeFilter.all()
+ .applications(testerId)
+ .states(active, reserved));
Set<HostName> parentHostnames = nodes.stream().map(node -> node.parentHostname().get()).collect(toSet());
- List<Node> parents = controller.serviceRegistry().configServer().nodeRepository().list(zone,
- NodeFilter.all()
- .hostnames(parentHostnames));
+ List<Node> parents = configServer().nodeRepository().list(zone,
+ NodeFilter.all()
+ .hostnames(parentHostnames));
NodeList nodeList = NodeList.of(nodes, parents, services.get());
logger.log(nodeList.asList().stream()
.flatMap(node -> nodeDetails(node, false))
@@ -534,6 +537,8 @@ public class InternalStepRunner implements StepRunner {
return Optional.empty();
}
+ private ConfigServer configServer() { return controller.serviceRegistry().configServer(); }
+
/** Returns true iff all containers in the tester deployment give 100 consecutive 200 OK responses on /status.html. */
private boolean testerContainersAreUp(ApplicationId id, ZoneId zoneId, DualLogger logger) {
DeploymentId deploymentId = new DeploymentId(id, zoneId);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
index d10e38fd990..e7ec6675a82 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
@@ -2,23 +2,76 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.LockedTenant;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingReporter;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Duration;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
public class BillingReportMaintainer extends ControllerMaintainer {
private final BillingReporter reporter;
+ private final BillingController billing;
+ private final PlanRegistry plans;
public BillingReportMaintainer(Controller controller, Duration interval) {
super(controller, interval, null, Set.of(SystemName.PublicCd));
this.reporter = controller.serviceRegistry().billingReporter();
+ this.billing = controller.serviceRegistry().billingController();
+ this.plans = controller.serviceRegistry().planRegistry();
}
@Override
protected double maintain() {
- return this.reporter.maintain();
+ maintainTenants();
+ return 0.0;
+ }
+
+ private void maintainTenants() {
+ var tenants = cloudTenants();
+ var tenantNames = List.copyOf(tenants.keySet());
+ var billableTenants = billableTenants(tenantNames);
+
+ billableTenants.forEach(tenant -> {
+ controller().tenants().lockIfPresent(tenant, LockedTenant.Cloud.class, locked -> {
+ var ref = reporter.maintainTenant(locked.get());
+ if (locked.get().billingReference().isEmpty() || ! locked.get().billingReference().get().equals(ref)) {
+ controller().tenants().store(locked.with(ref));
+ }
+ });
+ });
+ }
+
+ private Map<TenantName, CloudTenant> cloudTenants() {
+ return controller().tenants().asList()
+ .stream()
+ .filter(CloudTenant.class::isInstance)
+ .map(CloudTenant.class::cast)
+ .collect(Collectors.toMap(
+ Tenant::name,
+ Function.identity()));
+ }
+
+ private List<Plan> billablePlans() {
+ return plans.all().stream()
+ .filter(Plan::isBilled)
+ .toList();
+ }
+
+ private List<TenantName> billableTenants(List<TenantName> tenants) {
+ return billablePlans().stream()
+ .flatMap(p -> billing.tenantsWithPlan(tenants, p.id()).stream())
+ .toList();
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
index 70eeb2b9f6c..ed383175cc3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
@@ -69,7 +69,7 @@ public class CertificatePoolMaintainer extends ControllerMaintainer {
// Create metric for available certificates in the pool as a fraction of configured size
int poolSize = certPoolSize.value();
long available = certificatePool.stream().filter(c -> c.state() == UnassignedCertificate.State.ready).count();
- metric.set(ControllerMetrics.CERTIFICATE_POOL_AVAILABLE.baseName(), (poolSize > 0 ? (available/poolSize) : 1.0), metric.createContext(Map.of()));
+ metric.set(ControllerMetrics.CERTIFICATE_POOL_AVAILABLE.baseName(), (poolSize > 0 ? ((double)available/poolSize) : 1.0), metric.createContext(Map.of()));
if (certificatePool.size() < poolSize) {
provisionRandomizedCertificate();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java
new file mode 100644
index 00000000000..f0fc8985bdf
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java
@@ -0,0 +1,55 @@
+package com.yahoo.vespa.hosted.controller.maintenance;
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
+import com.yahoo.vespa.hosted.controller.tenant.Tenant;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.WARNING;
+
+/**
+ * Verifies the cloud accounts that may be used by a given user have applied the enclave template
+ * and extracts the version of the applied template.
+ *
+ * All maintainers that operate on external cloud accounts should use the list on the Tenant instance
+ * maintained by this class rather than the cloud-accounts feature flag.
+ *
+ * The template version can be used to determine if new features can be enabled for the cloud account.
+ *
+ * @author freva
+ */
+public class CloudAccountVerifier extends ControllerMaintainer {
+
+ private static final Logger logger = Logger.getLogger(CloudAccountVerifier.class.getName());
+
+ CloudAccountVerifier(Controller controller, Duration interval) {
+ super(controller, interval, null, Set.of(SystemName.PublicCd, SystemName.Public));
+ }
+
+ @Override
+ protected double maintain() {
+ int attempts = 0, failures = 0;
+ for (Tenant tenant : controller().tenants().asList()) {
+ try {
+ attempts++;
+ List<CloudAccountInfo> cloudAccountInfos = controller().applications().accountsOf(tenant.name()).stream()
+ .flatMap(account -> controller().serviceRegistry()
+ .archiveService()
+ .getEnclaveTemplateVersion(account)
+ .map(version -> new CloudAccountInfo(account, version))
+ .stream())
+ .toList();
+ controller().tenants().updateCloudAccounts(tenant.name(), cloudAccountInfos);
+ } catch (RuntimeException e) {
+ logger.log(WARNING, "Failed to verify cloud accounts for tenant " + tenant.name(), e);
+ failures++;
+ }
+ }
+ return asSuccessFactorDeviation(attempts, failures);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
index f9c93a87c44..f6da3609fbb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
@@ -29,8 +29,8 @@ public class ContactInformationMaintainer extends ControllerMaintainer {
private final ContactRetriever contactRetriever;
- public ContactInformationMaintainer(Controller controller, Duration interval) {
- super(controller, interval, null, SystemName.allOf(Predicate.not(SystemName::isPublic)));
+ public ContactInformationMaintainer(Controller controller, Duration interval, Double successFactorBaseline) {
+ super(controller, interval, null, SystemName.allOf(Predicate.not(SystemName::isPublic)), successFactorBaseline);
this.contactRetriever = controller.serviceRegistry().contactRetriever();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index 6fae732df0a..7afa10ab8d5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -59,7 +59,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new SystemUpgrader(controller, intervals.systemUpgrader));
maintainers.add(new JobRunner(controller, intervals.jobRunner));
maintainers.add(new OsVersionStatusUpdater(controller, intervals.osVersionStatusUpdater));
- maintainers.add(new ContactInformationMaintainer(controller, intervals.contactInformationMaintainer));
+ maintainers.add(new ContactInformationMaintainer(controller, intervals.contactInformationMaintainer, successFactorBaseline.contactInformationMaintainerBaseline));
maintainers.add(new NameServiceDispatcher(controller, intervals.nameServiceDispatcher));
maintainers.add(new CostReportMaintainer(controller, intervals.costReportMaintainer, controller.serviceRegistry().costReportConsumer()));
maintainers.add(new ResourceMeterMaintainer(controller, intervals.resourceMeterMaintainer, metric, controller.serviceRegistry().resourceDatabase()));
@@ -85,6 +85,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new EnclaveAccessMaintainer(controller, intervals.defaultInterval));
maintainers.add(new CertificatePoolMaintainer(controller, metric, intervals.certificatePoolMaintainer));
maintainers.add(new BillingReportMaintainer(controller, intervals.billingReportMaintainer));
+ maintainers.add(new CloudAccountVerifier(controller, intervals.cloudAccountVerifier));
}
public Upgrader upgrader() { return upgrader; }
@@ -147,6 +148,7 @@ public class ControllerMaintenance extends AbstractComponent {
private final Duration meteringMonitorMaintainer;
private final Duration certificatePoolMaintainer;
private final Duration billingReportMaintainer;
+ private final Duration cloudAccountVerifier;
public Intervals(SystemName system) {
this.system = Objects.requireNonNull(system);
@@ -184,6 +186,7 @@ public class ControllerMaintenance extends AbstractComponent {
this.meteringMonitorMaintainer = duration(30, MINUTES);
this.certificatePoolMaintainer = duration(15, MINUTES);
this.billingReportMaintainer = duration(60, MINUTES);
+ this.cloudAccountVerifier = duration(10, MINUTES);
}
private Duration duration(long amount, TemporalUnit unit) {
@@ -201,12 +204,14 @@ public class ControllerMaintenance extends AbstractComponent {
private final Double deploymentMetricsMaintainerBaseline;
private final Double trafficFractionUpdater;
private final Double deploymentInfoMaintainerBaseline;
+ private final Double contactInformationMaintainerBaseline;
public SuccessFactorBaseline(SystemName system) {
Objects.requireNonNull(system);
this.deploymentMetricsMaintainerBaseline = 0.90;
this.trafficFractionUpdater = system.isCd() ? 0.5 : 0.65;
this.deploymentInfoMaintainerBaseline = system.isCd() ? 0.5 : 0.95;
+ this.contactInformationMaintainerBaseline = 0.95;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
index 5218da91c46..6c1c4daa1bb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
@@ -33,7 +33,7 @@ public class EnclaveAccessMaintainer extends ControllerMaintainer {
private Set<CloudAccount> externalAccounts() {
Set<CloudAccount> accounts = new HashSet<>();
for (Tenant tenant : controller().tenants().asList())
- accounts.addAll(controller().applications().accountsOf(tenant.name()));
+ tenant.cloudAccounts().forEach(accountInfo -> accounts.add(accountInfo.cloudAccount()));
return accounts;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
index c90fcb81c71..805bf3d7ada 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
@@ -67,7 +67,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
private final EndpointSecretManager endpointSecretManager;
private final EndpointCertificateProvider endpointCertificateProvider;
final Comparator<EligibleJob> oldestFirst = Comparator.comparing(e -> e.deployment.at());
- final BooleanFlag assignRandomizedId;
private final StringFlag endpointCertificateAlgo;
private final BooleanFlag useAlternateCertProvider;
private final IntFlag assignRandomizedIdRate;
@@ -81,7 +80,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
this.endpointSecretManager = controller.serviceRegistry().secretManager();
this.curator = controller().curator();
this.endpointCertificateProvider = controller.serviceRegistry().endpointCertificateProvider();
- this.assignRandomizedId = Flags.ASSIGN_RANDOMIZED_ID.bindTo(controller.flagSource());
this.useAlternateCertProvider = PermanentFlags.USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER.bindTo(controller.flagSource());
this.endpointCertificateAlgo = PermanentFlags.ENDPOINT_CERTIFICATE_ALGORITHM.bindTo(controller.flagSource());
this.assignRandomizedIdRate = Flags.ASSIGNED_RANDOMIZED_ID_RATE.bindTo(controller.flagSource());
@@ -283,7 +281,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
assignedCertificates.stream()
.filter(c -> c.instance().isPresent())
.filter(c -> c.certificate().randomizedId().isEmpty())
- .filter(c -> assignRandomizedId.with(FetchVector.Dimension.INSTANCE_ID, c.application().instance(c.instance().get()).serializedForm()).value())
.filter(c -> controller().applications().getApplication(c.application()).isPresent()) // In case application has been deleted, but certificate is pending deletion
.limit(assignRandomizedIdRate.value())
.forEach(c -> assignRandomizedId(c.application(), c.instance().get()));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index a25aa9797ba..dc9c4650191 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -7,6 +7,7 @@ import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.ClusterSpec.Id;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
@@ -602,7 +603,7 @@ public class CuratorDb {
public List<DnsChallenge> readDnsChallenges(DeploymentId id) {
return curator.getChildren(dnsChallengePath(id)).stream()
- .map(cluster -> readDnsChallenge(new ClusterId(id, ClusterSpec.Id.from(cluster))))
+ .map(cluster -> readDnsChallenge(new ClusterId(id, Id.from(cluster))))
.toList();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index e3d61c81667..760fb9b0366 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.ArrayTraverser;
@@ -20,6 +22,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
@@ -85,6 +88,9 @@ public class TenantSerializer {
private static final String invalidateUserSessionsBeforeField = "invalidateUserSessionsBefore";
private static final String tenantRolesLastMaintainedField = "tenantRolesLastMaintained";
private static final String billingReferenceField = "billingReference";
+ private static final String cloudAccountsField = "cloudAccounts";
+ private static final String accountField = "account";
+ private static final String templateVersionField = "templateVersion";
private static final String awsIdField = "awsId";
private static final String roleField = "role";
@@ -97,6 +103,7 @@ public class TenantSerializer {
tenantObject.setLong(createdAtField, tenant.createdAt().toEpochMilli());
toSlime(tenant.lastLoginInfo(), tenantObject.setObject(lastLoginInfoField));
tenantObject.setLong(tenantRolesLastMaintainedField, tenant.tenantRolesLastMaintained().toEpochMilli());
+ cloudAccountsToSlime(tenant.cloudAccounts(), tenantObject.setArray(cloudAccountsField));
switch (tenant.type()) {
case athenz: toSlime((AthenzTenant) tenant, tenantObject); break;
@@ -162,6 +169,14 @@ public class TenantSerializer {
}
}
+ private void cloudAccountsToSlime(List<CloudAccountInfo> cloudAccounts, Cursor cloudAccountsObject) {
+ cloudAccounts.forEach(cloudAccountInfo -> {
+ Cursor object = cloudAccountsObject.addObject();
+ object.setString(accountField, cloudAccountInfo.cloudAccount().account());
+ object.setString(templateVersionField, cloudAccountInfo.templateVersion().toFullString());
+ });
+ }
+
public Tenant tenantFrom(Slime slime) {
Inspector tenantObject = slime.get();
Tenant.Type type = typeOf(tenantObject.field(typeField).asString());
@@ -183,7 +198,8 @@ public class TenantSerializer {
Instant createdAt = SlimeUtils.instant(tenantObject.field(createdAtField));
LastLoginInfo lastLoginInfo = lastLoginInfoFromSlime(tenantObject.field(lastLoginInfoField));
Instant tenantRolesLastMaintained = SlimeUtils.instant(tenantObject.field(tenantRolesLastMaintainedField));
- return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained);
+ List<CloudAccountInfo> cloudAccountInfos = cloudAccountsFromSlime(tenantObject.field(cloudAccountsField));
+ return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccountInfos);
}
private CloudTenant cloudTenantFrom(Inspector tenantObject) {
@@ -197,8 +213,9 @@ public class TenantSerializer {
ArchiveAccess archiveAccess = archiveAccessFromSlime(tenantObject);
Optional<Instant> invalidateUserSessionsBefore = SlimeUtils.optionalInstant(tenantObject.field(invalidateUserSessionsBeforeField));
Instant tenantRolesLastMaintained = SlimeUtils.instant(tenantObject.field(tenantRolesLastMaintainedField));
+ List<CloudAccountInfo> cloudAccountInfos = cloudAccountsFromSlime(tenantObject.field(cloudAccountsField));
Optional<BillingReference> billingReference = billingReferenceFrom(tenantObject.field(billingReferenceField));
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, billingReference);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccountInfos, billingReference);
}
private DeletedTenant deletedTenantFrom(Inspector tenantObject) {
@@ -284,6 +301,14 @@ public class TenantSerializer {
return new LastLoginInfo(lastLoginByUserLevel);
}
+ private List<CloudAccountInfo> cloudAccountsFromSlime(Inspector cloudAccountsObject) {
+ return SlimeUtils.entriesStream(cloudAccountsObject)
+ .map(inspector -> new CloudAccountInfo(
+ CloudAccount.from(inspector.field(accountField).asString()),
+ Version.fromString(inspector.field(templateVersionField).asString())))
+ .toList();
+ }
+
void toSlime(TenantInfo info, Cursor parentCursor) {
if (info.isEmpty()) return;
Cursor infoCursor = parentCursor.setObject("info");
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 46c81fc073f..16d862a66ef 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
@@ -2915,6 +2915,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
}
tenantMetaDataToSlime(tenant, applications, object.setObject("metaData"));
+
+ if (!tenant.cloudAccounts().isEmpty()) {
+ Cursor cloudAccounts = object.setArray("cloudAccounts");
+ tenant.cloudAccounts().forEach(accountInfo -> {
+ Cursor accountObject = cloudAccounts.addObject();
+ accountObject.setString("cloudAccount", accountInfo.cloudAccount().value());
+ accountObject.setString("templateVersion", accountInfo.templateVersion().toFullString());
+ });
+ }
}
private void toSlime(ArchiveAccess archiveAccess, Cursor object) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
index 67dd172fd83..c5fb1afbae8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
@@ -85,6 +85,8 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
.addRoute(RestApi.route("/billing/v2/accountant/preview/tenant/{tenant}")
.get(self::previewBill)
.post(Slime.class, self::createBill))
+ .addRoute(RestApi.route("/billing/v2/accountant/bill/{invoice}/export")
+ .put(Slime.class, self::putAccountantInvoiceExport))
.addRoute(RestApi.route("/billing/v2/accountant/plans")
.get(self::plans))
.addExceptionMapper(RuntimeException.class, (c, e) -> ErrorResponses.logThrowing(c.request(), log, e))
@@ -262,6 +264,19 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
return new SlimeJsonResponse(slime);
}
+ private HttpResponse putAccountantInvoiceExport(RestApi.RequestContext ctx, Slime slime) {
+ var billId = ctx.attributes().get("invoice")
+ .map(id -> Bill.Id.of((String) id))
+ .orElseThrow(() -> new RestApiException.BadRequest("Missing bill ID"));
+
+ // TODO: try to find a way to retrieve the cloud tenant from BillingControllerImpl
+ var bill = billing.getBill(billId);
+ var cloudTenant = tenants.require(bill.tenant(), CloudTenant.class);
+
+ var exportMethod = slime.get().field("method").asString();
+ var result = billing.exportBill(bill, exportMethod, cloudTenant);
+ return new MessageResponse("Bill has been exported: " + result);
+ }
// --------- INVOICE RENDERING ----------
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
index de25161c461..a21c6548a0b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
@@ -5,6 +5,7 @@ import ai.vespa.http.DomainName;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.zone.AuthMethod;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.transaction.Mutex;
@@ -263,8 +264,14 @@ public class RoutingPolicies {
} else {
weightedEndpoints = weightedEndpoints.not().generated();
}
+ if (generated && weightedEndpoints.isEmpty()) {
+ // Ignore this policy. If an instance has a global endpoint, and is switching from non-generated to
+ // generated endpoints we cannot update global DNS record for a deployment until it has been deployed at
+ // least once (which assigns a generated endpoint).
+ continue;
+ }
if (weightedEndpoints.size() != 1) {
- throw new IllegalStateException("Expected to compute exactly one region endpoint for " + policy.id() + " with parent " + parent);
+ throw new IllegalStateException("Expected to compute exactly one region endpoint for " + policy.id() + " with parent " + parent + ", got " + weightedEndpoints);
}
Endpoint endpoint = weightedEndpoints.first().get();
RegionEndpoint regionEndpoint = endpoints.computeIfAbsent(endpoint, (k) -> new RegionEndpoint(
@@ -410,24 +417,22 @@ public class RoutingPolicies {
new Record(Record.Type.CNAME, name, RecordData.fqdn(policy.canonicalName().get().value())) :
new Record(Record.Type.A, name, RecordData.from(policy.ipAddress().orElseThrow()));
nameServiceForwarder(endpoint).createRecord(record, Priority.normal, ownerOf(deploymentId));
- setPrivateDns(endpoint, loadBalancer, deploymentId);
}
+ setPrivateDns(zoneEndpoints, loadBalancer, deploymentId);
}
- private void setPrivateDns(Endpoint endpoint, LoadBalancer loadBalancer, DeploymentId deploymentId) {
+ private void setPrivateDns(EndpointList endpoints, LoadBalancer loadBalancer, DeploymentId deploymentId) {
if (loadBalancer.service().isEmpty()) return;
- // TODO(mpolden): Why is this done? Consider creating private DNS for all auth methods
- boolean skipBasedOnAuthMethod = switch (endpoint.authMethod()) {
- case token -> true;
- case mtls -> false;
- case none -> true;
- };
- if (skipBasedOnAuthMethod) return;
+ // TODO(mpolden): Model one service for each endpoint (type), to allow private endpoints with tokens.
+ EndpointList mtlsEndpoints = endpoints.authMethod(AuthMethod.mtls);
+ if (mtlsEndpoints.isEmpty()) return;
+ Endpoint endpoint = mtlsEndpoints.generated().first().orElse(mtlsEndpoints.first().get());
if (endpoint.routingMethod() != RoutingMethod.exclusive) return; // Not supported for this routing method
controller.serviceRegistry().vpcEndpointService()
.setPrivateDns(DomainName.of(endpoint.dnsName()),
new ClusterId(deploymentId, endpoint.cluster()),
- loadBalancer.cloudAccount())
+ loadBalancer.cloudAccount(),
+ endpoint.generated().isPresent())
.ifPresent(challenge -> {
try (Mutex lock = db.lockNameServiceQueue()) {
controller.nameServiceForwarder().createTxt(challenge.name(), List.of(challenge.data()), Priority.high, ownerOf(deploymentId));
@@ -436,10 +441,18 @@ public class RoutingPolicies {
});
}
+ /** Deletes all DNS challenges, and corresponding TXT records, for the given deployment. */
+ public void removeDnsChallenges(DeploymentId deploymentId) {
+ try (Mutex lock = db.lockNameServiceQueue()) {
+ db.readDnsChallenges(deploymentId).forEach(this::removeDnsChallenge);
+ }
+ }
+
/** Returns true iff. the given deployment has no incomplete DNS challenges, or throws (and cleans up) on errors. */
public boolean processDnsChallenges(DeploymentId deploymentId) {
try (Mutex lock = db.lockNameServiceQueue()) {
List<DnsChallenge> challenges = new ArrayList<>(db.readDnsChallenges(deploymentId));
+ challenges.removeIf(challenge -> challenge.state() == ChallengeState.done);
Set<RecordName> pendingRequests = controller.curator().readNameServiceQueue().requests().stream()
.map(NameServiceRequest::name)
.collect(Collectors.toSet());
@@ -450,14 +463,8 @@ public class RoutingPolicies {
challenge = challenge.withState(ChallengeState.ready);
}
ChallengeState state = controller.serviceRegistry().vpcEndpointService().process(challenge);
- if (state == ChallengeState.done) {
- removeDnsChallenge(challenge);
- return true;
- }
- else {
- db.writeDnsChallenge(challenge.withState(state));
- return false;
- }
+ db.writeDnsChallenge(challenge.withState(state));
+ return state == ChallengeState.done;
});
return challenges.isEmpty();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
index df0226176a2..99f60735f6e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
@@ -57,6 +57,7 @@ public abstract class DeploymentRoutingContext implements RoutingContext {
/** Deactivate routing configuration for the deployment in this context, using given deployment spec */
public final void deactivate(DeploymentSpec deploymentSpec) {
routing.policies().refresh(deployment, deploymentSpec, EndpointList.EMPTY);
+ routing.policies().removeDnsChallenges(deployment);
}
/** Routing method of this context */
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
index 1cb43453918..a6d3b435dcb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
@@ -306,6 +306,9 @@ public class EndpointCertificatesTest {
fail("Expected exception as certificate is not ready");
} catch (IllegalArgumentException ignored) {}
+ // Advance clock to verify last requested time
+ clock.advance(Duration.ofDays(3));
+
// Certificate is assigned from pool instead. The previously assigned certificate will eventually be cleaned up
// by EndpointCertificateMaintainer
{ // prod
@@ -315,6 +318,7 @@ public class EndpointCertificatesTest {
assertEquals(certId, cert.get().randomizedId().get());
assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.empty()).get().certificate().randomizedId().get(), "Certificate is assigned at application-level");
assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool");
+ assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested());
}
{ // dev
@@ -325,6 +329,7 @@ public class EndpointCertificatesTest {
assertEquals(certId, cert.get().randomizedId().get());
assertEquals(certId, tester.curator().readAssignedCertificate(instance.id()).get().certificate().randomizedId().get(), "Certificate is assigned at instance-level");
assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool");
+ assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index ed5226ebc8b..0e5308fcef5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -42,6 +42,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ProxyResponse;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.QuotaUsage;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TestReport;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
@@ -103,6 +105,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
private final Map<DeploymentId, TestReport> testReport = new HashMap<>();
private final Map<DeploymentId, CloudAccount> cloudAccounts = new HashMap<>();
private final Map<DeploymentId, List<X509Certificate>> additionalCertificates = new HashMap<>();
+ private final Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokenFingerprints = new HashMap<>();
private List<SearchNodeMetrics> searchNodeMetrics;
private Version lastPrepareVersion = null;
@@ -319,6 +322,10 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
return additionalCertificates.getOrDefault(deployment, List.of());
}
+ public void setActiveTokenFingerprints(HostName hostname, Map<TokenId, List<FingerPrint>> tokens) {
+ activeTokenFingerprints.put(hostname, tokens);
+ }
+
@Override
public NodeRepositoryMock nodeRepository() {
return nodeRepository;
@@ -585,6 +592,11 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
return "{\"settings\":{\"name\":\"foo\",\"role\":\"vespa-secretstore-access\",\"awsId\":\"892075328880\",\"externalId\":\"*****\",\"region\":\"us-east-1\"},\"status\":\"ok\"}";
}
+ @Override
+ public Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokenFingerprints(DeploymentId deploymentId) {
+ return activeTokenFingerprints;
+ }
+
public static class Application {
private final ApplicationId id;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
index f2c827478c0..c6386509585 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
@@ -22,6 +22,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingControll
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClient;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClientMock;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingReporter;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingReporterMock;
import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock;
@@ -52,9 +53,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainer;
import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainerMock;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.MockChangeRequestClient;
+import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import java.time.Instant;
import java.util.Optional;
+import java.util.UUID;
/**
* A mock implementation of a {@link ServiceRegistry} for testing purposes.
@@ -316,6 +320,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
@Override
public BillingReporter billingReporter() {
- return () -> 0.0;
+ return new BillingReporterMock(clock());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java
new file mode 100644
index 00000000000..b1e00ba0746
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java
@@ -0,0 +1,46 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.maintenance;
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock;
+import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class BillingReportMaintainerTest {
+ private final ControllerTester tester = new ControllerTester(SystemName.PublicCd);
+ private final BillingReportMaintainer maintainer = new BillingReportMaintainer(tester.controller(), Duration.ofMinutes(10));
+
+ @Test
+ void only_billable_tenants_are_maintained() {
+ var t1 = tester.createTenant("t1");
+ var t2 = tester.createTenant("t2");
+
+ tester.controller().serviceRegistry().billingController().setPlan(t1, PlanRegistryMock.paidPlan.id(), false, true);
+ maintainer.maintain();
+
+ var b1 = billingReference(t1);
+ var b2 = billingReference(t2);
+
+ assertFalse(b1.isEmpty());
+ assertTrue(b2.isEmpty());
+
+ assertEquals(tester.clock().instant(), b1.orElseThrow().updated());
+ assertNotNull(b1.orElseThrow().reference());
+ }
+
+ private Optional<BillingReference> billingReference(TenantName tenantName) {
+ var t = tester.controller().tenants().require(tenantName, CloudTenant.class);
+ return t.billingReference();
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
index 88c5ae9ff06..4257261b09b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
@@ -53,12 +53,4 @@ public class CertificatePoolMaintainerTest {
assertEquals(0.0, maintainer.maintain(), 0.0000001);
assertEquals(n, tester.curator().readUnassignedCertificates().size());
}
-
- void old_unassigned_certs_are_refreshed() {
- tester.flagSource().withIntFlag(PermanentFlags.CERT_POOL_SIZE.id(), 1);
- assertNumCerts(1);
- EndpointCertificateProviderMock endpointCertificateProvider = (EndpointCertificateProviderMock) tester.controller().serviceRegistry().endpointCertificateProvider();
- var request = endpointCertificateProvider.listCertificates().get(0);
-
- }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
index f0c11c0ddbd..2c54c0c9fb6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
@@ -30,7 +30,7 @@ public class ContactInformationMaintainerTest {
@BeforeEach
public void before() {
tester = new ControllerTester();
- maintainer = new ContactInformationMaintainer(tester.controller(), Duration.ofDays(1));
+ maintainer = new ContactInformationMaintainer(tester.controller(), Duration.ofDays(1), 1.0);
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
index 5bfac2866ce..1e1079a3314 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
@@ -21,17 +21,20 @@ class EnclaveAccessMaintainerTest {
void test() {
ControllerTester tester = new ControllerTester();
MockEnclaveAccessService amis = tester.serviceRegistry().enclaveAccessService();
- EnclaveAccessMaintainer sharer = new EnclaveAccessMaintainer(tester.controller(), Duration.ofMinutes(1));
+ EnclaveAccessMaintainer sharer = new EnclaveAccessMaintainer(tester.controller(), Duration.ofHours(1));
+ CloudAccountVerifier accountVerifier = new CloudAccountVerifier(tester.controller(), Duration.ofHours(1));
assertEquals(Set.of(), amis.currentAccounts());
assertEquals(1, sharer.maintain());
assertEquals(Set.of(), amis.currentAccounts());
tester.createTenant("tanten");
+ accountVerifier.maintain();
assertEquals(1, sharer.maintain());
assertEquals(Set.of(), amis.currentAccounts());
tester.flagSource().withListFlag(PermanentFlags.CLOUD_ACCOUNTS.id(), List.of("123123123123", "321321321321"), String.class);
+ accountVerifier.maintain();
assertEquals(1, sharer.maintain());
assertEquals(Set.of(CloudAccount.from("aws:123123123123"), CloudAccount.from("aws:321321321321")), amis.currentAccounts());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
index 918a4bed6f4..cbc69e52119 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
@@ -11,7 +11,9 @@ import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateDetails;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProviderMock;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateRequest;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -24,6 +26,7 @@ import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.SecretStoreMock;
+import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Duration;
@@ -36,10 +39,13 @@ import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.devUsEast1;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.perfUsEast3;
+import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsCentral1;
+import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsEast3;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsWest1;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.stagingTest;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.systemTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -100,12 +106,15 @@ public class EndpointCertificateMaintainerTest {
assertEquals(0.0, maintainer.maintain(), 0.0000001);
var cert = tester.curator().readAssignedCertificate(appId).orElseThrow().certificate();
- tester.controller().serviceRegistry().endpointCertificateProvider().certificateDetails(cert.rootRequestId()); // cert should not be deleted, the app is deployed!
+ tester.controller().serviceRegistry().endpointCertificateProvider().certificateDetails(cert.leafRequestId().get()); // cert should not be deleted, the app is deployed!
}
@Test
void refreshed_certificate_is_discovered_and_after_four_days_deployed() {
- var appId = ApplicationId.from("tenant", "application", "default");
+ prepareCertificatePool(1);
+
+ var instanceId = ApplicationId.from("tenant", "application", "default");
+ var applicationId = TenantAndApplicationId.from(instanceId);
DeploymentTester deploymentTester = new DeploymentTester(tester);
@@ -115,22 +124,25 @@ public class EndpointCertificateMaintainerTest {
DeploymentContext deploymentContext = deploymentTester.newDeploymentContext("tenant", "application", "default");
deploymentContext.submit(applicationPackage).runJob(systemTest).runJob(stagingTest).runJob(productionUsWest1);
- var assignedCertificate = tester.curator().readAssignedCertificate(appId).orElseThrow();
+ var assignedCertificate = tester.curator().readAssignedCertificate(applicationId, Optional.empty()).orElseThrow();
// cert should not be deleted, the app is deployed!
assertEquals(0.0, maintainer.maintain(), 0.0000001);
- assertEquals(tester.curator().readAssignedCertificate(appId), Optional.of(assignedCertificate));
+ assertEquals(tester.curator().readAssignedCertificate(applicationId, Optional.empty()).map(c->c.certificate().rootRequestId()), Optional.of(assignedCertificate.certificate().rootRequestId()));
tester.controller().serviceRegistry().endpointCertificateProvider().certificateDetails(assignedCertificate.certificate().rootRequestId());
+ // TODO: Remove this line when we have removed assignment of randomized id to application certificates
+ //assignedCertificate = tester.curator().readAssignedCertificate().orElseThrow();
// This simulates a cert refresh performed 3 days later
tester.clock().advance(Duration.ofDays(3));
secretStore.setSecret(assignedCertificate.certificate().keyName(), "foo", 1);
secretStore.setSecret(assignedCertificate.certificate().certName(), "bar", 1);
- tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate(appId.toFullString(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false);
+ tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate("preprovisioned." + assignedCertificate.certificate().randomizedId().get(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false);
+
// We should now pick up the new key and cert version + uuid, but not force trigger deployment yet
assertEquals(0.0, maintainer.maintain(), 0.0000001);
deploymentContext.assertNotRunning(productionUsWest1);
- var updatedCert = tester.curator().readAssignedCertificate(appId).orElseThrow().certificate();
+ var updatedCert = tester.curator().readAssignedCertificate(applicationId, Optional.empty()).orElseThrow().certificate();
assertNotEquals(assignedCertificate.certificate().leafRequestId().orElseThrow(), updatedCert.leafRequestId().orElseThrow());
assertEquals(updatedCert.version(), assignedCertificate.certificate().version() + 1);
@@ -179,24 +191,12 @@ public class EndpointCertificateMaintainerTest {
}
@Test
- void certificates_are_not_assigned_random_id_when_flag_disabled() {
- var app = ApplicationId.from("tenant", "app", "default");
- DeploymentTester deploymentTester = new DeploymentTester(tester);
- deployToAssignCert(deploymentTester, app, List.of(systemTest, stagingTest, productionUsWest1), Optional.empty());
- assertEquals(1, tester.curator().readAssignedCertificates().size());
-
- maintainer.maintain();
- assertEquals(1, tester.curator().readAssignedCertificates().size());
- }
-
- @Test
void production_deployment_certificates_are_assigned_random_id() {
var app = ApplicationId.from("tenant", "app", "default");
DeploymentTester deploymentTester = new DeploymentTester(tester);
deployToAssignCert(deploymentTester, app, List.of(systemTest, stagingTest, productionUsWest1), Optional.empty());
assertEquals(1, tester.curator().readAssignedCertificates().size());
- ((InMemoryFlagSource)deploymentTester.controller().flagSource()).withBooleanFlag(Flags.ASSIGN_RANDOMIZED_ID.id(), true);
maintainer.maintain();
assertEquals(2, tester.curator().readAssignedCertificates().size());
@@ -223,7 +223,6 @@ public class EndpointCertificateMaintainerTest {
DeploymentTester deploymentTester = new DeploymentTester(tester);
deployToAssignCert(deploymentTester, instance1, List.of(systemTest, stagingTest,productionUsWest1),Optional.of("instance1"));
assertEquals(1, tester.curator().readAssignedCertificates().size());
- ((InMemoryFlagSource)deploymentTester.controller().flagSource()).withBooleanFlag(Flags.ASSIGN_RANDOMIZED_ID.id(), true);
maintainer.maintain();
String randomId = tester.curator().readAssignedCertificate(instance1).get().certificate().randomizedId().get();
@@ -241,7 +240,6 @@ public class EndpointCertificateMaintainerTest {
DeploymentTester deploymentTester = new DeploymentTester(tester);
deployToAssignCert(deploymentTester, devApp, List.of(devUsEast1), Optional.empty());
assertEquals(1, tester.curator().readAssignedCertificates().size());
- ((InMemoryFlagSource)deploymentTester.controller().flagSource()).withBooleanFlag(Flags.ASSIGN_RANDOMIZED_ID.id(), true);
List<String> originalRequestedSans = tester.curator().readAssignedCertificate(devApp).get().certificate().requestedDnsSans();
maintainer.maintain();
assertEquals(1, tester.curator().readAssignedCertificates().size());
@@ -254,9 +252,64 @@ public class EndpointCertificateMaintainerTest {
assertEquals(3, randomizedNames.size());
}
+ @Test
+ void deploy_to_other_manual_zone_refreshes_cert() {
+ String devSan = "*.foo.manual.tenant.us-east-1.dev.vespa.oath.cloud";
+ String perfSan = "*.foo.manual.tenant.us-east-3.perf.vespa.oath.cloud";
+
+ var devApp = ApplicationId.from("tenant", "manual", "foo");
+ DeploymentTester deploymentTester = new DeploymentTester(tester);
+ deployToAssignCert(deploymentTester, devApp, List.of(devUsEast1), Optional.empty());
+ assertEquals(1, tester.curator().readAssignedCertificates().size());
+ maintainer.maintain();
+ Optional<AssignedCertificate> devCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(devApp), Optional.of(devApp.instance()));
+ List<String> devSans = devCertificate.get().certificate().requestedDnsSans();
+ Assertions.assertThat(devSans).contains(devSan);
+ Assertions.assertThat(devSans).doesNotContain(perfSan);
+
+ // Deploy to perf and verify that the certs are refreshed
+ deployToAssignCert(deploymentTester, devApp, List.of(perfUsEast3), Optional.empty());
+ Optional<AssignedCertificate> devAndPerfCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(devApp), Optional.of(devApp.instance()));
+ List<String> devAndPerfSans = devAndPerfCertificate.get().certificate().requestedDnsSans();
+
+ assertNotEquals(devSans, devAndPerfSans);
+ Assertions.assertThat(devAndPerfSans).contains(devSan);
+ Assertions.assertThat(devAndPerfSans).contains(perfSan);
+ }
+
+ @Test
+ void deploy_to_other_prod_zone_refreshes_cert() {
+ String westSan = "*.prod.tenant.us-west-1.vespa.oath.cloud";
+ String centralSan = "*.prod.tenant.us-central-1.vespa.oath.cloud";
+
+ var prodApp = ApplicationId.from("tenant", "prod", "default");
+ DeploymentTester deploymentTester = new DeploymentTester(tester);
+ deployToAssignCert(deploymentTester, prodApp, List.of(systemTest, stagingTest, productionUsWest1), Optional.empty());
+ assertEquals(1, tester.curator().readAssignedCertificates().size());
+ maintainer.maintain();
+ Optional<AssignedCertificate> usWestCert = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(prodApp), Optional.of(prodApp.instance()));
+ List<String> usWestSans = usWestCert.get().certificate().requestedDnsSans();
+ Assertions.assertThat(usWestSans).contains(westSan);
+ Assertions.assertThat(usWestSans).doesNotContain(centralSan);
+
+ // Deploy to perf and verify that the certs are refreshed
+ deployToAssignCert(deploymentTester, prodApp, List.of(systemTest, stagingTest, productionUsWest1, productionUsCentral1), Optional.empty());
+ Optional<AssignedCertificate> usCentralWestCert = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(prodApp), Optional.of(prodApp.instance()));
+ List<String> usCentralWestSans = usCentralWestCert.get().certificate().requestedDnsSans();
+ assertNotEquals(usWestSans, usCentralWestSans);
+ Assertions.assertThat(usCentralWestSans).contains(westSan);
+ Assertions.assertThat(usCentralWestSans).contains(centralSan);
+ }
+
+ private void deploy() {
+
+ }
+
private void deployToAssignCert(DeploymentTester tester, ApplicationId applicationId, List<JobType> jobTypes, Optional<String> instances) {
- var applicationPackageBuilder = new ApplicationPackageBuilder()
- .region("us-west-1");
+
+ var applicationPackageBuilder = new ApplicationPackageBuilder();
+ jobTypes.stream().filter(JobType::isProduction).map(job -> job.zone().region().value()).forEach(applicationPackageBuilder::region);
+
instances.map(applicationPackageBuilder::instances);
var applicationPackage = applicationPackageBuilder.build();
@@ -279,4 +332,23 @@ public class EndpointCertificateMaintainerTest {
return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate);
}
+ private void prepareCertificatePool(int numCertificates) {
+ ((InMemoryFlagSource)tester.controller().flagSource()).withIntFlag(PermanentFlags.CERT_POOL_SIZE.id(), numCertificates);
+ ((InMemoryFlagSource)tester.controller().flagSource()).withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+
+ // Provision certificates
+ for (int i = 0; i < numCertificates; i++) {
+ certificatePoolMaintainer.maintain();
+ }
+
+ // Make certificate ready
+ EndpointCertificateProviderMock endpointCertificateProvider = (EndpointCertificateProviderMock) tester.controller().serviceRegistry().endpointCertificateProvider();
+ List<EndpointCertificateRequest> endpointCertificateRequests = endpointCertificateProvider.listCertificates();
+ endpointCertificateRequests.forEach(cert -> {
+ EndpointCertificateDetails details = endpointCertificateProvider.certificateDetails(cert.requestId());
+ secretStore.setSecret(details.privateKeyKeyname(), "foo", 0);
+ secretStore.setSecret(details.certKeyKeyname(), "bar", 0);
+ });
+ certificatePoolMaintainer.maintain();
+ }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
index bdbbc4b293f..228a61cebc6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
@@ -10,7 +10,6 @@ import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.path.Path;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.flags.FlagSource;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
@@ -69,6 +68,7 @@ public class NotificationsDbTest {
new ArchiveAccess(),
Optional.empty(),
Instant.EPOCH,
+ List.of(),
Optional.empty());
private static final List<Notification> notifications = List.of(
notification(1001, Type.deployment, Level.error, NotificationSource.from(tenant), "tenant msg"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java
index ef1d9cd92e3..15524e2748c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java
@@ -6,7 +6,6 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
@@ -47,6 +46,7 @@ public class NotifierTest {
new ArchiveAccess(),
Optional.empty(),
Instant.EPOCH,
+ List.of(),
Optional.empty());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index dd7afa314ea..4369675ba3e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.hosted.controller.persistence;// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
import com.google.common.collect.ImmutableBiMap;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.Cursor;
@@ -16,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
@@ -91,7 +94,8 @@ public class TenantSerializerTest {
Optional.of(contact()),
Instant.EPOCH,
lastLoginInfo(321L, 654L, 987L),
- Instant.EPOCH);
+ Instant.EPOCH,
+ List.of());
AthenzTenant serialized = (AthenzTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.contact(), serialized.contact());
}
@@ -109,6 +113,7 @@ public class TenantSerializerTest {
new ArchiveAccess(),
Optional.empty(),
Instant.EPOCH,
+ List.of(),
Optional.empty());
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
@@ -133,6 +138,7 @@ public class TenantSerializerTest {
new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role"),
Optional.of(Instant.ofEpochMilli(1234567)),
Instant.EPOCH,
+ List.of(),
Optional.empty());
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
@@ -185,6 +191,8 @@ public class TenantSerializerTest {
new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role").withGCPMember("user:foo@example.com"),
Optional.empty(),
Instant.EPOCH,
+ List.of(new CloudAccountInfo(CloudAccount.from("aws:123456789012"), Version.fromString("1.2.3")),
+ new CloudAccountInfo(CloudAccount.from("gcp:my-project"), Version.fromString("3.2.1"))),
Optional.empty());
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(serialized.archiveAccess().awsRole().get(), "arn:aws:iam::123456789012:role/my-role");
@@ -263,7 +271,8 @@ public class TenantSerializerTest {
Optional.of(contact()),
Instant.EPOCH,
lastLoginInfo(321L, 654L, 987L),
- Instant.ofEpochMilli(1_000_000));
+ Instant.ofEpochMilli(1_000_000),
+ List.of());
assertEquals(tenant, serializer.tenantFrom(serializer.toSlime(tenant)));
}
@@ -281,6 +290,7 @@ public class TenantSerializerTest {
new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role").withGCPMember("user:foo@example.com"),
Optional.empty(),
Instant.EPOCH,
+ List.of(),
Optional.of(reference));
var slime = serializer.toSlime(tenant);
var deserialized = serializer.tenantFrom(slime);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
index 4eb6e080737..3b74fea2b9c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
@@ -5,6 +5,7 @@ import ai.vespa.hosted.api.MultiPartStreamer;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.restapi.RestApiException;
@@ -26,12 +27,14 @@ import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
import com.yahoo.vespa.hosted.controller.security.Auth0Credentials;
import com.yahoo.vespa.hosted.controller.security.CloudTenantSpec;
import com.yahoo.vespa.hosted.controller.security.Credentials;
+import com.yahoo.vespa.hosted.controller.tenant.CloudAccountInfo;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -369,10 +372,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
new DeploymentTester(wrapped).newDeploymentContext(ApplicationId.from(tenantName, applicationName, InstanceName.defaultName()))
.submit()
.deploy();
+ tester.controller().tenants().updateCloudAccounts(tenantName, List.of(new CloudAccountInfo(CloudAccount.from("aws:123456789012"), new Version(1, 2, 4))));
tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
- (response) -> assertFalse(response.getBodyAsString().contains("archiveAccessRole")),
- 200);
+ new File("tenant-cloud.json"));
tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/aws", PUT)
.data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
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 ab70dfd6073..6b377e2069b 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
@@ -1372,7 +1372,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Create legacy tenant name containing underscores
tester.controller().curator().writeTenant(new AthenzTenant(TenantName.from("my_tenant"), ATHENZ_TENANT_DOMAIN,
- new Property("property1"), Optional.empty(), Optional.empty(), Instant.EPOCH, LastLoginInfo.EMPTY, Instant.EPOCH));
+ new Property("property1"), Optional.empty(), Optional.empty(), Instant.EPOCH, LastLoginInfo.EMPTY, Instant.EPOCH, List.of()));
// POST (add) a Athenz tenant with dashes duplicates existing one with underscores
tester.assertResponse(request("/application/v4/tenant/my-tenant", POST)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-cloud.json
new file mode 100644
index 00000000000..c7258ab3aa6
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-cloud.json
@@ -0,0 +1,35 @@
+{
+ "tenant": "scoober",
+ "type": "CLOUD",
+ "creator": "developer@scoober",
+ "pemDeveloperKeys": [],
+ "secretStores": [],
+ "integrations": {
+ "aws": {
+ "tenantRole": "scoober-tenant-role",
+ "accounts": []
+ }
+ },
+ "quota": {
+ "budgetUsed": 1.304
+ },
+ "archiveAccess": {},
+ "applications": [
+ {
+ "tenant": "scoober",
+ "application": "albums",
+ "instance": "default",
+ "url": "http://localhost:8080/application/v4/tenant/scoober/application/albums/instance/default"
+ }
+ ],
+ "metaData": {
+ "createdAtMillis": 1600000000000,
+ "lastSubmissionToProdMillis": 1000
+ },
+ "cloudAccounts": [
+ {
+ "cloudAccount": "aws:123456789012",
+ "templateVersion": "1.2.4"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index eb376a95c74..8b76613676c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -34,6 +34,9 @@
"name": "ChangeRequestMaintainer"
},
{
+ "name": "CloudAccountVerifier"
+ },
+ {
"name": "CloudDatabaseMaintainer"
},
{
@@ -130,7 +133,5 @@
"name": "VersionStatusUpdater"
}
],
- "inactive": [
- "DeploymentExpirer"
- ]
+ "inactive": ["DeploymentExpirer"]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index 581f9704fc5..001e02e1b16 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -70,17 +70,7 @@ public class SignatureFilterTest {
filter = new SignatureFilter(tester.controller());
signer = new RequestSigner(privateKey, id.serializedForm(), tester.clock());
- tester.curator().writeTenant(new CloudTenant(appId.tenant(),
- Instant.EPOCH,
- LastLoginInfo.EMPTY,
- Optional.empty(),
- ImmutableBiMap.of(),
- TenantInfo.empty(),
- List.of(),
- new ArchiveAccess(),
- Optional.empty(),
- Instant.EPOCH,
- Optional.empty()));
+ tester.curator().writeTenant(CloudTenant.create(appId.tenant(), Instant.EPOCH, null));
tester.curator().writeApplication(new Application(appId, tester.clock().instant()));
}
@@ -129,6 +119,7 @@ public class SignatureFilterTest {
new ArchiveAccess(),
Optional.empty(),
Instant.EPOCH,
+ List.of(),
Optional.empty()));
verifySecurityContext(requestOf(signer.signed(request.copy(), Method.POST, () -> new ByteArrayInputStream(hiBytes)), hiBytes),
new SecurityContext(new SimplePrincipal("user"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
index 779aee73dae..eb3f9daef53 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
@@ -63,7 +63,7 @@ public class UserFlagsSerializerTest {
"{\"id\":\"int-id\",\"rules\":[{\"value\":456}]}," + // Default from DB
"{\"id\":\"jackson-id\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"tenant\"}],\"value\":{\"integer\":456,\"string\":\"xyz\"}},{\"value\":{\"integer\":123,\"string\":\"abc\"}}]}," + // Resolved for email
// Resolved for email, but conditions are empty since this user is not authorized for any tenants
- "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"application\"}],\"value\":[\"value1\"]},{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"application\"}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
+ "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"instance\"}],\"value\":[\"value1\"]},{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"instance\"}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
"{\"id\":\"string-id\",\"rules\":[{\"value\":\"value1\"}]}]}", // resolved for email
flagData, Set.of(), false, email1);
@@ -72,7 +72,7 @@ public class UserFlagsSerializerTest {
"{\"id\":\"int-id\",\"rules\":[{\"value\":456}]}," + // Default from DB
"{\"id\":\"jackson-id\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"tenant\",\"values\":[\"tenant1\"]}],\"value\":{\"integer\":456,\"string\":\"xyz\"}},{\"value\":{\"integer\":123,\"string\":\"abc\"}}]}," + // Resolved for email
// Resolved for email, but conditions have filtered out tenant2
- "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\"]}],\"value\":[\"value1\"]},{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\"]}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
+ "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"instance\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\"]}],\"value\":[\"value1\"]},{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"instance\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\"]}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
"{\"id\":\"string-id\",\"rules\":[{\"value\":\"value1\"}]}]}", // resolved for email
flagData, Set.of("tenant1"), false, email1);
@@ -81,7 +81,7 @@ public class UserFlagsSerializerTest {
"{\"id\":\"int-id\",\"rules\":[{\"value\":456}]}," + // Default from DB
"{\"id\":\"jackson-id\",\"rules\":[{\"value\":{\"integer\":123,\"string\":\"abc\"}}]}," + // Default from code, no DB values match
// Includes last value from DB which is not conditioned on email and the default from code
- "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\",\"tenant2:music:default\"]}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
+ "{\"id\":\"list-id\",\"rules\":[{\"conditions\":[{\"type\":\"blacklist\",\"dimension\":\"instance\",\"values\":[\"tenant1:video:default\",\"tenant1:video:default\",\"tenant2:music:default\"]}],\"value\":[\"value1\",\"value3\"]},{\"value\":[\"a\"]}]}," +
"{\"id\":\"string-id\",\"rules\":[{\"value\":\"default value\"}]}]}", // Default from code
flagData, Set.of(), true, "operator@domain.tld");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
index 630de5137bb..b2b34441219 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
@@ -598,21 +598,28 @@ public class RoutingPoliciesTest {
app.deploy();
- // TXT records are cleaned up as we go—the last challenge is the last to go here, and we must flush it ourselves.
+ // TXT records are cleaned up when deployments are deactivated.
+ // The last challenge is the last to go here, and we must flush it ourselves.
assertEquals(List.of("a.t.aws-us-east-33a.vespa.oath.cloud",
"challenge--a.t.aws-us-east-33a.vespa.oath.cloud"),
tester.recordNames());
app.flushDnsUpdates();
assertEquals(Set.of(new Record(Type.CNAME,
RecordName.from("a.t.aws-us-east-33a.vespa.oath.cloud"),
- RecordData.from("lb-0--t.a.default--prod.aws-us-east-33a."))),
+ RecordData.from("lb-0--t.a.default--prod.aws-us-east-33a.")),
+ new Record(Type.TXT,
+ RecordName.from("challenge--a.t.aws-us-east-33a.vespa.oath.cloud"),
+ RecordData.from("system"))),
tester.controllerTester().nameService().records());
+ tester.controllerTester().controller().applications().deactivate(app.instanceId(), zone3);
+ app.flushDnsUpdates();
+ assertEquals(Set.of(),
+ tester.controllerTester().nameService().records());
+ // Deployment fails because challenge is not answered (immediately).
tester.tester.controllerTester().serviceRegistry().vpcEndpointService().outcomes
.put(RecordName.from("challenge--a.t.aws-us-east-33a.vespa.oath.cloud"), ChallengeState.running);
-
- // Deployment fails because challenge is not answered (immediately).
assertEquals("Status of run 2 of production-aws-us-east-33a for t.a ==> expected: <succeeded> but was: <unfinished>",
assertThrows(AssertionError.class,
() -> app.submit(appPackage).deploy())
@@ -1057,40 +1064,47 @@ public class RoutingPoliciesTest {
int clustersPerZone = 2;
var zone1 = ZoneId.from("prod", "aws-us-east-1c");
var zone2 = ZoneId.from("prod", "aws-eu-west-1a");
+ var zone3 = ZoneId.from("prod", "aws-us-east-1a"); // To test global endpoint pointing to two zones in same cloud-native region
ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
.region(zone2.region())
+ .region(zone3.region())
.container("c0", AuthMethod.mtls)
.container("c1", AuthMethod.mtls, AuthMethod.token)
.endpoint("foo", "c0")
.applicationEndpoint("bar", "c0", Map.of(zone1.region().value(), Map.of(InstanceName.defaultName(), 1)))
.build();
- tester.provisionLoadBalancers(clustersPerZone, context.instanceId(), zone1, zone2);
+ tester.provisionLoadBalancers(clustersPerZone, context.instanceId(), zone1, zone2, zone3);
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
// Deployment creates generated zone names
List<String> expectedRecords = List.of(
// save me, jebus!
- "b36bf591.cafed00d.aws-us-east-1.w.vespa-app.cloud",
+ "a6414896.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
"b36bf591.cafed00d.z.vespa-app.cloud",
"bar.app1.tenant1.a.vespa-app.cloud",
"bc50b636.cafed00d.z.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1.w.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1.w.vespa-app.cloud",
+ "c0.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
+ "c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c33db5ed.cafed00d.z.vespa-app.cloud",
+ "d467800f.cafed00d.z.vespa-app.cloud",
"d71005bf.cafed00d.z.vespa-app.cloud",
- "dd0971b4.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
"dd0971b4.cafed00d.z.vespa-app.cloud",
"eb48ad53.cafed00d.z.vespa-app.cloud",
+ "ec1e1288.cafed00d.z.vespa-app.cloud",
"f2fa41ec.cafed00d.g.vespa-app.cloud",
+ "f411d177.cafed00d.z.vespa-app.cloud",
"f4a4d111.cafed00d.a.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud",
"foo.app1.tenant1.g.vespa-app.cloud"
);
assertEquals(expectedRecords, tester.recordNames());
- assertEquals(4, tester.policiesOf(context.instanceId()).size());
+ assertEquals(6, tester.policiesOf(context.instanceId()).size());
ClusterSpec.Id cluster0 = ClusterSpec.Id.from("c0");
ClusterSpec.Id cluster1 = ClusterSpec.Id.from("c1");
for (var zone : List.of(zone1, zone2)) {
@@ -1107,13 +1121,17 @@ public class RoutingPoliciesTest {
// Ordinary endpoints point to expected targets
tester.assertTargets(context.instanceId(), EndpointId.of("foo"), cluster0, 0,
- Map.of(zone1, 1L, zone2, 1L));
+ ImmutableMap.of(zone1, 1L,
+ zone2, 1L,
+ zone3, 1L));
tester.assertTargets(context.application().id(), EndpointId.of("bar"), cluster0, 0,
Map.of(context.deploymentIdIn(zone1), 1));
// Generated endpoints point to expected targets
tester.assertTargets(context.instanceId(), EndpointId.of("foo"), cluster0, 0,
- Map.of(zone1, 1L, zone2, 1L),
+ ImmutableMap.of(zone1, 1L,
+ zone2, 1L,
+ zone3, 1L),
true);
tester.assertTargets(context.application().id(), EndpointId.of("bar"), cluster0, 0,
Map.of(context.deploymentIdIn(zone1), 1),
@@ -1127,6 +1145,7 @@ public class RoutingPoliciesTest {
// One endpoint is removed
applicationPackage = applicationPackageBuilder().region(zone1.region())
.region(zone2.region())
+ .region(zone3.region())
.container("c0", AuthMethod.mtls)
.container("c1", AuthMethod.mtls, AuthMethod.token)
.applicationEndpoint("bar", "c0", Map.of(zone1.region().value(), Map.of(InstanceName.defaultName(), 1)))
@@ -1138,13 +1157,18 @@ public class RoutingPoliciesTest {
"bar.app1.tenant1.a.vespa-app.cloud",
"bc50b636.cafed00d.z.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
+ "c0.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
+ "c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c33db5ed.cafed00d.z.vespa-app.cloud",
+ "d467800f.cafed00d.z.vespa-app.cloud",
"d71005bf.cafed00d.z.vespa-app.cloud",
"dd0971b4.cafed00d.z.vespa-app.cloud",
"eb48ad53.cafed00d.z.vespa-app.cloud",
+ "ec1e1288.cafed00d.z.vespa-app.cloud",
+ "f411d177.cafed00d.z.vespa-app.cloud",
"f4a4d111.cafed00d.a.vespa-app.cloud"
), tester.recordNames());
@@ -1157,6 +1181,35 @@ public class RoutingPoliciesTest {
}
@Test
+ public void generated_endpoints_only() {
+ var tester = new RoutingPoliciesTester(SystemName.Public);
+ var context = tester.newDeploymentContext("tenant1", "app1", "default");
+ tester.controllerTester().flagSource()
+ .withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true)
+ .withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false);
+ addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
+
+ // Deploy application
+ var zone1 = ZoneId.from("prod", "aws-us-east-1c");
+ ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .container("c0", AuthMethod.mtls)
+ .endpoint("foo", "c0")
+ .build();
+ tester.provisionLoadBalancers(1, context.instanceId(), zone1);
+ // ConfigServerMock provisions a load balancer for the "default" cluster, but in this scenario we need full
+ // control over the load balancer name because "default" has no special treatment when using generated endpoints
+ tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c"));
+ tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c"));
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ tester.assertTargets(context.instance().id(), EndpointId.of("foo"), ClusterSpec.Id.from("c0"),
+ 0, Map.of(zone1, 1L), true);
+ assertEquals(List.of("a9c8c045.cafed00d.g.vespa-app.cloud",
+ "ebd395b6.cafed00d.z.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"),
+ tester.recordNames());
+ }
+
+ @Test
public void generated_endpoints_multi_instance() {
var tester = new RoutingPoliciesTester(SystemName.Public);
var context0 = tester.newDeploymentContext("tenant1", "app1", "default");
@@ -1213,6 +1266,32 @@ public class RoutingPoliciesTest {
assertEquals(List.of(), tester.recordNames());
}
+ @Test
+ public void generated_endpoint_migration_with_global_endpoint() {
+ var tester = new RoutingPoliciesTester(SystemName.Public);
+ var context = tester.newDeploymentContext("tenant1", "app1", "default");
+ addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
+
+ // Deploy application
+ int clustersPerZone = 2;
+ var zone1 = ZoneId.from("prod", "aws-us-east-1c");
+ var zone2 = ZoneId.from("prod", "aws-eu-west-1a");
+ ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .region(zone2.region())
+ .container("c0", AuthMethod.mtls)
+ .endpoint("foo", "c0")
+ .build();
+ tester.provisionLoadBalancers(clustersPerZone, context.instanceId(), zone1, zone2);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
+ tester.assertTargets(context.instanceId(), EndpointId.of("foo"), 0, zone1, zone2);
+
+ // Switch to generated
+ tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
+ tester.assertTargets(context.instance().id(), EndpointId.of("foo"), ClusterSpec.Id.from("c0"),
+ 0, Map.of(zone1, 1L, zone2, 1L), true);
+ }
+
private void addCertificateToPool(String id, UnassignedCertificate.State state, RoutingPoliciesTester tester) {
EndpointCertificate cert = new EndpointCertificate("testKey", "testCert", 1, 0,
"request-id",
@@ -1270,6 +1349,11 @@ public class RoutingPoliciesTest {
.withCloudNativeRegionName("eu-west-1")
.build(),
ZoneApiMock.newBuilder()
+ .with(ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1a")))
+ .with(CloudName.AWS)
+ .withCloudNativeRegionName("us-east-1")
+ .build(),
+ ZoneApiMock.newBuilder()
.with(ZoneId.from(Environment.prod, RegionName.from("gcp-us-south1-b")))
.with(CloudName.GCP)
.withCloudNativeRegionName("us-south1")
diff --git a/dependency-versions/pom.xml b/dependency-versions/pom.xml
index 962d666bf6b..3127233f67f 100644
--- a/dependency-versions/pom.xml
+++ b/dependency-versions/pom.xml
@@ -34,7 +34,7 @@
<!-- DO NOT UPGRADE THESE TO A NEW MAJOR VERSION WITHOUT CHECKING FOR BINARY COMPATIBILITY -->
<aopalliance.vespa.version>1.0</aopalliance.vespa.version>
<commons-logging.vespa.version>1.2</commons-logging.vespa.version> <!-- This version is exported by jdisc via jcl-over-slf4j. -->
- <error-prone-annotations.vespa.version>2.21.1</error-prone-annotations.vespa.version>
+ <error-prone-annotations.vespa.version>2.22.0</error-prone-annotations.vespa.version>
<guava.vespa.version>32.1.2-jre</guava.vespa.version>
<guice.vespa.version>6.0.0</guice.vespa.version>
<jackson2.vespa.version>2.15.2</jackson2.vespa.version>
@@ -86,7 +86,7 @@
<commons.math3.vespa.version>3.6.1</commons.math3.vespa.version>
<commons-compress.vespa.version>1.24.0</commons-compress.vespa.version>
<curator.vespa.version>5.5.0</curator.vespa.version>
- <dropwizard.metrics.vespa.version>4.2.19</dropwizard.metrics.vespa.version>
+ <dropwizard.metrics.vespa.version>4.2.20</dropwizard.metrics.vespa.version>
<eclipse-collections.vespa.version>11.1.0</eclipse-collections.vespa.version>
<felix.vespa.version>7.0.5</felix.vespa.version>
<felix.log.vespa.version>1.3.0</felix.log.vespa.version>
@@ -106,13 +106,13 @@
<junit.platform.vespa.version>1.10.0</junit.platform.vespa.version>
<junit4.vespa.version>4.13.2</junit4.vespa.version>
<luben.zstd.vespa.version>1.5.5-5</luben.zstd.vespa.version>
- <lucene.vespa.version>9.7.0</lucene.vespa.version>
+ <lucene.vespa.version>9.8.0</lucene.vespa.version>
<maven-archiver.vespa.version>3.6.1</maven-archiver.vespa.version>
<maven-wagon.vespa.version>3.5.3</maven-wagon.vespa.version>
<mimepull.vespa.version>1.10.0</mimepull.vespa.version>
<mockito.vespa.version>5.5.0</mockito.vespa.version>
<mojo-executor.vespa.version>2.4.0</mojo-executor.vespa.version>
- <netty.vespa.version>4.1.98.Final</netty.vespa.version>
+ <netty.vespa.version>4.1.99.Final</netty.vespa.version>
<netty-tcnative.vespa.version>2.0.61.Final</netty-tcnative.vespa.version>
<onnxruntime.vespa.version>1.15.1</onnxruntime.vespa.version>
<opennlp.vespa.version>2.3.0</opennlp.vespa.version>
@@ -123,9 +123,9 @@
<protobuf.vespa.version>3.24.3</protobuf.vespa.version>
<questdb.vespa.version>7.3.2</questdb.vespa.version>
<spifly.vespa.version>1.3.6</spifly.vespa.version>
- <snappy.vespa.version>1.1.10.3</snappy.vespa.version>
+ <snappy.vespa.version>1.1.10.5</snappy.vespa.version>
<surefire.vespa.version>3.1.2</surefire.vespa.version>
- <wiremock.vespa.version>3.1.0</wiremock.vespa.version>
+ <wiremock.vespa.version>3.2.0</wiremock.vespa.version>
<xerces.vespa.version>2.12.2</xerces.vespa.version>
<zero-allocation-hashing.vespa.version>0.16</zero-allocation-hashing.vespa.version>
<zookeeper.client.vespa.version>3.8.0</zookeeper.client.vespa.version>
@@ -153,7 +153,7 @@
<maven-plugin-api.vespa.version>${maven-core.vespa.version}</maven-plugin-api.vespa.version>
<maven-plugin-tools.vespa.version>3.9.0</maven-plugin-tools.vespa.version>
<maven-resources-plugin.vespa.version>3.3.1</maven-resources-plugin.vespa.version>
- <maven-shade-plugin.vespa.version>3.5.0</maven-shade-plugin.vespa.version>
+ <maven-shade-plugin.vespa.version>3.5.1</maven-shade-plugin.vespa.version>
<maven-site-plugin.vespa.version>3.12.1</maven-site-plugin.vespa.version>
<maven-source-plugin.vespa.version>3.3.0</maven-source-plugin.vespa.version>
<properties-maven-plugin.vespa.version>1.2.0</properties-maven-plugin.vespa.version>
diff --git a/document/src/vespa/document/select/parse_utils.cpp b/document/src/vespa/document/select/parse_utils.cpp
index 4c116d5bff4..d1e559f211d 100644
--- a/document/src/vespa/document/select/parse_utils.cpp
+++ b/document/src/vespa/document/select/parse_utils.cpp
@@ -24,7 +24,7 @@ parse_i64(const char* str, size_t len, int64_t& out) {
}
bool
parse_double(const char* str, size_t len, double& out) {
-#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000
+#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180000
// Temporary workaround that also handles underflow (cf. issue 3081)
// until libc++ supports std::from_chars for double
char *str_end = const_cast<char*>(str) + len;
diff --git a/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp b/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
index 3fafb8f8b18..d0db56f3433 100644
--- a/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
+++ b/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
@@ -14,7 +14,9 @@
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/Transforms/Scalar.h>
+#if LLVM_VERSION_MAJOR < 17
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
+#endif
#include <llvm/Support/ManagedStatic.h>
#include <vespa/eval/eval/check_type.h>
#include <vespa/vespalib/stllike/hash_set.h>
diff --git a/eval/src/vespa/eval/eval/tensor_function.cpp b/eval/src/vespa/eval/eval/tensor_function.cpp
index 97590322a50..7bef6d2d880 100644
--- a/eval/src/vespa/eval/eval/tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/tensor_function.cpp
@@ -350,12 +350,12 @@ Peek::make_spec() const
// the value peeked is child 0, so
// children (for label computation) in spec start at 1:
size_t child_idx = 1;
- for (const auto & [dim_name, label_or_child] : map()) {
+ for (const auto & [outer_dim_name, label_or_child] : map()) {
std::visit(vespalib::overload {
- [&,&dim_name = dim_name](const TensorSpec::Label &label) {
+ [&,&dim_name = outer_dim_name](const TensorSpec::Label &label) {
generic_spec.emplace(dim_name, label);
},
- [&,&dim_name = dim_name](const TensorFunction::Child &) {
+ [&,&dim_name = outer_dim_name](const TensorFunction::Child &) {
generic_spec.emplace(dim_name, child_idx++);
}
}, label_or_child);
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
index b16d26a04a4..bbf932ab652 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -22,6 +22,9 @@ public class FetchVector {
* Note: If this enum is changed, you must also change {@link DimensionHelper}.
*/
public enum Dimension {
+ /** Application id from ApplicationId::toSerializedForm(TenantName, ApplicationName) on the form tenant:applicationName. */
+ APPLICATION_ID,
+
/**
* Cloud from com.yahoo.config.provision.CloudName::value, e.g. yahoo, aws, gcp.
*
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 2e158f0f3ef..54a3ea4f2c2 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -13,6 +13,7 @@ import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Predicate;
+import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.INSTANCE_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CLOUD_ACCOUNT;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CLUSTER_ID;
@@ -62,6 +63,7 @@ public class Flags {
" latency-amortized-over-requests, latency-amortized-over-time",
"Takes effect at redeployment (requires restart)",
INSTANCE_ID);
+
public static final UnboundStringFlag SUMMARY_DECODE_POLICY = defineStringFlag(
"summary-decode-policy", "eager",
List.of("baldersheim"), "2023-03-30", "2023-12-31",
@@ -311,10 +313,10 @@ public class Flags {
"randomized-endpoint-names", false, List.of("andreer"), "2023-04-26", "2023-10-14",
"Whether to use randomized endpoint names",
"Takes effect on application deployment",
- INSTANCE_ID);
+ INSTANCE_ID, APPLICATION_ID, TENANT_ID);
public static final UnboundBooleanFlag ENABLE_THE_ONE_THAT_SHOULD_NOT_BE_NAMED = defineFeatureFlag(
- "enable-the-one-that-should-not-be-named", false, List.of("hmusum"), "2023-05-08", "2023-10-01",
+ "enable-the-one-that-should-not-be-named", false, List.of("hmusum"), "2023-05-08", "2023-11-01",
"Whether to enable the one program that should not be named",
"Takes effect at next host-admin tick");
@@ -340,13 +342,13 @@ public class Flags {
public static final UnboundBooleanFlag WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
"write-config-server-session-data-as-blob", false,
- List.of("hmusum"), "2023-07-19", "2023-10-01",
+ List.of("hmusum"), "2023-07-19", "2023-11-01",
"Whether to write config server session data in one blob or as individual paths",
"Takes effect immediately");
public static final UnboundBooleanFlag READ_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
"read-config-server-session-data-as-blob", false,
- List.of("hmusum"), "2023-07-19", "2023-10-01",
+ List.of("hmusum"), "2023-07-19", "2023-11-01",
"Whether to read config server session data from session data blob or from individual paths",
"Takes effect immediately");
@@ -371,12 +373,6 @@ public class Flags {
"Takes effect on next host provisioning / run of host-admin",
HOSTNAME, CLOUD_ACCOUNT);
- public static final UnboundBooleanFlag WRITE_APPLICATION_DATA_AS_JSON = defineFeatureFlag(
- "write-application-data-as-json", true,
- List.of("hmusum"), "2023-08-27", "2023-10-01",
- "Whether to write application data (active session id, last deployed session id etc. ) as json",
- "Takes effect immediately");
-
public static final UnboundIntFlag MIN_EXCLUSIVE_ADVERTISED_MEMORY_GB = defineIntFlag(
"min-exclusive-advertised-memory-gb", 8,
List.of("freva"), "2023-09-08", "2023-11-01",
@@ -385,7 +381,7 @@ public class Flags {
INSTANCE_ID, CLUSTER_ID, CLUSTER_TYPE);
public static final UnboundBooleanFlag ASSIGN_RANDOMIZED_ID = defineFeatureFlag(
- "assign-randomized-id", false,
+ "assign-randomized-id", true,
List.of("mortent"), "2023-08-31", "2024-02-01",
"Whether to assign randomized id to the application",
"Takes effect immediately",
@@ -406,6 +402,26 @@ public class Flags {
"Takes effect at redeployment",
INSTANCE_ID);
+ public static final UnboundBooleanFlag DYNAMIC_HEAP_SIZE = defineFeatureFlag(
+ "dynamic-heap-size", false,
+ List.of("bjorncs"), "2023-09-21", "2024-01-15",
+ "Whether to calculate JVM heap size based on predicted Onnx model memory requirements",
+ "Takes effect at redeployment",
+ INSTANCE_ID);
+
+ public static final UnboundStringFlag UNKNOWN_CONFIG_DEFINITION = defineStringFlag(
+ "unknown-config-definition", "warn",
+ List.of("hmusum"), "2023-09-25", "2023-11-01",
+ "How to handle user config referencing unknown config definitions. Valid values are log, warn, fail",
+ "Takes effect at redeployment",
+ INSTANCE_ID);
+
+ public static final UnboundBooleanFlag LEGACY_ENDPOINTS = defineFeatureFlag(
+ "legacy-endpoints", true, List.of("mpolden", "tokle"), "2023-09-29", "2024-03-01",
+ "Whether legacy (non-anonymized) endpoints should be created in DNS",
+ "Takes effect on redeployment through controller",
+ INSTANCE_ID, APPLICATION_ID, TENANT_ID);
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
index f867daac245..04beec19d73 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
@@ -7,28 +7,30 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* @author hakonhall
*/
public class DimensionHelper {
- private static final Map<FetchVector.Dimension, List<String>> serializedDimensions = new HashMap<>();
+ private static final Map<FetchVector.Dimension, String> serializedDimensions = new HashMap<>();
static {
- serializedDimensions.put(FetchVector.Dimension.CLOUD, List.of("cloud"));
- serializedDimensions.put(FetchVector.Dimension.CLOUD_ACCOUNT, List.of("cloud-account"));
- serializedDimensions.put(FetchVector.Dimension.CLUSTER_ID, List.of("cluster-id"));
- serializedDimensions.put(FetchVector.Dimension.CLUSTER_TYPE, List.of("cluster-type"));
- serializedDimensions.put(FetchVector.Dimension.CONSOLE_USER_EMAIL, List.of("console-user-email"));
- serializedDimensions.put(FetchVector.Dimension.ENVIRONMENT, List.of("environment"));
- serializedDimensions.put(FetchVector.Dimension.HOSTNAME, List.of("hostname"));
- serializedDimensions.put(FetchVector.Dimension.INSTANCE_ID, List.of("application", "instance"));
- serializedDimensions.put(FetchVector.Dimension.NODE_TYPE, List.of("node-type"));
- serializedDimensions.put(FetchVector.Dimension.SYSTEM, List.of("system"));
- serializedDimensions.put(FetchVector.Dimension.TENANT_ID, List.of("tenant"));
- serializedDimensions.put(FetchVector.Dimension.VESPA_VERSION, List.of("vespa-version"));
- serializedDimensions.put(FetchVector.Dimension.ZONE_ID, List.of("zone"));
+ serializedDimensions.put(FetchVector.Dimension.APPLICATION_ID, "application");
+ serializedDimensions.put(FetchVector.Dimension.CLOUD, "cloud");
+ serializedDimensions.put(FetchVector.Dimension.CLOUD_ACCOUNT, "cloud-account");
+ serializedDimensions.put(FetchVector.Dimension.CLUSTER_ID, "cluster-id");
+ serializedDimensions.put(FetchVector.Dimension.CLUSTER_TYPE, "cluster-type");
+ serializedDimensions.put(FetchVector.Dimension.CONSOLE_USER_EMAIL, "console-user-email");
+ serializedDimensions.put(FetchVector.Dimension.ENVIRONMENT, "environment");
+ serializedDimensions.put(FetchVector.Dimension.HOSTNAME, "hostname");
+ serializedDimensions.put(FetchVector.Dimension.INSTANCE_ID, "instance");
+ serializedDimensions.put(FetchVector.Dimension.NODE_TYPE, "node-type");
+ serializedDimensions.put(FetchVector.Dimension.SYSTEM, "system");
+ serializedDimensions.put(FetchVector.Dimension.TENANT_ID, "tenant");
+ serializedDimensions.put(FetchVector.Dimension.VESPA_VERSION, "vespa-version");
+ serializedDimensions.put(FetchVector.Dimension.ZONE_ID, "zone");
if (serializedDimensions.size() != FetchVector.Dimension.values().length) {
throw new IllegalStateException(FetchVectorHelper.class.getName() + " is not in sync with " +
@@ -36,27 +38,16 @@ public class DimensionHelper {
}
}
- private static final Map<String, FetchVector.Dimension> deserializedDimensions = reverseMapping(serializedDimensions);
-
- private static Map<String, FetchVector.Dimension> reverseMapping(Map<FetchVector.Dimension, List<String>> mapping) {
- Map<String, FetchVector.Dimension> reverseMapping = new LinkedHashMap<>();
- mapping.forEach((dimension, serializedDimensions) -> {
- serializedDimensions.forEach(serializedDimension -> {
- if (reverseMapping.put(serializedDimension, dimension) != null) {
- throw new IllegalStateException("Duplicate serialized dimension: '" + serializedDimension + "'");
- }
- });
- });
- return Map.copyOf(reverseMapping);
- }
+ private static final Map<String, FetchVector.Dimension> deserializedDimensions = serializedDimensions.
+ entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
public static String toWire(FetchVector.Dimension dimension) {
- List<String> serializedDimension = serializedDimensions.get(dimension);
- if (serializedDimension == null || serializedDimension.isEmpty()) {
+ String serializedDimension = serializedDimensions.get(dimension);
+ if (serializedDimension == null) {
throw new IllegalArgumentException("Unsupported dimension (please add it): '" + dimension + "'");
}
- return serializedDimension.get(0);
+ return serializedDimension;
}
public static FetchVector.Dimension fromWire(String serializedDimension) {
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java b/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
index ed81afc8054..40310c47f78 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
@@ -30,7 +30,7 @@ public class FlagDataTest {
},
{
"type": "blacklist",
- "dimension": "application",
+ "dimension": "instance",
"values": [ "app1", "app2" ]
}
],
@@ -52,8 +52,6 @@ public class FlagDataTest {
}
}""";
- private final String json_with_instance = json.replace("application", "instance");
-
private final FetchVector vector = new FetchVector();
@Test
@@ -273,11 +271,6 @@ public class FlagDataTest {
}
private void verify(Optional<String> expectedValue, FetchVector vector) {
- verify(json, expectedValue, vector);
- verify(json_with_instance, expectedValue, vector);
- }
-
- private void verify(String json, Optional<String> expectedValue, FetchVector vector) {
FlagData data = FlagData.deserialize(json);
assertEquals("id1", data.id().toString());
Optional<RawFlag> rawFlag = data.resolve(vector);
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizer.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizer.java
index 5df066f06fa..1a1e26b1091 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizer.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizer.java
@@ -70,7 +70,7 @@ public class ExpressionOptimizer extends ExpressionConverter {
}
return exp instanceof InputExpression ||
exp instanceof NowExpression ||
- exp instanceof SetValueExpression ||
+ exp instanceof ConstantExpression ||
exp instanceof HostNameExpression ||
exp instanceof GetVarExpression;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateAdapter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateAdapter.java
index 3c3f75a6693..24ed6b898fd 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateAdapter.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateAdapter.java
@@ -75,7 +75,7 @@ public class FieldPathUpdateAdapter implements UpdateAdapter {
if (type == FieldPathEntry.Type.STRUCT_FIELD) {
if (!(value instanceof StructuredFieldValue)) {
throw new IllegalArgumentException("Expected structured field value, got " +
- value.getClass().getName() + ".");
+ value.getClass().getName());
}
for (Iterator<Map.Entry<Field, FieldValue>> it = ((StructuredFieldValue)value).iterator(); it.hasNext();) {
Map.Entry<Field, FieldValue> structEntry = it.next();
@@ -84,8 +84,7 @@ public class FieldPathUpdateAdapter implements UpdateAdapter {
createUpdatesAt(nextPath, structEntry.getValue(), idx + 1, out);
}
} else if (type == FieldPathEntry.Type.MAP_KEY) {
- if (value instanceof WeightedSet) {
- WeightedSet wset = (WeightedSet)value;
+ if (value instanceof WeightedSet wset) {
for (Iterator<FieldValue> it = wset.fieldValueIterator(); it.hasNext();) {
FieldValue wsetEntry = it.next();
List<FieldPathEntry> nextPath = new ArrayList<>(path);
@@ -102,7 +101,7 @@ public class FieldPathUpdateAdapter implements UpdateAdapter {
}
} else {
throw new IllegalArgumentException("Expected map or weighted set, got " +
- value.getClass().getName() + ".");
+ value.getClass().getName());
}
} else {
path.add(pathEntry);
@@ -111,7 +110,7 @@ public class FieldPathUpdateAdapter implements UpdateAdapter {
} else if (update instanceof AddFieldPathUpdate) {
if (!(value instanceof Array)) {
throw new IllegalStateException("Expected array, got " +
- value.getClass().getName() + ".");
+ value.getClass().getName());
}
out.addFieldPathUpdate(new AddFieldPathUpdate(update.getDocumentType(), new FieldPath(path).toString(),
update.getOriginalWhereClause(), (Array)value));
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateAdapter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateAdapter.java
index 2601c5d0f71..b2ef838bcc4 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateAdapter.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateAdapter.java
@@ -152,7 +152,7 @@ public class FieldUpdateAdapter implements UpdateAdapter {
if (val instanceof Array) {
lst.addAll(createMapValueUpdatesForArray((Array)val, (MapValueUpdate)upd));
} else if (val instanceof MapFieldValue) {
- throw new UnsupportedOperationException("Can not map into a " + val.getClass().getName() + ".");
+ throw new UnsupportedOperationException("Can not map into a " + val.getClass().getName());
} else if (val instanceof StructuredFieldValue) {
lst.addAll(createMapValueUpdatesForStruct((StructuredFieldValue)val, (MapValueUpdate)upd));
} else if (val instanceof WeightedSet) {
@@ -168,7 +168,7 @@ public class FieldUpdateAdapter implements UpdateAdapter {
lst.add(upd);
} else {
throw new UnsupportedOperationException(
- "Value update type " + upd.getClass().getName() + " not supported.");
+ "Value update type " + upd.getClass().getName() + " not supported");
}
return lst;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateHelper.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateHelper.java
index 8c32f2e451d..a7fed91c360 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateHelper.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldUpdateHelper.java
@@ -95,7 +95,7 @@ public abstract class FieldUpdateHelper {
return applyUpdate(nestedUpdate, value);
}
} else if (value instanceof MapFieldValue) {
- throw new UnsupportedOperationException("Can not map into a " + value.getClass().getName() + ".");
+ throw new UnsupportedOperationException("Can not map into a " + value.getClass().getName());
} else if (value instanceof StructuredFieldValue) {
Field field = ((StructuredFieldValue)value).getField(String.valueOf(update.getValue()));
if (field == null) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java
index 4f96f2b7a31..d2cf97273ad 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java
@@ -49,7 +49,7 @@ public abstract class FieldValueConverter {
nextType = nextVal.getDataType();
} else if (!nextType.isValueCompatible(nextVal)) {
throw new IllegalArgumentException("Expected " + nextType.getName() + ", got " +
- nextVal.getDataType().getName() + ".");
+ nextVal.getDataType().getName());
}
next.add(nextVal);
}
@@ -63,7 +63,7 @@ public abstract class FieldValueConverter {
@SuppressWarnings({ "unchecked", "rawtypes" })
private FieldValue convertMap(MapFieldValue<FieldValue, FieldValue> val) {
- Map<FieldValue, FieldValue> next = new LinkedHashMap<FieldValue, FieldValue>();
+ Map<FieldValue, FieldValue> next = new LinkedHashMap<>();
DataType nextKeyType = null, nextValType = null;
for (Map.Entry<FieldValue, FieldValue> entry : val.entrySet()) {
FieldValue prevKey = entry.getKey();
@@ -75,7 +75,7 @@ public abstract class FieldValueConverter {
nextKeyType = nextKey.getDataType();
} else if (!nextKeyType.isValueCompatible(nextKey)) {
throw new IllegalArgumentException("Expected " + nextKeyType.getName() + ", got " +
- nextKey.getDataType().getName() + ".");
+ nextKey.getDataType().getName());
}
FieldValue prevVal = entry.getValue();
FieldValue nextVal = convert(prevVal);
@@ -86,7 +86,7 @@ public abstract class FieldValueConverter {
nextValType = nextVal.getDataType();
} else if (!nextValType.isValueCompatible(nextVal)) {
throw new IllegalArgumentException("Expected " + nextValType.getName() + ", got " +
- nextVal.getDataType().getName() + ".");
+ nextVal.getDataType().getName());
}
next.put(nextKey, nextVal);
}
@@ -100,7 +100,7 @@ public abstract class FieldValueConverter {
@SuppressWarnings({ "unchecked", "rawtypes" })
private FieldValue convertWset(WeightedSet val) {
- Map<FieldValue, Integer> next = new LinkedHashMap<FieldValue, Integer>();
+ Map<FieldValue, Integer> next = new LinkedHashMap<>();
DataType nextType = null;
for (Iterator<FieldValue> it = val.fieldValueIterator(); it.hasNext();) {
FieldValue prevKey = it.next();
@@ -114,7 +114,7 @@ public abstract class FieldValueConverter {
nextType = nextKey.getDataType();
} else if (!nextType.isValueCompatible(nextKey)) {
throw new IllegalArgumentException("Expected " + nextType.getName() + ", got " +
- nextKey.getDataType().getName() + ".");
+ nextKey.getDataType().getName());
}
next.put(nextKey, prevVal);
}
@@ -143,7 +143,7 @@ public abstract class FieldValueConverter {
}
/**
- * Returns whether or not the given {@link FieldValue} should be converted. If this method returns <em>false</em>,
+ * Returns whether the given {@link FieldValue} should be converted. If this method returns <em>false</em>,
* the converter will proceed to traverse the value itself to see if its internal can be converted.
*
* @param value the value to check
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapter.java
index f36c44539c7..bab1f5fe7c0 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapter.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapter.java
@@ -41,7 +41,7 @@ public class SimpleDocumentAdapter implements DocumentAdapter {
try {
return input.getDataType().buildFieldPath(fieldName).getResultingDataType();
} catch (IllegalArgumentException e) {
- throw new VerificationException(exp, "Input field '" + fieldName + "' not found.");
+ throw new VerificationException(exp, "Input field '" + fieldName + "' not found");
}
}
@@ -67,12 +67,12 @@ public class SimpleDocumentAdapter implements DocumentAdapter {
public void tryOutputType(Expression exp, String fieldName, DataType valueType) {
Field field = output.getDataType().getField(fieldName);
if (field == null) {
- throw new VerificationException(exp, "Field '" + fieldName + "' not found.");
+ throw new VerificationException(exp, "Field '" + fieldName + "' not found");
}
DataType fieldType = field.getDataType();
if (!fieldType.isAssignableFrom(valueType)) {
throw new VerificationException(exp, "Can not assign " + valueType.getName() + " to field '" +
- fieldName + "' which is " + fieldType.getName() + ".");
+ fieldName + "' which is " + fieldType.getName());
}
}
@@ -81,7 +81,7 @@ public class SimpleDocumentAdapter implements DocumentAdapter {
Field field = output.getField(fieldName);
if (field == null) {
throw new IllegalArgumentException("Field '" + fieldName + "' not found in document type '" +
- output.getDataType().getName() + "'.");
+ output.getDataType().getName());
}
output.setFieldValue(field, fieldValue);
return this;
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
index b7ee444975f..8fff5d488c2 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
@@ -42,28 +42,28 @@ public final class ArithmeticExpression extends CompositeExpression {
}
}
- private final Expression lhs;
+ private final Expression left;
private final Operator op;
- private final Expression rhs;
+ private final Expression right;
- public ArithmeticExpression(Expression lhs, Operator op, Expression rhs) {
- super(requiredInputType(lhs, rhs));
- lhs.getClass(); // throws NullPointerException
+ public ArithmeticExpression(Expression left, Operator op, Expression right) {
+ super(requiredInputType(left, right));
+ left.getClass(); // throws NullPointerException
op.getClass();
- rhs.getClass();
- this.lhs = lhs;
+ right.getClass();
+ this.left = left;
this.op = op;
- this.rhs = rhs;
+ this.right = right;
}
@Override
public ArithmeticExpression convertChildren(ExpressionConverter converter) {
// TODO: branch()?
- return new ArithmeticExpression(converter.convert(lhs), op, converter.convert(rhs));
+ return new ArithmeticExpression(converter.convert(left), op, converter.convert(right));
}
public Expression getLeftHandSide() {
- return lhs;
+ return left;
}
public Operator getOperator() {
@@ -71,21 +71,21 @@ public final class ArithmeticExpression extends CompositeExpression {
}
public Expression getRightHandSide() {
- return rhs;
+ return right;
}
@Override
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
- context.setValue(evaluate(context.setValue(input).execute(lhs).getValue(),
- context.setValue(input).execute(rhs).getValue()));
+ context.setValue(evaluate(context.setValue(input).execute(left).getValue(),
+ context.setValue(input).execute(right).getValue()));
}
@Override
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
- context.setValueType(evaluate(context.setValueType(input).execute(lhs).getValueType(),
- context.setValueType(input).execute(rhs).getValueType()));
+ context.setValueType(evaluate(context.setValueType(input).execute(left).getValueType(),
+ context.setValueType(input).execute(right).getValueType()));
}
private static DataType requiredInputType(Expression lhs, Expression rhs) {
@@ -99,7 +99,7 @@ public final class ArithmeticExpression extends CompositeExpression {
}
if (!lhsType.equals(rhsType)) {
throw new VerificationException(ArithmeticExpression.class, "Operands require conflicting input types, " +
- lhsType.getName() + " vs " + rhsType.getName() + ".");
+ lhsType.getName() + " vs " + rhsType.getName());
}
return lhsType;
}
@@ -111,7 +111,7 @@ public final class ArithmeticExpression extends CompositeExpression {
@Override
public String toString() {
- return lhs + " " + op + " " + rhs;
+ return left + " " + op + " " + right;
}
@Override
@@ -120,13 +120,13 @@ public final class ArithmeticExpression extends CompositeExpression {
return false;
}
ArithmeticExpression exp = (ArithmeticExpression)obj;
- if (!lhs.equals(exp.lhs)) {
+ if (!left.equals(exp.left)) {
return false;
}
if (!op.equals(exp.op)) {
return false;
}
- if (!rhs.equals(exp.rhs)) {
+ if (!right.equals(exp.right)) {
return false;
}
return true;
@@ -134,12 +134,12 @@ public final class ArithmeticExpression extends CompositeExpression {
@Override
public int hashCode() {
- return getClass().hashCode() + lhs.hashCode() + op.hashCode() + rhs.hashCode();
+ return getClass().hashCode() + left.hashCode() + op.hashCode() + right.hashCode();
}
private DataType evaluate(DataType lhs, DataType rhs) {
if (lhs == null || rhs == null) {
- throw new VerificationException(this, "Attempting to perform arithmetic on a null value.");
+ throw new VerificationException(this, "Attempting to perform arithmetic on a null value");
}
if (!(lhs instanceof NumericDataType) ||
!(rhs instanceof NumericDataType))
@@ -210,12 +210,12 @@ public final class ArithmeticExpression extends CompositeExpression {
return BigDecimal.valueOf(((LongFieldValue)value).getLong());
}
throw new IllegalArgumentException("Unsupported numeric field value type '" +
- value.getClass().getName() + "'.");
+ value.getClass().getName() + "'");
}
@Override
public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
- lhs.select(predicate, operation);
- rhs.select(predicate, operation);
+ left.select(predicate, operation);
+ right.select(predicate, operation);
}
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeExpression.java
index 2474dadab77..d5b5fa2ddfa 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeExpression.java
@@ -23,11 +23,11 @@ public final class Base64DecodeExpression extends Expression {
return;
}
if (input.length() > 12) {
- throw new NumberFormatException("Base64 value '" + input + "' is out of range.");
+ throw new NumberFormatException("Base64 value '" + input + "' is out of range");
}
byte[] decoded = Base64.getDecoder().decode(input);
if (decoded == null || decoded.length == 0) {
- throw new NumberFormatException("Illegal base64 value '" + input + "'.");
+ throw new NumberFormatException("Illegal base64 value '" + input + "'");
}
long output = 0;
for (int i = decoded.length; --i >= 0;) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
index 564ab015e10..b495a0b3bbf 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
@@ -62,7 +62,7 @@ public final class CatExpression extends ExpressionList<Expression> {
DataType val = context.setValueType(input).execute(exp).getValueType();
types.add(val);
if (val == null) {
- throw new VerificationException(this, "Attempting to concatenate a null value (" + exp + ").");
+ throw new VerificationException(this, "Attempting to concatenate a null value (" + exp + ")");
}
}
context.setValueType(resolveOutputType(types));
@@ -78,7 +78,7 @@ public final class CatExpression extends ExpressionList<Expression> {
prev = next;
} else if (!prev.isAssignableFrom(next)) {
throw new VerificationException(CatExpression.class, "Operands require conflicting input types, " +
- prev.getName() + " vs " + next.getName() + ".");
+ prev.getName() + " vs " + next.getName());
}
}
return prev;
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
index 5dbb9292a9d..991cbd30433 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
@@ -64,14 +64,14 @@ public class ChoiceExpression extends ExpressionList<Expression> {
previousInput = thisInput;
else if (thisInput != null && !previousInput.isAssignableFrom(thisInput))
throw new VerificationException(ScriptExpression.class, "Choice expression require conflicting input types, " +
- previousInput.getName() + " vs " + thisInput.getName() + ".");
+ previousInput.getName() + " vs " + thisInput.getName());
DataType thisOutput = choice.createdOutputType();
if (previousOutput == null)
previousOutput = thisOutput;
else if (thisOutput != null && !previousOutput.isAssignableFrom(thisOutput))
throw new VerificationException(ScriptExpression.class, "Choice expression produce conflicting output types, " +
- previousOutput.getName() + " vs " + thisOutput.getName() + ".");
+ previousOutput.getName() + " vs " + thisOutput.getName());
}
return previousInput;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ConstantExpression.java
index f7348c24af5..b44d4844c4d 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ConstantExpression.java
@@ -7,17 +7,18 @@ import com.yahoo.document.datatypes.LongFieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.text.StringUtilities;
+import java.util.Objects;
+
/**
* @author Simon Thoresen Hult
*/
-public final class SetValueExpression extends Expression {
+public final class ConstantExpression extends Expression {
private final FieldValue value;
- public SetValueExpression(FieldValue value) {
+ public ConstantExpression(FieldValue value) {
super(null);
- value.getClass(); // throws NullPointerException
- this.value = value;
+ this.value = Objects.requireNonNull(value);
}
public FieldValue getValue() {
@@ -52,7 +53,7 @@ public final class SetValueExpression extends Expression {
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof SetValueExpression rhs)) return false;
+ if (!(obj instanceof ConstantExpression rhs)) return false;
if (!value.equals(rhs.value)) return false;
return true;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/EmbedExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/EmbedExpression.java
index 5ee5fea3158..0407e17596b 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/EmbedExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/EmbedExpression.java
@@ -116,7 +116,7 @@ public class EmbedExpression extends Expression {
String outputField = context.getOutputField();
if (outputField == null)
throw new VerificationException(this, "No output field in this statement: " +
- "Don't know what tensor type to embed into.");
+ "Don't know what tensor type to embed into");
targetType = toTargetTensor(context.getInputType(this, outputField));
if ( ! validTarget(targetType))
throw new VerificationException(this, "The embedding target field must either be a dense 1d tensor, " +
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExecutionContext.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExecutionContext.java
index f01f2fcc9fb..7f0abfd64db 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExecutionContext.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExecutionContext.java
@@ -50,7 +50,7 @@ public class ExecutionContext implements FieldTypeAdapter, FieldValueAdapter, Cl
@Override
public FieldValue getInputValue(String fieldName) {
if (adapter == null) {
- throw new IllegalStateException("Can not get field '" + fieldName + "' because adapter is null.");
+ throw new IllegalStateException("Can not get field '" + fieldName + "' because adapter is null");
}
return adapter.getInputValue(fieldName);
}
@@ -58,7 +58,7 @@ public class ExecutionContext implements FieldTypeAdapter, FieldValueAdapter, Cl
@Override
public FieldValue getInputValue(FieldPath fieldPath) {
if (adapter == null) {
- throw new IllegalStateException("Can not get field '" + fieldPath + "' because adapter is null.");
+ throw new IllegalStateException("Can not get field '" + fieldPath + "' because adapter is null");
}
return adapter.getInputValue(fieldPath);
}
@@ -71,7 +71,7 @@ public class ExecutionContext implements FieldTypeAdapter, FieldValueAdapter, Cl
@Override
public ExecutionContext setOutputValue(Expression exp, String fieldName, FieldValue fieldValue) {
if (adapter == null)
- throw new IllegalStateException("Can not set field '" + fieldName + "' because adapter is null.");
+ throw new IllegalStateException("Can not set field '" + fieldName + "' because adapter is null");
adapter.setOutputValue(exp, fieldName, fieldValue);
return this;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
index f498b871096..a6f117beb40 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
@@ -28,8 +28,8 @@ public abstract class Expression extends Selectable {
* Creates an expression
*
* @param inputType the type of the input this expression can work with.
- * UnresolvedDataType.INSTANCE if it works with any type,
- * and null if it does not consume any input.
+ * UnresolvedDataType.INSTANCE if it works with any type,
+ * and null if it does not consume any input.
*/
protected Expression(DataType inputType) {
this.inputType = inputType;
@@ -93,7 +93,7 @@ public abstract class Expression extends Selectable {
}
if (!inputType.isValueCompatible(input)) {
throw new IllegalArgumentException("Expression '" + this + "' expected " + inputType.getName() +
- " input, got " + input.getDataType().getName() + ".");
+ " input, got " + input.getDataType().getName());
}
}
doExecute(context);
@@ -102,7 +102,7 @@ public abstract class Expression extends Selectable {
FieldValue output = context.getValue();
if (output != null && !outputType.isValueCompatible(output)) {
throw new IllegalStateException("Expression '" + this + "' expected " + outputType.getName() +
- " output, got " + output.getDataType().getName() + ".");
+ " output, got " + output.getDataType().getName());
}
}
return context.getValue();
@@ -163,14 +163,14 @@ public abstract class Expression extends Selectable {
if (inputType != null) {
DataType input = context.getValueType();
if (input == null) {
- throw new VerificationException(this, "Expected " + inputType.getName() + " input, got null.");
+ throw new VerificationException(this, "Expected " + inputType.getName() + " input, but no input is specified");
}
if (input.getPrimitiveType() == UnresolvedDataType.INSTANCE) {
- throw new VerificationException(this, "Failed to resolve input type.");
+ throw new VerificationException(this, "Failed to resolve input type");
}
if (!inputType.isAssignableFrom(input)) {
throw new VerificationException(this, "Expected " + inputType.getName() + " input, got " +
- input.getName() + ".");
+ input.getName());
}
}
doVerify(context);
@@ -178,14 +178,13 @@ public abstract class Expression extends Selectable {
if (outputType != null) {
DataType output = context.getValueType();
if (output == null) {
- throw new VerificationException(this, "Expected " + outputType.getName() + " output, got null.");
+ throw new VerificationException(this, "Expected " + outputType.getName() + " output, but no output is specified");
}
if (output.getPrimitiveType() == UnresolvedDataType.INSTANCE) {
- throw new VerificationException(this, "Failed to resolve output type.");
+ throw new VerificationException(this, "Failed to resolve output type");
}
if (!outputType.isAssignableFrom(output)) {
- throw new VerificationException(this, "Expected " + outputType.getName() + " output, got " +
- output.getName() + ".");
+ throw new VerificationException(this, "Expected " + outputType.getName() + " output, got " + output.getName());
}
}
return context.getValueType();
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
index 57de66f80a0..f3e9e33d841 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
@@ -21,9 +21,9 @@ public abstract class ExpressionList<T extends Expression> extends CompositeExpr
private final List<T> expressions = new LinkedList<T>();
- protected ExpressionList(Iterable<? extends T> lst, DataType inputType) {
+ protected ExpressionList(Iterable<? extends T> expressions, DataType inputType) {
super(inputType);
- for (T exp : lst) {
+ for (T exp : expressions) {
this.expressions.add(exp);
}
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
index 3053a391823..7e32c93faff 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
@@ -52,7 +52,7 @@ public final class ForEachExpression extends CompositeExpression {
context.setValue(new MyConverter(context, exp).convert(input));
} else {
throw new IllegalArgumentException("Expected Array, Struct or WeightedSet input, got " +
- input.getDataType().getName() + ".");
+ input.getDataType().getName());
}
}
@@ -80,13 +80,13 @@ public final class ForEachExpression extends CompositeExpression {
DataType structValueType = context.setValueType(fieldType).execute(exp).getValueType();
if (!fieldType.isAssignableFrom(structValueType))
throw new VerificationException(this, "Expected " + fieldType.getName() + " output, got " +
- structValueType.getName() + ".");
+ structValueType.getName());
}
context.setValueType(valueType);
}
else {
throw new VerificationException(this, "Expected Array, Struct or WeightedSet input, got " +
- valueType.getName() + ".");
+ valueType.getName());
}
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldExpression.java
index ecb6980f795..8a0fc9a56ec 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldExpression.java
@@ -27,7 +27,7 @@ public final class GetFieldExpression extends Expression {
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
if (!(input instanceof StructuredFieldValue struct)) {
- throw new IllegalArgumentException("Expected structured input, got " + input.getDataType().getName() + ".");
+ throw new IllegalArgumentException("Expected structured input, got " + input.getDataType().getName());
}
Field field = struct.getField(fieldName);
if (field == null) {
@@ -41,7 +41,7 @@ public final class GetFieldExpression extends Expression {
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
if (!(input instanceof StructuredDataType)) {
- throw new VerificationException(this, "Expected structured input, got " + input.getName() + ".");
+ throw new VerificationException(this, "Expected structured input, got " + input.getName());
}
Field field = ((StructuredDataType)input).getField(fieldName);
if (field == null) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarExpression.java
index 4ebf5da2ff8..54e85be1986 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarExpression.java
@@ -28,7 +28,7 @@ public final class GetVarExpression extends Expression {
protected void doVerify(VerificationContext context) {
DataType input = context.getVariable(varName);
if (input == null) {
- throw new VerificationException(this, "Variable '" + varName + "' not found.");
+ throw new VerificationException(this, "Variable '" + varName + "' not found");
}
context.setValueType(input);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HashExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HashExpression.java
index 2952692b5d0..3b4c1b432bf 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HashExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HashExpression.java
@@ -63,7 +63,7 @@ public class HashExpression extends Expression {
String outputField = context.getOutputField();
if (outputField == null)
throw new VerificationException(this, "No output field in this statement: " +
- "Don't know what value to hash to.");
+ "Don't know what value to hash to");
DataType outputFieldType = context.getInputType(this, outputField);
if ( ! canStoreHash(outputFieldType))
throw new VerificationException(this, "The type of the output field " + outputField +
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeExpression.java
index 93f101a422e..4a2c7381ac0 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeExpression.java
@@ -28,12 +28,12 @@ public final class HexDecodeExpression extends Expression {
try {
output = new BigInteger(input, 16);
} catch (NumberFormatException e) {
- throw new NumberFormatException("Illegal hex value '" + input + "'.");
+ throw new NumberFormatException("Illegal hex value '" + input + "'");
}
if (output.bitLength() > 64) {
- throw new NumberFormatException("Hex value '" + input + "' is out of range.");
+ throw new NumberFormatException("Hex value '" + input + "' is out of range");
}
- if (output.compareTo(BigInteger.ZERO) == 1 && output.bitLength() == 64) {
+ if (output.compareTo(BigInteger.ZERO) > 0 && output.bitLength() == 64) {
output = output.subtract(ULONG_MAX); // flip to negative
}
context.setValue(new LongFieldValue(output.longValue()));
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
index f05795aa234..e0fb4e0337a 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
@@ -27,7 +27,7 @@ public final class IfThenExpression extends CompositeExpression {
private final String img;
- private Comparator(String img) {
+ Comparator(String img) {
this.img = img;
}
@@ -38,77 +38,67 @@ public final class IfThenExpression extends CompositeExpression {
}
- private final Expression lhs;
- private final Comparator cmp;
- private final Expression rhs;
+ private final Expression left;
+ private final Comparator comparator;
+ private final Expression right;
private final Expression ifTrue;
private final Expression ifFalse;
- public IfThenExpression(Expression lhs, Comparator cmp, Expression rhs, Expression ifTrue) {
- this(lhs, cmp, rhs, ifTrue, null);
+ public IfThenExpression(Expression lhs, Comparator cmp, Expression right, Expression ifTrue) {
+ this(lhs, cmp, right, ifTrue, null);
}
- public IfThenExpression(Expression lhs, Comparator cmp, Expression rhs, Expression ifTrue, Expression ifFalse) {
- super(resolveInputType(lhs, rhs, ifTrue, ifFalse));
- this.lhs = lhs;
- this.cmp = cmp;
- this.rhs = rhs;
+ public IfThenExpression(Expression lhs, Comparator cmp, Expression right, Expression ifTrue, Expression ifFalse) {
+ super(resolveInputType(lhs, right, ifTrue, ifFalse));
+ this.left = lhs;
+ this.comparator = cmp;
+ this.right = right;
this.ifTrue = ifTrue;
this.ifFalse = ifFalse;
}
@Override
public IfThenExpression convertChildren(ExpressionConverter converter) {
- return new IfThenExpression(converter.branch().convert(lhs),
- cmp,
- converter.branch().convert(rhs),
+ return new IfThenExpression(converter.branch().convert(left),
+ comparator,
+ converter.branch().convert(right),
converter.branch().convert(ifTrue),
converter.branch().convert(ifFalse));
}
@Override
public void setStatementOutput(DocumentType documentType, Field field) {
- lhs.setStatementOutput(documentType, field);
- rhs.setStatementOutput(documentType, field);
+ left.setStatementOutput(documentType, field);
+ right.setStatementOutput(documentType, field);
ifTrue.setStatementOutput(documentType, field);
ifFalse.setStatementOutput(documentType, field);
}
- public Expression getLeftHandSide() {
- return lhs;
- }
+ public Expression getLeftHandSide() { return left; }
- public Comparator getComparator() {
- return cmp;
- }
+ public Comparator getComparator() { return comparator; }
- public Expression getRightHandSide() {
- return rhs;
- }
+ public Expression getRightHandSide() { return right; }
- public Expression getIfTrueExpression() {
- return ifTrue;
- }
+ public Expression getIfTrueExpression() { return ifTrue; }
- public Expression getIfFalseExpression() {
- return ifFalse;
- }
+ public Expression getIfFalseExpression() { return ifFalse; }
@Override
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
- FieldValue lhsVal = context.setValue(input).execute(lhs).getValue();
- if (lhsVal == null) {
+ FieldValue leftValue = context.setValue(input).execute(left).getValue();
+ if (leftValue == null) {
context.setValue(null);
return;
}
- FieldValue rhsVal = context.setValue(input).execute(rhs).getValue();
- if (rhsVal == null) {
+ FieldValue rightValue = context.setValue(input).execute(right).getValue();
+ if (rightValue == null) {
context.setValue(null);
return;
}
context.setValue(input);
- if (isTrue(lhsVal, cmp, rhsVal)) {
+ if (isTrue(leftValue, comparator, rightValue)) {
ifTrue.execute(context);
} else if (ifFalse != null) {
ifFalse.execute(context);
@@ -118,17 +108,19 @@ public final class IfThenExpression extends CompositeExpression {
@Override
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
- context.setValueType(input).execute(lhs);
- context.setValueType(input).execute(rhs);
- context.setValueType(input).execute(ifTrue);
- context.setValueType(input).execute(ifFalse);
- context.setValueType(input);
+ context.setValueType(input).execute(left);
+ context.setValueType(input).execute(right);
+ var trueValue = context.setValueType(input).execute(ifTrue);
+ var falseValue = context.setValueType(input).execute(ifFalse);
+ var valueType = trueValue.getValueType().isAssignableFrom(falseValue.getValueType()) ?
+ trueValue.getValueType() : falseValue.getValueType();
+ context.setValueType(valueType);
}
@Override
public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
- select(lhs, predicate, operation);
- select(rhs, predicate, operation);
+ select(left, predicate, operation);
+ select(right, predicate, operation);
select(ifTrue, predicate, operation);
select(ifFalse, predicate, operation);
}
@@ -146,13 +138,19 @@ public final class IfThenExpression extends CompositeExpression {
@Override
public DataType createdOutputType() {
- return null;
+ DataType ifTrueType = ifTrue.createdOutputType();
+ DataType ifFalseType = ifFalse == null ? null : ifFalse.createdOutputType();
+ if (ifTrueType == null || ifFalseType == null) return null;
+ if (ifTrueType.isAssignableFrom(ifFalseType))
+ return ifTrueType;
+ else
+ return ifFalseType;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
- ret.append("if (").append(lhs).append(" ").append(cmp).append(" ").append(rhs).append(") ");
+ ret.append("if (").append(left).append(" ").append(comparator).append(" ").append(right).append(") ");
ret.append(toScriptBlock(ifTrue));
if (ifFalse != null) {
ret.append(" else ").append(toScriptBlock(ifFalse));
@@ -165,13 +163,13 @@ public final class IfThenExpression extends CompositeExpression {
if (!(obj instanceof IfThenExpression exp)) {
return false;
}
- if (!lhs.equals(exp.lhs)) {
+ if (!left.equals(exp.left)) {
return false;
}
- if (!cmp.equals(exp.cmp)) {
+ if (!comparator.equals(exp.comparator)) {
return false;
}
- if (!rhs.equals(exp.rhs)) {
+ if (!right.equals(exp.right)) {
return false;
}
if (!ifTrue.equals(exp.ifTrue)) {
@@ -185,7 +183,7 @@ public final class IfThenExpression extends CompositeExpression {
@Override
public int hashCode() {
- int ret = getClass().hashCode() + lhs.hashCode() + cmp.hashCode() + rhs.hashCode() + ifTrue.hashCode();
+ int ret = getClass().hashCode() + left.hashCode() + comparator.hashCode() + right.hashCode() + ifTrue.hashCode();
if (ifFalse != null) {
ret += ifFalse.hashCode();
}
@@ -201,7 +199,7 @@ public final class IfThenExpression extends CompositeExpression {
}
if (!prev.equals(next)) {
throw new VerificationException(IfThenExpression.class, "Operands require conflicting input types, " +
- prev.getName() + " vs " + next.getName() + ".");
+ prev.getName() + " vs " + next.getName());
}
return prev;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/InputExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/InputExpression.java
index bba1b09cda2..8534e58694e 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/InputExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/InputExpression.java
@@ -42,7 +42,7 @@ public final class InputExpression extends Expression {
protected void doVerify(VerificationContext context) {
DataType val = context.getInputType(this, fieldName);
if (val == null)
- throw new VerificationException(this, "Field '" + fieldName + "' not found.");
+ throw new VerificationException(this, "Field '" + fieldName + "' not found");
context.setValueType(val);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/JoinExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/JoinExpression.java
index 1c3582ea695..39325385dde 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/JoinExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/JoinExpression.java
@@ -31,7 +31,7 @@ public final class JoinExpression extends Expression {
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
if (!(input instanceof Array)) {
- throw new IllegalArgumentException("Expected Array input, got " + input.getDataType().getName() + ".");
+ throw new IllegalArgumentException("Expected Array input, got " + input.getDataType().getName());
}
StringBuilder output = new StringBuilder();
for (Iterator<FieldValue> it = ((Array)input).fieldValueIterator(); it.hasNext(); ) {
@@ -47,7 +47,7 @@ public final class JoinExpression extends Expression {
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
if (!(input instanceof ArrayDataType)) {
- throw new VerificationException(this, "Expected Array input, got " + input.getName() + ".");
+ throw new VerificationException(this, "Expected Array input, got " + input.getName());
}
context.setValueType(createdOutputType());
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateExpression.java
index d4b6f2e0a0a..97bbb1494e0 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateExpression.java
@@ -54,10 +54,10 @@ public final class OptimizePredicateExpression extends Expression {
DataType input = ctx.getVariable(var);
if (input == null) {
if (required) {
- throw new VerificationException(this, "Variable '" + var + "' must be set.");
+ throw new VerificationException(this, "Variable '" + var + "' must be set");
}
} else if (input != type) {
- throw new VerificationException(this, "Variable '" + var + "' must have type " + type.getName() + ".");
+ throw new VerificationException(this, "Variable '" + var + "' must have type " + type.getName());
}
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
index 1a640c9924e..f9ceed4cb34 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
@@ -29,12 +29,12 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
this(Collections.emptyList());
}
- public ScriptExpression(StatementExpression... lst) {
- this(Arrays.asList(lst));
+ public ScriptExpression(StatementExpression... statements) {
+ this(Arrays.asList(statements));
}
- public ScriptExpression(Collection<? extends StatementExpression> lst) {
- super(lst, resolveInputType(lst));
+ public ScriptExpression(Collection<? extends StatementExpression> statements) {
+ super(statements, resolveInputType(statements));
}
@Override
@@ -67,10 +67,8 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
@Override
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
- for (Expression exp : this) {
+ for (Expression exp : this)
context.setValueType(input).execute(exp);
- }
- context.setValueType(input);
}
private static DataType resolveInputType(Collection<? extends StatementExpression> list) {
@@ -81,7 +79,7 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
prev = next;
} else if (next != null && !prev.isAssignableFrom(next)) {
throw new VerificationException(ScriptExpression.class, "Statements require conflicting input types, " +
- prev.getName() + " vs " + next.getName() + ".");
+ prev.getName() + " vs " + next.getName());
}
}
return prev;
@@ -89,7 +87,9 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
@Override
public DataType createdOutputType() {
- return null;
+ var expressions = asList();
+ if (expressions.isEmpty()) return null;
+ return (expressions.get(expressions.size() - 1)).createdOutputType();
}
@Override
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
index bb8111f358e..90b2253b520 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
@@ -65,7 +65,7 @@ public final class SelectInputExpression extends CompositeExpression {
for (Pair<String, Expression> entry : cases) {
DataType val = context.getInputType(this, entry.getFirst());
if (val == null) {
- throw new VerificationException(this, "Field '" + entry.getFirst() + "' not found.");
+ throw new VerificationException(this, "Field '" + entry.getFirst() + "' not found");
}
context.setValueType(val).execute(entry.getSecond());
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarExpression.java
index a855ba86c9c..c80efbf7d19 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarExpression.java
@@ -30,7 +30,7 @@ public final class SetVarExpression extends Expression {
DataType prev = context.getVariable(varName);
if (prev != null && !prev.equals(next)) {
throw new VerificationException(this, "Attempting to assign conflicting types to variable '" + varName +
- "', " + prev.getName() + " vs " + next.getName() + ".");
+ "', " + prev.getName() + " vs " + next.getName());
}
context.setVariable(varName, next);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
index 75f206ef47d..da067935d18 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
@@ -73,12 +73,12 @@ public final class StatementExpression extends ExpressionList<Expression> {
context.execute(expression);
}
- private static DataType resolveInputType(Iterable<Expression> lst) {
- for (Expression exp : lst) {
- DataType type = exp.requiredInputType();
+ private static DataType resolveInputType(Iterable<Expression> expressions) {
+ for (Expression expression : expressions) {
+ DataType type = expression.requiredInputType();
if (type != null) return type;
- type = exp.createdOutputType();
+ type = expression.createdOutputType();
if (type != null) return null;
}
return null;
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
index c7cf7066483..3bf67ff9c5d 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
@@ -70,7 +70,7 @@ public final class SwitchExpression extends CompositeExpression {
if (input != null) {
if (!(input instanceof StringFieldValue)) {
throw new IllegalArgumentException("Expected " + DataType.STRING.getName() + " input, got " +
- input.getDataType().getName() + ".");
+ input.getDataType().getName());
}
exp = cases.get(String.valueOf(input));
}
@@ -95,11 +95,11 @@ public final class SwitchExpression extends CompositeExpression {
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
if (input == null) {
- throw new VerificationException(this, "Expected " + DataType.STRING.getName() + " input, got null.");
+ throw new VerificationException(this, "Expected " + DataType.STRING.getName() + " input, but no input is specified");
}
if (input != DataType.STRING) {
throw new VerificationException(this, "Expected " + DataType.STRING.getName() + " input, got " +
- input.getName() + ".");
+ input.getName());
}
for (Expression exp : cases.values()) {
context.setValueType(input).execute(exp);
diff --git a/indexinglanguage/src/main/javacc/IndexingParser.jj b/indexinglanguage/src/main/javacc/IndexingParser.jj
index d559d9b7260..3c67a468aea 100644
--- a/indexinglanguage/src/main/javacc/IndexingParser.jj
+++ b/indexinglanguage/src/main/javacc/IndexingParser.jj
@@ -32,7 +32,6 @@ import com.yahoo.document.datatypes.*;
import com.yahoo.text.StringUtilities;
import com.yahoo.vespa.indexinglanguage.expressions.*;
import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig;
-import com.yahoo.language.process.StemMode;
import com.yahoo.language.process.Embedder;
import com.yahoo.language.Linguistics;
@@ -592,7 +591,7 @@ Expression setValueExp() :
}
{
( val = fieldValue() )
- { return new SetValueExpression(val); }
+ { return new ConstantExpression(val); }
}
Expression setVarExp() :
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
index 8aeaa084e1b..329ea6c95ba 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
@@ -10,6 +10,7 @@ import com.yahoo.vespa.indexinglanguage.expressions.Base64DecodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Base64EncodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.CatExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ClearStateExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ConstantExpression;
import com.yahoo.vespa.indexinglanguage.expressions.EchoExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Expression;
import com.yahoo.vespa.indexinglanguage.expressions.ForEachExpression;
@@ -32,7 +33,6 @@ import com.yahoo.vespa.indexinglanguage.expressions.RandomExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SelectInputExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SetLanguageExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.SetValueExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SetVarExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SplitExpression;
import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
@@ -103,7 +103,7 @@ public class ExpressionConverterTestCase {
assertConvertable(new SelectInputExpression(new Pair<String, Expression>("foo", new IndexExpression("bar")),
new Pair<String, Expression>("bar", new IndexExpression("foo"))));
assertConvertable(new SetLanguageExpression());
- assertConvertable(new SetValueExpression(new IntegerFieldValue(69)));
+ assertConvertable(new ConstantExpression(new IntegerFieldValue(69)));
assertConvertable(new SetVarExpression("foo"));
assertConvertable(new SplitExpression("foo"));
assertConvertable(new StatementExpression(new InputExpression("foo")));
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizerTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizerTestCase.java
index f16e170c981..d9cb89d6be5 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizerTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionOptimizerTestCase.java
@@ -1,15 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.indexinglanguage;
-import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.IntegerFieldValue;
import com.yahoo.vespa.indexinglanguage.expressions.*;
import com.yahoo.vespa.indexinglanguage.parser.ParseException;
import org.junit.Test;
-import java.util.HashMap;
-import java.util.Map;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -26,7 +22,7 @@ public class ExpressionOptimizerTestCase {
public void requireThatStatementsBeforeOneThatIgnoresInputAreRemoved() {
checkStatementThatIgnoresInput(new InputExpression("foo"));
checkStatementThatIgnoresInput(new NowExpression());
- checkStatementThatIgnoresInput(new SetValueExpression(new IntegerFieldValue(42)));
+ checkStatementThatIgnoresInput(new ConstantExpression(new IntegerFieldValue(42)));
checkStatementThatIgnoresInput(new HostNameExpression());
checkStatementThatIgnoresInput(new RandomExpression(42));
checkStatementThatIgnoresInput(new ArithmeticExpression(
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionVisitorTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionVisitorTestCase.java
index 8683874d44d..84e5c730fa6 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionVisitorTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionVisitorTestCase.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.indexinglanguage;
import com.yahoo.collections.Pair;
import com.yahoo.document.datatypes.IntegerFieldValue;
-import com.yahoo.language.Linguistics;
import com.yahoo.language.simple.SimpleLinguistics;
import com.yahoo.vespa.indexinglanguage.expressions.ArithmeticExpression;
import com.yahoo.vespa.indexinglanguage.expressions.AttributeExpression;
@@ -11,6 +10,7 @@ import com.yahoo.vespa.indexinglanguage.expressions.Base64DecodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Base64EncodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.CatExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ClearStateExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ConstantExpression;
import com.yahoo.vespa.indexinglanguage.expressions.EchoExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Expression;
import com.yahoo.vespa.indexinglanguage.expressions.ForEachExpression;
@@ -33,7 +33,6 @@ import com.yahoo.vespa.indexinglanguage.expressions.RandomExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SelectInputExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SetLanguageExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.SetValueExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SetVarExpression;
import com.yahoo.vespa.indexinglanguage.expressions.SplitExpression;
import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
@@ -100,7 +99,7 @@ public class ExpressionVisitorTestCase {
assertCount(3, new SelectInputExpression(new Pair<String, Expression>("foo", new IndexExpression("bar")),
new Pair<String, Expression>("bar", new IndexExpression("foo"))));
assertCount(1, new SetLanguageExpression());
- assertCount(1, new SetValueExpression(new IntegerFieldValue(69)));
+ assertCount(1, new ConstantExpression(new IntegerFieldValue(69)));
assertCount(1, new SetVarExpression("foo"));
assertCount(1, new SplitExpression("foo"));
assertCount(2, new StatementExpression(new InputExpression("foo")));
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ScriptTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ScriptTestCase.java
index 98458dd965c..77b38dd7549 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ScriptTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ScriptTestCase.java
@@ -69,7 +69,7 @@ public class ScriptTestCase {
fail();
} catch (VerificationException e) {
assertEquals(e.getExpressionType(), ScriptExpression.class);
- assertEquals("Expected any input, got null.", e.getMessage());
+ assertEquals("Expected any input, but no input is specified", e.getMessage());
}
}
@@ -85,7 +85,7 @@ public class ScriptTestCase {
}
@Test
- public void requireThatIfExpressionPassesOriginalInputAlong() throws ParseException {
+ public void requireThatIfExpressionReturnsTheProducedType() throws ParseException {
Document input = new Document(type, "id:scheme:mytype::");
Document output = Expression.execute(Expression.fromString("'foo' | if (1 < 2) { 'bar' | index 'out-1' } else { 'baz' | index 'out-1' } | index 'out-1'"), input);
assertNotNull(output);
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapterTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapterTestCase.java
index 76f96a80bb9..5a85d8ef5c3 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapterTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/SimpleDocumentAdapterTestCase.java
@@ -48,7 +48,7 @@ public class SimpleDocumentAdapterTestCase {
adapter.getInputType(null, "foo");
fail();
} catch (VerificationException e) {
- assertEquals("Input field 'foo' not found.", e.getMessage());
+ assertEquals("Input field 'foo' not found", e.getMessage());
}
assertNull(adapter.getInputValue("foo"));
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ValueTransformProviderTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ValueTransformProviderTestCase.java
index d0e9356ee76..f7eeca6f70a 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ValueTransformProviderTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ValueTransformProviderTestCase.java
@@ -111,14 +111,14 @@ public class ValueTransformProviderTestCase {
@Test
public void requireThatSelectInputBranchesAreManagedSeparately() {
- List<Pair<String, Expression>> before = new LinkedList<Pair<String, Expression>>();
- before.add(new Pair<String, Expression>("a", new IndexExpression("b")));
- before.add(new Pair<String, Expression>("c", new IndexExpression("d")));
+ List<Pair<String, Expression>> before = new LinkedList<>();
+ before.add(new Pair<>("a", new IndexExpression("b")));
+ before.add(new Pair<>("c", new IndexExpression("d")));
- List<Pair<String, Expression>> after = new LinkedList<Pair<String, Expression>>();
- after.add(new Pair<String, Expression>("a", new StatementExpression(new LowerCaseExpression(),
+ List<Pair<String, Expression>> after = new LinkedList<>();
+ after.add(new Pair<>("a", new StatementExpression(new LowerCaseExpression(),
new IndexExpression("b"))));
- after.add(new Pair<String, Expression>("c", new StatementExpression(new LowerCaseExpression(),
+ after.add(new Pair<>("c", new StatementExpression(new LowerCaseExpression(),
new IndexExpression("d"))));
assertProvided(new StatementExpression(new SelectInputExpression(before),
@@ -130,11 +130,11 @@ public class ValueTransformProviderTestCase {
@Test
public void requireThatSwitchBranchesAreManagedSeparately() {
- Map<String, Expression> before = new LinkedHashMap<String, Expression>();
+ Map<String, Expression> before = new LinkedHashMap<>();
before.put("a", new IndexExpression("b"));
before.put("c", new IndexExpression("d"));
- Map<String, Expression> after = new LinkedHashMap<String, Expression>();
+ Map<String, Expression> after = new LinkedHashMap<>();
after.put("a", new StatementExpression(new LowerCaseExpression(),
new IndexExpression("b")));
after.put("c", new StatementExpression(new LowerCaseExpression(),
@@ -170,4 +170,5 @@ public class ValueTransformProviderTestCase {
return new LowerCaseExpression();
}
}
+
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticTestCase.java
index 91d8f833701..f6dc7f839ed 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticTestCase.java
@@ -64,13 +64,13 @@ public class ArithmeticTestCase {
SimpleExpression.newOutput(DataType.INT), null);
assertVerifyThrows(SimpleExpression.newOutput(null), Operator.ADD,
SimpleExpression.newOutput(DataType.INT), null,
- "Attempting to perform arithmetic on a null value.");
+ "Attempting to perform arithmetic on a null value");
assertVerifyThrows(SimpleExpression.newOutput(DataType.INT), Operator.ADD,
SimpleExpression.newOutput(null), null,
- "Attempting to perform arithmetic on a null value.");
+ "Attempting to perform arithmetic on a null value");
assertVerifyThrows(SimpleExpression.newOutput(null), Operator.ADD,
SimpleExpression.newOutput(null), null,
- "Attempting to perform arithmetic on a null value.");
+ "Attempting to perform arithmetic on a null value");
assertVerifyThrows(SimpleExpression.newOutput(DataType.INT), Operator.ADD,
SimpleExpression.newOutput(DataType.STRING), null,
"Attempting to perform unsupported arithmetic: [int] + [string]");
@@ -95,7 +95,7 @@ public class ArithmeticTestCase {
new SimpleExpression(DataType.INT), DataType.INT);
assertVerifyThrows(new SimpleExpression(DataType.INT), Operator.ADD,
new SimpleExpression(DataType.STRING), null,
- "Operands require conflicting input types, int vs string.");
+ "Operands require conflicting input types, int vs string");
}
@Test
@@ -114,23 +114,23 @@ public class ArithmeticTestCase {
@Test
public void requireThatArithmeticWithNullEvaluatesToNull() {
assertNull(newArithmetic(new SimpleExpression(), Operator.ADD,
- new SetValueExpression(new LongFieldValue(69))).execute());
- assertNull(newArithmetic(new SetValueExpression(new LongFieldValue(69)), Operator.ADD,
+ new ConstantExpression(new LongFieldValue(69))).execute());
+ assertNull(newArithmetic(new ConstantExpression(new LongFieldValue(69)), Operator.ADD,
new SimpleExpression()).execute());
}
@Test
public void requireThatNonNumericOperandThrows() {
try {
- newArithmetic(new SetValueExpression(new IntegerFieldValue(6)), Operator.ADD,
- new SetValueExpression(new StringFieldValue("9"))).execute();
+ newArithmetic(new ConstantExpression(new IntegerFieldValue(6)), Operator.ADD,
+ new ConstantExpression(new StringFieldValue("9"))).execute();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unsupported operation: [int] + [string]", e.getMessage());
}
try {
- newArithmetic(new SetValueExpression(new StringFieldValue("6")), Operator.ADD,
- new SetValueExpression(new IntegerFieldValue(9))).execute();
+ newArithmetic(new ConstantExpression(new StringFieldValue("6")), Operator.ADD,
+ new ConstantExpression(new IntegerFieldValue(9))).execute();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unsupported operation: [string] + [int]", e.getMessage());
@@ -156,8 +156,8 @@ public class ArithmeticTestCase {
}
private void assertResult(FieldValue lhs, Operator op, FieldValue rhs, FieldValue expected) {
- assertEquals(expected, evaluate(new SetValueExpression(lhs), op,
- new SetValueExpression(rhs)));
+ assertEquals(expected, evaluate(new ConstantExpression(lhs), op,
+ new ConstantExpression(rhs)));
}
private void assertType(DataType lhs, Operator op, DataType rhs, DataType expected) {
@@ -178,15 +178,15 @@ public class ArithmeticTestCase {
}
private static ArithmeticExpression newArithmetic(FieldValue lhs, Operator op, FieldValue rhs) {
- return newArithmetic(new SetValueExpression(lhs), op, new SetValueExpression(rhs));
+ return newArithmetic(new ConstantExpression(lhs), op, new ConstantExpression(rhs));
}
private static ArithmeticExpression newArithmetic(Expression lhs, Operator op, Expression rhs) {
return new ArithmeticExpression(lhs, op, rhs);
}
- private static SetValueExpression newLong(long val) {
- return new SetValueExpression(new LongFieldValue(val));
+ private static ConstantExpression newLong(long val) {
+ return new ConstantExpression(new LongFieldValue(val));
}
private static void assertVerify(Expression lhs, Operator op, Expression rhs, DataType val) {
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeTestCase.java
index fee467b4c2c..eec95aa6644 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64DecodeTestCase.java
@@ -48,7 +48,7 @@ public class Base64DecodeTestCase {
new Base64DecodeExpression().execute(new StringFieldValue("abcdefghijlkm"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Base64 value 'abcdefghijlkm' is out of range.", e.getMessage());
+ assertEquals("Base64 value 'abcdefghijlkm' is out of range", e.getMessage());
}
}
@@ -66,7 +66,7 @@ public class Base64DecodeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new Base64DecodeExpression();
assertVerify(DataType.STRING, exp, DataType.LONG);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.LONG, exp, "Expected string input, got long.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.LONG, exp, "Expected string input, got long");
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64EncodeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64EncodeTestCase.java
index 50c8d540e81..d82044c6c80 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64EncodeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/Base64EncodeTestCase.java
@@ -40,7 +40,7 @@ public class Base64EncodeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new Base64EncodeExpression();
assertVerify(DataType.LONG, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected long input, got null.");
- assertVerifyThrows(DataType.STRING, exp, "Expected long input, got string.");
+ assertVerifyThrows(null, exp, "Expected long input, but no input is specified");
+ assertVerifyThrows(DataType.STRING, exp, "Expected long input, got string");
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CatTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CatTestCase.java
index 09341a6f9ba..d4f97c62e7d 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CatTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CatTestCase.java
@@ -44,22 +44,22 @@ public class CatTestCase {
@Test
public void requireThatExpressionCanBeVerified() {
- assertVerify(new SetValueExpression(new StringFieldValue("foo")),
- new SetValueExpression(new StringFieldValue("bar")), null);
+ assertVerify(new ConstantExpression(new StringFieldValue("foo")),
+ new ConstantExpression(new StringFieldValue("bar")), null);
assertVerify(new SimpleExpression(DataType.STRING),
new SimpleExpression(DataType.STRING), DataType.STRING);
assertVerifyThrows(new SimpleExpression().setCreatedOutput(null),
new SimpleExpression().setCreatedOutput(DataType.STRING), null,
- "Attempting to concatenate a null value ");
+ "Attempting to concatenate a null value");
assertVerifyThrows(new SimpleExpression(DataType.STRING),
new SimpleExpression(DataType.INT), null,
- "Operands require conflicting input types, string vs int.");
+ "Operands require conflicting input types, string vs int");
assertVerifyThrows(new SimpleExpression(DataType.STRING),
new SimpleExpression(DataType.STRING), null,
- "Expected string input, got null.");
+ "Expected string input, but no input is specified");
assertVerifyThrows(new SimpleExpression(DataType.STRING),
new SimpleExpression(DataType.STRING), DataType.INT,
- "Expected string input, got int.");
+ "Expected string input, got int");
}
@Test
@@ -89,16 +89,16 @@ public class CatTestCase {
@Test
public void requireThatInputValueIsAvailableToAllInnerExpressions() {
assertEquals(new StringFieldValue("foobarfoo"),
- new StatementExpression(new SetValueExpression(new StringFieldValue("foo")),
+ new StatementExpression(new ConstantExpression(new StringFieldValue("foo")),
new CatExpression(new ThisExpression(),
- new SetValueExpression(new StringFieldValue("bar")),
+ new ConstantExpression(new StringFieldValue("bar")),
new ThisExpression())).execute());
}
@Test
public void requiredThatRequiredInputTypeAllowsNull() {
- assertVerify(new SetValueExpression(new StringFieldValue("foo")), new TrimExpression(), DataType.STRING);
- assertVerify(new TrimExpression(), new SetValueExpression(new StringFieldValue("foo")), DataType.STRING);
+ assertVerify(new ConstantExpression(new StringFieldValue("foo")), new TrimExpression(), DataType.STRING);
+ assertVerify(new TrimExpression(), new ConstantExpression(new StringFieldValue("foo")), DataType.STRING);
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpressionTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpressionTestCase.java
index 6ad01e1e9bf..3d2230bb524 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpressionTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpressionTestCase.java
@@ -15,7 +15,7 @@ public class CompositeExpressionTestCase {
@Test
public void requireThatToScriptBlockOutputIsParsable() throws ParseException {
- Expression exp = new SetValueExpression(new IntegerFieldValue(69));
+ Expression exp = new ConstantExpression(new IntegerFieldValue(69));
assertScript("{ 69; }", exp);
assertScript("{ 69; }", new StatementExpression(exp));
assertScript("{ 69; }", new ScriptExpression(new StatementExpression(exp)));
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/EchoTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/EchoTestCase.java
index 29a0cd1617d..4dd5ef4d9f9 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/EchoTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/EchoTestCase.java
@@ -53,6 +53,6 @@ public class EchoTestCase {
Expression exp = new EchoExpression();
assertVerify(DataType.INT, exp, DataType.INT);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExactTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExactTestCase.java
index e993f6cbdee..d1d62442857 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExactTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExactTestCase.java
@@ -75,8 +75,8 @@ public class ExactTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ExactExpression();
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
private static void assertAnnotation(int expectedFrom, int expectedLen, StringFieldValue expectedVal,
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionTestCase.java
index d3ff552ab10..e42ab5a60d5 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionTestCase.java
@@ -34,22 +34,22 @@ public class ExpressionTestCase {
public void requireThatInputTypeIsCheckedBeforeVerify() {
assertVerify(newRequiredInput(DataType.INT), DataType.INT);
assertVerifyThrows(newRequiredInput(DataType.INT), null,
- "Expected int input, got null.");
+ "Expected int input, but no input is specified");
assertVerifyThrows(newRequiredInput(DataType.INT), UnresolvedDataType.INSTANCE,
- "Failed to resolve input type.");
+ "Failed to resolve input type");
assertVerifyThrows(newRequiredInput(DataType.INT), DataType.STRING,
- "Expected int input, got string.");
+ "Expected int input, got string");
}
@Test
public void requireThatOutputTypeIsCheckedAfterVerify() {
assertVerify(newCreatedOutput(DataType.INT, DataType.INT), null);
assertVerifyThrows(newCreatedOutput(DataType.INT, (DataType)null), null,
- "Expected int output, got null.");
+ "Expected int output, but no output is specified");
assertVerifyThrows(newCreatedOutput(DataType.INT, UnresolvedDataType.INSTANCE), null,
- "Failed to resolve output type.");
+ "Failed to resolve output type");
assertVerifyThrows(newCreatedOutput(DataType.INT, DataType.STRING), null,
- "Expected int output, got string.");
+ "Expected int output, got string");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/FlattenTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/FlattenTestCase.java
index ec0af3ba5f2..b875abb923b 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/FlattenTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/FlattenTestCase.java
@@ -87,7 +87,7 @@ public class FlattenTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new FlattenExpression();
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachTestCase.java
index 356910a299a..04a682aa82a 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachTestCase.java
@@ -53,9 +53,9 @@ public class ForEachTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ForEachExpression(SimpleExpression.newConversion(DataType.INT, DataType.STRING));
assertVerify(DataType.getArray(DataType.INT), exp, DataType.getArray(DataType.STRING));
- assertVerifyThrows(null, exp, "Expected any input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected Array, Struct or WeightedSet input, got int.");
- assertVerifyThrows(DataType.getArray(DataType.STRING), exp, "Expected int input, got string.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected Array, Struct or WeightedSet input, got int");
+ assertVerifyThrows(DataType.getArray(DataType.STRING), exp, "Expected int input, got string");
}
@Test
@@ -64,9 +64,9 @@ public class ForEachTestCase {
type.addField(new Field("foo", DataType.INT));
assertVerify(type, new ForEachExpression(new SimpleExpression()), type);
assertVerifyThrows(type, new ForEachExpression(SimpleExpression.newConversion(DataType.STRING, DataType.INT)),
- "Expected string input, got int.");
+ "Expected string input, got int");
assertVerifyThrows(type, new ForEachExpression(SimpleExpression.newConversion(DataType.INT, DataType.STRING)),
- "Expected int output, got string.");
+ "Expected int output, got string");
}
@Test
@@ -94,7 +94,7 @@ public class ForEachTestCase {
@Test
public void requireThatCreatedOutputTypeDependsOnInnerExpression() {
assertNull(new ForEachExpression(new SimpleExpression()).createdOutputType());
- assertNotNull(new ForEachExpression(new SetValueExpression(new IntegerFieldValue(69))).createdOutputType());
+ assertNotNull(new ForEachExpression(new ConstantExpression(new IntegerFieldValue(69))).createdOutputType());
}
@Test
@@ -134,7 +134,7 @@ public class ForEachTestCase {
new ForEachExpression(new SimpleExpression()).execute(new StringFieldValue("foo"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Expected Array, Struct or WeightedSet input, got string.", e.getMessage());
+ assertEquals("Expected Array, Struct or WeightedSet input, got string", e.getMessage());
}
}
@@ -215,7 +215,7 @@ public class ForEachTestCase {
new ForEachExpression(new ToArrayExpression()).verify(ctx);
fail();
} catch (VerificationException e) {
- assertEquals("Expected int output, got Array<int>.", e.getMessage());
+ assertEquals("Expected int output, got Array<int>", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldTestCase.java
index 9fbb95c0def..5da7a3544dc 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetFieldTestCase.java
@@ -38,8 +38,8 @@ public class GetFieldTestCase {
type.addField(new Field("foo", DataType.STRING));
Expression exp = new GetFieldExpression("foo");
assertVerify(type, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected structured input, got int.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected structured input, got int");
assertVerifyThrows(type, new GetFieldExpression("bar"), "Field 'bar' not found in struct type 'my_struct'");
}
@@ -69,7 +69,7 @@ public class GetFieldTestCase {
new GetFieldExpression("foo").execute(new StringFieldValue("bar"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Expected structured input, got string.", e.getMessage());
+ assertEquals("Expected structured input, got string", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarTestCase.java
index 30ee50fccc8..daa02edcaa2 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GetVarTestCase.java
@@ -41,7 +41,7 @@ public class GetVarTestCase {
new GetVarExpression("bar").verify(ctx);
fail();
} catch (VerificationException e) {
- assertEquals("Variable 'bar' not found.", e.getMessage());
+ assertEquals("Variable 'bar' not found", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GuardTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GuardTestCase.java
index 092ca6b5f59..b0fa561a62e 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GuardTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/GuardTestCase.java
@@ -46,8 +46,8 @@ public class GuardTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new GuardExpression(SimpleExpression.newConversion(DataType.INT, DataType.STRING));
assertVerify(DataType.INT, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected int input, got null.");
- assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string.");
+ assertVerifyThrows(null, exp, "Expected int input, but no input is specified");
+ assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeTestCase.java
index b92d7988df2..c42cfec5847 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexDecodeTestCase.java
@@ -29,8 +29,8 @@ public class HexDecodeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new HexDecodeExpression();
assertVerify(DataType.STRING, exp, DataType.LONG);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.LONG, exp, "Expected string input, got long.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.LONG, exp, "Expected string input, got long");
}
@Test
@@ -67,7 +67,7 @@ public class HexDecodeTestCase {
new HexDecodeExpression().execute(new StringFieldValue("1ffffffffffffffff"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Hex value '1ffffffffffffffff' is out of range.", e.getMessage());
+ assertEquals("Hex value '1ffffffffffffffff' is out of range", e.getMessage());
}
}
@@ -77,7 +77,7 @@ public class HexDecodeTestCase {
new HexDecodeExpression().execute(new StringFieldValue("???"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Illegal hex value '???'.", e.getMessage());
+ assertEquals("Illegal hex value '???'", e.getMessage());
}
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexEncodeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexEncodeTestCase.java
index 14745e5d61d..23c55327ed6 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexEncodeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/HexEncodeTestCase.java
@@ -29,8 +29,8 @@ public class HexEncodeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new HexEncodeExpression();
assertVerify(DataType.LONG, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected long input, got null.");
- assertVerifyThrows(DataType.STRING, exp, "Expected long input, got string.");
+ assertVerifyThrows(null, exp, "Expected long input, but no input is specified");
+ assertVerifyThrows(DataType.STRING, exp, "Expected long input, got string");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenTestCase.java
index d429d340480..6c8980c8c1d 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenTestCase.java
@@ -42,29 +42,29 @@ public class IfThenTestCase {
Expression exp = newRequiredInput(DataType.STRING, Comparator.EQ, DataType.STRING,
DataType.STRING, DataType.STRING);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
assertVerifyThrows(null, () -> newRequiredInput(DataType.INT, Comparator.EQ, DataType.STRING,
DataType.STRING, DataType.STRING),
- "Operands require conflicting input types, int vs string.");
+ "Operands require conflicting input types, int vs string");
assertVerifyThrows(null, () -> newRequiredInput(DataType.STRING, Comparator.EQ, DataType.INT,
DataType.STRING, DataType.STRING),
- "Operands require conflicting input types, string vs int.");
+ "Operands require conflicting input types, string vs int");
assertVerifyThrows(null, () -> newRequiredInput(DataType.STRING, Comparator.EQ, DataType.STRING,
DataType.INT, DataType.STRING),
- "Operands require conflicting input types, string vs int.");
+ "Operands require conflicting input types, string vs int");
assertVerifyThrows(null, () -> newRequiredInput(DataType.STRING, Comparator.EQ, DataType.STRING,
DataType.STRING, DataType.INT),
- "Operands require conflicting input types, string vs int.");
+ "Operands require conflicting input types, string vs int");
}
@Test
public void requireThatExpressionCanBeVerified() {
assertVerify(DataType.STRING, new FlattenExpression(), DataType.STRING);
assertVerifyThrows(null, new FlattenExpression(),
- "Expected string input, got null.");
+ "Expected string input, but no input is specified");
assertVerifyThrows(DataType.INT, new FlattenExpression(),
- "Expected string input, got int.");
+ "Expected string input, got int");
}
@Test
@@ -116,7 +116,7 @@ public class IfThenTestCase {
@Test
public void requireThatAllChildrenSeeInputValue() {
FieldValueAdapter adapter = createTestAdapter();
- new StatementExpression(new SetValueExpression(new IntegerFieldValue(69)),
+ new StatementExpression(new ConstantExpression(new IntegerFieldValue(69)),
new IfThenExpression(new AttributeExpression("lhs"),
Comparator.EQ,
new AttributeExpression("rhs"),
@@ -128,7 +128,7 @@ public class IfThenTestCase {
assertNull(null, adapter.getInputValue("ifFalse"));
adapter = createTestAdapter();
- new StatementExpression(new SetValueExpression(new IntegerFieldValue(69)),
+ new StatementExpression(new ConstantExpression(new IntegerFieldValue(69)),
new IfThenExpression(new AttributeExpression("lhs"),
Comparator.NE,
new AttributeExpression("rhs"),
@@ -143,10 +143,10 @@ public class IfThenTestCase {
@Test
public void requireThatElseExpIsOptional() {
ExecutionContext ctx = new ExecutionContext();
- Expression exp = new IfThenExpression(new SetValueExpression(new IntegerFieldValue(6)),
+ Expression exp = new IfThenExpression(new ConstantExpression(new IntegerFieldValue(6)),
Comparator.GT,
- new SetValueExpression(new IntegerFieldValue(9)),
- new SetValueExpression(new StringFieldValue("69")));
+ new ConstantExpression(new IntegerFieldValue(9)),
+ new ConstantExpression(new StringFieldValue("69")));
FieldValue val = ctx.setValue(new IntegerFieldValue(96)).execute(exp).getValue();
assertTrue(val instanceof IntegerFieldValue);
assertEquals(96, ((IntegerFieldValue)val).getInteger());
@@ -225,8 +225,8 @@ public class IfThenTestCase {
@Test
public void requireThatNullLeftOrRightHandSideEvaluatesToNull() {
Expression exp = new IfThenExpression(new GetVarExpression("lhs"), Comparator.EQ, new GetVarExpression("rhs"),
- new SetValueExpression(new StringFieldValue("true")),
- new SetValueExpression(new StringFieldValue("false")));
+ new ConstantExpression(new StringFieldValue("true")),
+ new ConstantExpression(new StringFieldValue("false")));
assertEquals(new StringFieldValue("true"),
exp.execute(new ExecutionContext().setVariable("lhs", new IntegerFieldValue(69))
.setVariable("rhs", new IntegerFieldValue(69))));
@@ -237,6 +237,25 @@ public class IfThenTestCase {
assertNull(exp.execute(new ExecutionContext().setVariable("rhs", new IntegerFieldValue(69))));
}
+ @Test
+ public void testRequiredInputType() {
+ var ifExpression = new IfThenExpression(new InputExpression("field1"),
+ Comparator.EQ,
+ new ConstantExpression(new IntegerFieldValue(0)),
+ wrapLikeTheParser(new ConstantExpression(new StringFieldValue("true"))),
+ wrapLikeTheParser(new ConstantExpression(new StringFieldValue("false"))));
+ assertNull(ifExpression.requiredInputType());
+ assertEquals(DataType.STRING, ifExpression.createdOutputType());
+
+ var expression = new ScriptExpression(new StatementExpression(ifExpression,
+ new AttributeExpression(null)));
+ assertNull(expression.requiredInputType());
+ }
+
+ private Expression wrapLikeTheParser(Expression expression) {
+ return new ScriptExpression(new StatementExpression(expression));
+ }
+
private static void assertCmpTrue(FieldValue lhs, Comparator cmp, FieldValue rhs) {
assertTrue(evaluateIfThen(lhs, cmp, rhs));
}
@@ -248,8 +267,8 @@ public class IfThenTestCase {
private static boolean evaluateIfThen(FieldValue lhs, Comparator cmp, FieldValue rhs) {
ExecutionContext ctx = new ExecutionContext(new SimpleTestAdapter());
new StatementExpression(
- new SetValueExpression(new IntegerFieldValue(1)),
- new IfThenExpression(new SetValueExpression(lhs), cmp, new SetValueExpression(rhs),
+ new ConstantExpression(new IntegerFieldValue(1)),
+ new IfThenExpression(new ConstantExpression(lhs), cmp, new ConstantExpression(rhs),
new SetVarExpression("true"),
new SetVarExpression("false"))).execute(ctx);
return ctx.getVariable("true") != null;
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/InputTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/InputTestCase.java
index fe16e8d10ab..0cfbb23bd1b 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/InputTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/InputTestCase.java
@@ -40,7 +40,7 @@ public class InputTestCase {
new InputExpression("bar").verify(adapter);
fail();
} catch (VerificationException e) {
- assertEquals("Field 'bar' not found.", e.getMessage());
+ assertEquals("Field 'bar' not found", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/JoinTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/JoinTestCase.java
index a1eb46afd33..b4c94166c2d 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/JoinTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/JoinTestCase.java
@@ -36,8 +36,8 @@ public class JoinTestCase {
Expression exp = new JoinExpression(";");
assertVerify(DataType.getArray(DataType.INT), exp, DataType.STRING);
assertVerify(DataType.getArray(DataType.STRING), exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected Array input, got int.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected Array input, got int");
}
@Test
@@ -58,7 +58,7 @@ public class JoinTestCase {
new JoinExpression(";").execute(new StringFieldValue("foo"));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Expected Array input, got string.", e.getMessage());
+ assertEquals("Expected Array input, got string", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/LowerCaseTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/LowerCaseTestCase.java
index ce2251d1884..f1d68f6438a 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/LowerCaseTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/LowerCaseTestCase.java
@@ -28,8 +28,8 @@ public class LowerCaseTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new LowerCaseExpression();
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/MathResolverTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/MathResolverTestCase.java
index 1f9afdea44d..0bcbcb2b1b3 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/MathResolverTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/MathResolverTestCase.java
@@ -93,7 +93,7 @@ public class MathResolverTestCase {
// --------------------------------------------------------------------------------
private static Expression newInteger(int val) {
- return new SetValueExpression(new IntegerFieldValue(val));
+ return new ConstantExpression(new IntegerFieldValue(val));
}
private static int evaluate(Expression exp) {
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NGramTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NGramTestCase.java
index 4d4f11416cb..ae52ad83e8c 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NGramTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NGramTestCase.java
@@ -46,8 +46,8 @@ public class NGramTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new NGramExpression(new SimpleLinguistics(), 69);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NormalizeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NormalizeTestCase.java
index f00ad6a95aa..9584bfa1438 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NormalizeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/NormalizeTestCase.java
@@ -43,8 +43,8 @@ public class NormalizeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new NormalizeExpression(new SimpleLinguistics());
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateTestCase.java
index 28dc12781f0..685416c905a 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OptimizePredicateTestCase.java
@@ -76,21 +76,21 @@ public class OptimizePredicateTestCase {
@Test
public void requireThatExpressionCanBeVerified() {
Expression exp = new OptimizePredicateExpression();
- assertVerifyThrows(null, exp, "Expected predicate input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected predicate input, got int.");
- assertVerifyThrows(DataType.PREDICATE, exp, "Variable 'arity' must be set.");
+ assertVerifyThrows(null, exp, "Expected predicate input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected predicate input, got int");
+ assertVerifyThrows(DataType.PREDICATE, exp, "Variable 'arity' must be set");
VerificationContext context = new VerificationContext().setValueType(DataType.PREDICATE);
context.setVariable("arity", DataType.STRING);
- assertVerifyCtxThrows(context, exp, "Variable 'arity' must have type int.");
+ assertVerifyCtxThrows(context, exp, "Variable 'arity' must have type int");
context.setVariable("arity", DataType.INT);
assertVerifyCtx(context, exp, DataType.PREDICATE);
context.setVariable("lower_bound", DataType.INT);
- assertVerifyCtxThrows(context, exp, "Variable 'lower_bound' must have type long.");
+ assertVerifyCtxThrows(context, exp, "Variable 'lower_bound' must have type long");
context.setVariable("lower_bound", DataType.LONG);
assertVerifyCtx(context, exp, DataType.PREDICATE);
context.setVariable("upper_bound", DataType.INT);
- assertVerifyCtxThrows(context, exp, "Variable 'upper_bound' must have type long.");
+ assertVerifyCtxThrows(context, exp, "Variable 'upper_bound' must have type long");
context.setVariable("upper_bound", DataType.LONG);
assertVerifyCtx(context, exp, DataType.PREDICATE);
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OutputAssert.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OutputAssert.java
index 26bc531f806..10dab2061e7 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OutputAssert.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/OutputAssert.java
@@ -27,7 +27,7 @@ class OutputAssert {
public static void assertVerify(OutputExpression exp) {
assertVerify(new MyAdapter(null), DataType.INT, exp);
assertVerify(new MyAdapter(null), DataType.STRING, exp);
- assertVerifyThrows(new MyAdapter(null), null, exp, "Expected any input, got null.");
+ assertVerifyThrows(new MyAdapter(null), null, exp, "Expected any input, but no input is specified");
assertVerifyThrows(new MyAdapter(new VerificationException((Expression) null, "foo")), DataType.INT, exp, "foo");
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisTestCase.java
index 9f3a220249b..5ca3e336f69 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisTestCase.java
@@ -37,8 +37,8 @@ public class ParenthesisTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ParenthesisExpression(SimpleExpression.newConversion(DataType.INT, DataType.STRING));
assertVerify(DataType.INT, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected int input, got null.");
- assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string.");
+ assertVerifyThrows(null, exp, "Expected int input, but no input is specified");
+ assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptTestCase.java
index c648bb12ede..62b960568e9 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptTestCase.java
@@ -54,20 +54,20 @@ public class ScriptTestCase {
@Test
public void requireThatExpressionCanBeVerified() {
Expression exp = newScript(newStatement(SimpleExpression.newConversion(DataType.INT, DataType.STRING)));
- assertVerify(DataType.INT, exp, DataType.INT); // does not touch output
- assertVerifyThrows(null, exp, "Expected int input, got null.");
- assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string.");
+ assertVerify(DataType.INT, exp, DataType.STRING);
+ assertVerifyThrows(null, exp, "Expected int input, but no input is specified");
+ assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string");
assertVerifyThrows(null, () -> newScript(newStatement(SimpleExpression.newConversion(DataType.INT, DataType.STRING)),
newStatement(SimpleExpression.newConversion(DataType.STRING, DataType.INT))),
- "Statements require conflicting input types, int vs string.");
+ "Statements require conflicting input types, int vs string");
}
@Test
public void requireThatInputValueIsAvailableToAllStatements() {
SimpleTestAdapter adapter = new SimpleTestAdapter(new Field("out-1", DataType.INT),
new Field("out-2", DataType.INT));
- newStatement(new SetValueExpression(new IntegerFieldValue(69)),
+ newStatement(new ConstantExpression(new IntegerFieldValue(69)),
newScript(newStatement(new AttributeExpression("out-1"),
new AttributeExpression("out-2")))).execute(adapter);
assertEquals(new IntegerFieldValue(69), adapter.getInputValue("out-1"));
@@ -90,8 +90,8 @@ public class ScriptTestCase {
@Test
public void requireThatScriptEvaluatesToInputValue() {
SimpleTestAdapter adapter = new SimpleTestAdapter(new Field("out", DataType.INT));
- newStatement(new SetValueExpression(new IntegerFieldValue(6)),
- newScript(newStatement(new SetValueExpression(new IntegerFieldValue(9)))),
+ newStatement(new ConstantExpression(new IntegerFieldValue(6)),
+ newScript(newStatement(new ConstantExpression(new IntegerFieldValue(9)))),
new AttributeExpression("out")).execute(adapter);
assertEquals(new IntegerFieldValue(6), adapter.getInputValue("out"));
}
@@ -99,7 +99,7 @@ public class ScriptTestCase {
@Test
public void requireThatVariablesAreAvailableInScript() {
SimpleTestAdapter adapter = new SimpleTestAdapter(new Field("out", DataType.INT));
- newScript(newStatement(new SetValueExpression(new IntegerFieldValue(69)),
+ newScript(newStatement(new ConstantExpression(new IntegerFieldValue(69)),
new SetVarExpression("tmp")),
newStatement(new GetVarExpression("tmp"),
new AttributeExpression("out"))).execute(adapter);
@@ -109,7 +109,7 @@ public class ScriptTestCase {
@Test
public void requireThatVariablesAreAvailableOutsideScript() {
SimpleTestAdapter adapter = new SimpleTestAdapter(new Field("out", DataType.INT));
- newStatement(newScript(newStatement(new SetValueExpression(new IntegerFieldValue(69)),
+ newStatement(newScript(newStatement(new ConstantExpression(new IntegerFieldValue(69)),
new SetVarExpression("tmp"))),
new GetVarExpression("tmp"),
new AttributeExpression("out")).execute(adapter);
@@ -119,9 +119,9 @@ public class ScriptTestCase {
@Test
public void requireThatVariablesReplaceOthersOutsideScript() {
SimpleTestAdapter adapter = new SimpleTestAdapter(new Field("out", DataType.INT));
- newStatement(new SetValueExpression(new IntegerFieldValue(6)),
+ newStatement(new ConstantExpression(new IntegerFieldValue(6)),
new SetVarExpression("tmp"),
- newScript(newStatement(new SetValueExpression(new IntegerFieldValue(9)),
+ newScript(newStatement(new ConstantExpression(new IntegerFieldValue(9)),
new SetVarExpression("tmp"))),
new GetVarExpression("tmp"),
new AttributeExpression("out")).execute(adapter);
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputTestCase.java
index 46605cc5f6a..f4d26bee9f1 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputTestCase.java
@@ -61,16 +61,16 @@ public class SelectInputTestCase {
assertVerifyThrows(adapter, newSelectInput(new AttributeExpression("my_int"), "my_str"),
"Can not assign string to field 'my_int' which is int.");
assertVerifyThrows(adapter, newSelectInput(new AttributeExpression("my_int"), "my_unknown"),
- "Field 'my_unknown' not found.");
+ "Field 'my_unknown' not found");
}
@Test
public void requireThatSelectedExpressionIsRun() {
- assertSelect(Arrays.asList("foo", "bar"), Arrays.asList("foo"), "foo");
- assertSelect(Arrays.asList("foo", "bar"), Arrays.asList("bar"), "bar");
- assertSelect(Arrays.asList("foo", "bar"), Arrays.asList("foo", "bar"), "foo");
- assertSelect(Arrays.asList("foo", "bar"), Arrays.asList("bar", "baz"), "bar");
- assertSelect(Arrays.asList("foo", "bar"), Arrays.asList("baz", "cox"), null);
+ assertSelect(Arrays.asList("foo", "bar"), List.of("foo"), "foo");
+ assertSelect(Arrays.asList("foo", "bar"), List.of("bar"), "bar");
+ assertSelect(Arrays.asList("foo", "bar"), List.of("foo", "bar"), "foo");
+ assertSelect(Arrays.asList("foo", "bar"), List.of("bar", "baz"), "bar");
+ assertSelect(Arrays.asList("foo", "bar"), List.of("baz", "cox"), null);
}
private static void assertVerify(FieldTypeAdapter adapter, DataType value, Expression exp) {
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetLanguageTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetLanguageTestCase.java
index 9b95ffc31bb..0821ab0cc40 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetLanguageTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetLanguageTestCase.java
@@ -29,8 +29,8 @@ public class SetLanguageTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new SetLanguageExpression();
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueTestCase.java
index cace2c5923d..488a8c098fd 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetValueTestCase.java
@@ -19,23 +19,23 @@ public class SetValueTestCase {
@Test
public void requireThatAccessorsWork() {
FieldValue foo = new StringFieldValue("foo");
- SetValueExpression exp = new SetValueExpression(foo);
+ ConstantExpression exp = new ConstantExpression(foo);
assertSame(foo, exp.getValue());
}
@Test
public void requireThatHashCodeAndEqualsAreImplemented() {
FieldValue foo = new StringFieldValue("foo");
- Expression exp = new SetValueExpression(foo);
+ Expression exp = new ConstantExpression(foo);
assertFalse(exp.equals(new Object()));
- assertFalse(exp.equals(new SetValueExpression(new StringFieldValue("bar"))));
- assertEquals(exp, new SetValueExpression(foo));
- assertEquals(exp.hashCode(), new SetValueExpression(foo).hashCode());
+ assertFalse(exp.equals(new ConstantExpression(new StringFieldValue("bar"))));
+ assertEquals(exp, new ConstantExpression(foo));
+ assertEquals(exp.hashCode(), new ConstantExpression(foo).hashCode());
}
@Test
public void requireThatExpressionCanBeVerified() {
- Expression exp = new SetValueExpression(new StringFieldValue("foo"));
+ Expression exp = new ConstantExpression(new StringFieldValue("foo"));
assertVerify(null, exp, DataType.STRING);
assertVerify(DataType.INT, exp, DataType.STRING);
assertVerify(DataType.STRING, exp, DataType.STRING);
@@ -44,7 +44,7 @@ public class SetValueTestCase {
@Test
public void requireThatNullValueThrowsException() {
try {
- new SetValueExpression(null);
+ new ConstantExpression(null);
fail();
} catch (NullPointerException e) {
@@ -54,12 +54,12 @@ public class SetValueTestCase {
@Test
public void requireThatValueIsSet() {
ExecutionContext ctx = new ExecutionContext(new SimpleTestAdapter());
- new SetValueExpression(new StringFieldValue("69")).execute(ctx);
+ new ConstantExpression(new StringFieldValue("69")).execute(ctx);
assertEquals(new StringFieldValue("69"), ctx.getValue());
}
@Test
public void requireThatLongFieldValueGetsATrailingL() {
- assertEquals("69L", new SetValueExpression(new LongFieldValue(69)).toString());
+ assertEquals("69L", new ConstantExpression(new LongFieldValue(69)).toString());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarTestCase.java
index 0cae20af29d..ae393837dfe 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SetVarTestCase.java
@@ -36,13 +36,13 @@ public class SetVarTestCase {
Expression exp = new SetVarExpression("foo");
assertVerify(DataType.INT, exp, DataType.INT);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
try {
new VerificationContext().setVariable("foo", DataType.INT).setValueType(DataType.STRING).execute(exp);
fail();
} catch (VerificationException e) {
- assertEquals("Attempting to assign conflicting types to variable 'foo', int vs string.", e.getMessage());
+ assertEquals("Attempting to assign conflicting types to variable 'foo', int vs string", e.getMessage());
}
}
@@ -69,7 +69,7 @@ public class SetVarTestCase {
fail();
} catch (VerificationException e) {
assertTrue(e.getExpressionType().equals(SetVarExpression.class));
- assertEquals("Attempting to assign conflicting types to variable 'out', int vs string.", e.getMessage());
+ assertEquals("Attempting to assign conflicting types to variable 'out', int vs string", e.getMessage());
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SimpleExpression.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SimpleExpression.java
index db51158d5c2..80ca6622001 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SimpleExpression.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SimpleExpression.java
@@ -64,29 +64,14 @@ final class SimpleExpression extends Expression {
}
@Override
- public boolean equals(Object obj) {
- if (!(obj instanceof SimpleExpression)) {
- return false;
- }
- SimpleExpression rhs = (SimpleExpression)obj;
- if (hasExecuteValue != rhs.hasExecuteValue) {
- return false;
- }
- if (!equals(executeValue, rhs.executeValue)) {
- return false;
- }
- if (hasVerifyValue != rhs.hasVerifyValue) {
- return false;
- }
- if (!equals(verifyValue, rhs.verifyValue)) {
- return false;
- }
- if (!equals(requiredInputType(), rhs.requiredInputType())) {
- return false;
- }
- if (!equals(createdOutput, rhs.createdOutput)) {
- return false;
- }
+ public boolean equals(Object o) {
+ if (!(o instanceof SimpleExpression other)) return false;
+ if (hasExecuteValue != other.hasExecuteValue) return false;
+ if (!equals(executeValue, other.executeValue)) return false;
+ if (hasVerifyValue != other.hasVerifyValue) return false;
+ if (!equals(verifyValue, other.verifyValue)) return false;
+ if (!equals(requiredInputType(), other.requiredInputType())) return false;
+ if (!equals(createdOutput, other.createdOutput)) return false;
return true;
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SplitTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SplitTestCase.java
index 7ad4db41e33..d9a8dfcebe6 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SplitTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SplitTestCase.java
@@ -37,8 +37,8 @@ public class SplitTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new SplitExpression(";");
assertVerify(DataType.STRING, exp, DataType.getArray(DataType.STRING));
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/StatementTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/StatementTestCase.java
index 1a1442f68ac..6ceadff8b28 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/StatementTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/StatementTestCase.java
@@ -90,9 +90,9 @@ public class StatementTestCase {
public void requireThatInternalVerificationIsPerformed() {
Expression exp = newStatement(SimpleExpression.newOutput(DataType.STRING),
SimpleExpression.newConversion(DataType.INT, DataType.STRING));
- assertVerifyThrows(null, exp, "Expected int input, got string.");
- assertVerifyThrows(DataType.INT, exp, "Expected int input, got string.");
- assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string.");
+ assertVerifyThrows(null, exp, "Expected int input, got string");
+ assertVerifyThrows(DataType.INT, exp, "Expected int input, got string");
+ assertVerifyThrows(DataType.STRING, exp, "Expected int input, got string");
exp = newStatement(SimpleExpression.newOutput(DataType.INT),
SimpleExpression.newConversion(DataType.INT, DataType.STRING));
@@ -104,7 +104,7 @@ public class StatementTestCase {
@Test
public void requireThatStatementIsExecuted() {
ExecutionContext ctx = new ExecutionContext(new SimpleTestAdapter());
- StatementExpression statement = newStatement(new SetValueExpression(new IntegerFieldValue(69)));
+ StatementExpression statement = newStatement(new ConstantExpression(new IntegerFieldValue(69)));
newStatement(statement).execute(ctx);
FieldValue val = ctx.getValue();
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SubstringTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SubstringTestCase.java
index f6bed7831ae..b209533f379 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SubstringTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SubstringTestCase.java
@@ -37,8 +37,8 @@ public class SubstringTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new SubstringExpression(6, 9);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchTestCase.java
index c79d38f5650..5d8be76c1b4 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchTestCase.java
@@ -58,8 +58,8 @@ public class SwitchTestCase {
Expression foo = SimpleExpression.newConversion(DataType.STRING, DataType.INT);
Expression exp = new SwitchExpression(Collections.singletonMap("foo", foo));
assertVerify(DataType.STRING, exp, DataType.STRING); // does not touch output
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
@@ -67,10 +67,10 @@ public class SwitchTestCase {
Map<String, Expression> cases = new HashMap<>();
cases.put("foo", SimpleExpression.newRequired(DataType.INT));
assertVerifyThrows(DataType.STRING, new SwitchExpression(cases),
- "Expected int input, got string.");
+ "Expected int input, got string");
assertVerifyThrows(DataType.STRING, new SwitchExpression(Collections.<String, Expression>emptyMap(),
SimpleExpression.newRequired(DataType.INT)),
- "Expected int input, got string.");
+ "Expected int input, got string");
}
@Test
@@ -79,7 +79,7 @@ public class SwitchTestCase {
new SwitchExpression(Collections.<String, Expression>emptyMap()).execute(new IntegerFieldValue(69));
fail();
} catch (IllegalArgumentException e) {
- assertEquals("Expected string input, got int.", e.getMessage());
+ assertEquals("Expected string input, got int", e.getMessage());
}
}
@@ -104,9 +104,9 @@ public class SwitchTestCase {
@Test
public void requireThatCorrectExpressionIsExecuted() {
Map<String, Expression> cases = new HashMap<>();
- cases.put("foo", new StatementExpression(new SetValueExpression(new StringFieldValue("bar")),
+ cases.put("foo", new StatementExpression(new ConstantExpression(new StringFieldValue("bar")),
new SetVarExpression("out")));
- cases.put("baz", new StatementExpression(new SetValueExpression(new StringFieldValue("cox")),
+ cases.put("baz", new StatementExpression(new ConstantExpression(new StringFieldValue("cox")),
new SetVarExpression("out")));
Expression exp = new SwitchExpression(cases);
assertEvaluate(new StringFieldValue("foo"), exp, new StringFieldValue("bar"));
@@ -117,9 +117,9 @@ public class SwitchTestCase {
@Test
public void requireThatDefaultExpressionIsExecuted() {
Map<String, Expression> cases = new HashMap<>();
- cases.put("foo", new StatementExpression(new SetValueExpression(new StringFieldValue("bar")),
+ cases.put("foo", new StatementExpression(new ConstantExpression(new StringFieldValue("bar")),
new SetVarExpression("out")));
- Expression defaultExp = new StatementExpression(new SetValueExpression(new StringFieldValue("cox")),
+ Expression defaultExp = new StatementExpression(new ConstantExpression(new StringFieldValue("cox")),
new SetVarExpression("out"));
Expression exp = new SwitchExpression(cases, defaultExp);
assertEvaluate(new StringFieldValue("foo"), exp, new StringFieldValue("bar"));
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ThisTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ThisTestCase.java
index e33c8330d8e..7fc1f2fb4bd 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ThisTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ThisTestCase.java
@@ -29,7 +29,7 @@ public class ThisTestCase {
Expression exp = new ThisExpression();
assertVerify(DataType.INT, exp, DataType.INT);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToArrayTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToArrayTestCase.java
index 294f6f2e266..e7a7ef5aee5 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToArrayTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToArrayTestCase.java
@@ -33,7 +33,7 @@ public class ToArrayTestCase {
Expression exp = new ToArrayExpression();
assertVerify(DataType.INT, exp, DataType.getArray(DataType.INT));
assertVerify(DataType.STRING, exp, DataType.getArray(DataType.STRING));
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToBoolTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToBoolTestCase.java
index 920f186c468..cac754e2b77 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToBoolTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToBoolTestCase.java
@@ -34,7 +34,7 @@ public class ToBoolTestCase {
Expression exp = new ToBoolExpression();
assertVerify(DataType.INT, exp, DataType.BOOL);
assertVerify(DataType.STRING, exp, DataType.BOOL);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToByteTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToByteTestCase.java
index 76e075b4315..4bee5953b8c 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToByteTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToByteTestCase.java
@@ -30,7 +30,7 @@ public class ToByteTestCase {
Expression exp = new ToByteExpression();
assertVerify(DataType.INT, exp, DataType.BYTE);
assertVerify(DataType.STRING, exp, DataType.BYTE);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToDoubleTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToDoubleTestCase.java
index ab0263ee134..39f347417b1 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToDoubleTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToDoubleTestCase.java
@@ -30,7 +30,7 @@ public class ToDoubleTestCase {
Expression exp = new ToDoubleExpression();
assertVerify(DataType.INT, exp, DataType.DOUBLE);
assertVerify(DataType.STRING, exp, DataType.DOUBLE);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToEpochSecondExpressionTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToEpochSecondExpressionTestCase.java
index 7203afcc1a0..3a6bf85f972 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToEpochSecondExpressionTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToEpochSecondExpressionTestCase.java
@@ -27,8 +27,8 @@ public class ToEpochSecondExpressionTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ToEpochSecondExpression();
assertVerify(DataType.STRING, exp, DataType.LONG);
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
- assertVerifyThrows(null, exp, "Expected string input, got null.");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToFloatTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToFloatTestCase.java
index d36b7c88da3..14b0bf57a45 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToFloatTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToFloatTestCase.java
@@ -30,7 +30,7 @@ public class ToFloatTestCase {
Expression exp = new ToFloatExpression();
assertVerify(DataType.INT, exp, DataType.FLOAT);
assertVerify(DataType.STRING, exp, DataType.FLOAT);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToIntegerTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToIntegerTestCase.java
index 7831c6f675f..56c26cba19b 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToIntegerTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToIntegerTestCase.java
@@ -30,7 +30,7 @@ public class ToIntegerTestCase {
Expression exp = new ToIntegerExpression();
assertVerify(DataType.INT, exp, DataType.INT);
assertVerify(DataType.STRING, exp, DataType.INT);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToLongTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToLongTestCase.java
index f586b8ce110..60f7a84f044 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToLongTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToLongTestCase.java
@@ -30,7 +30,7 @@ public class ToLongTestCase {
Expression exp = new ToLongExpression();
assertVerify(DataType.INT, exp, DataType.LONG);
assertVerify(DataType.STRING, exp, DataType.LONG);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToPositionTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToPositionTestCase.java
index 99d4fc2731f..1317df130ff 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToPositionTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToPositionTestCase.java
@@ -31,8 +31,8 @@ public class ToPositionTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ToPositionExpression();
assertVerify(DataType.STRING, exp, PositionDataType.INSTANCE);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToStringTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToStringTestCase.java
index 43dfbf76b7f..6c3bc8fc160 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToStringTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToStringTestCase.java
@@ -30,7 +30,7 @@ public class ToStringTestCase {
Expression exp = new ToStringExpression();
assertVerify(DataType.INT, exp, DataType.STRING);
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToWsetTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToWsetTestCase.java
index 3b284fd9ae9..c0c938bb3b6 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToWsetTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ToWsetTestCase.java
@@ -60,7 +60,7 @@ public class ToWsetTestCase {
DataType.getWeightedSet(DataType.INT, createIfNonExistent, removeIfZero));
ExpressionAssert.assertVerify(DataType.STRING, exp,
DataType.getWeightedSet(DataType.STRING, createIfNonExistent, removeIfZero));
- assertVerifyThrows(null, exp, "Expected any input, got null.");
+ assertVerifyThrows(null, exp, "Expected any input, but no input is specified");
}
private static void assertConvert(boolean createIfNonExistent, boolean removeIfZero) {
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TokenizeTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TokenizeTestCase.java
index 309c57533cc..a92205cc30f 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TokenizeTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TokenizeTestCase.java
@@ -48,8 +48,8 @@ public class TokenizeTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new TokenizeExpression(new SimpleLinguistics(), new AnnotatorConfig());
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TrimTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TrimTestCase.java
index 5664349a96e..95c272e1fca 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TrimTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/TrimTestCase.java
@@ -28,8 +28,8 @@ public class TrimTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new TrimExpression();
assertVerify(DataType.STRING, exp, DataType.STRING);
- assertVerifyThrows(null, exp, "Expected string input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected string input, got int.");
+ assertVerifyThrows(null, exp, "Expected string input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected string input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ZCurveTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ZCurveTestCase.java
index ee6a5793d76..b0899d209cb 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ZCurveTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ZCurveTestCase.java
@@ -30,8 +30,8 @@ public class ZCurveTestCase {
public void requireThatExpressionCanBeVerified() {
Expression exp = new ZCurveExpression();
assertVerify(PositionDataType.INSTANCE, exp, DataType.LONG);
- assertVerifyThrows(null, exp, "Expected position input, got null.");
- assertVerifyThrows(DataType.INT, exp, "Expected position input, got int.");
+ assertVerifyThrows(null, exp, "Expected position input, but no input is specified");
+ assertVerifyThrows(DataType.INT, exp, "Expected position input, got int");
}
@Test
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ExpressionTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ExpressionTestCase.java
index 551f770a01e..45438a8f273 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ExpressionTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ExpressionTestCase.java
@@ -51,7 +51,7 @@ public class ExpressionTestCase {
assertExpression(ScriptExpression.class, "{ 1; 2 }");
assertExpression(SelectInputExpression.class, "select_input { field1: 2; }");
assertExpression(SetLanguageExpression.class, "set_language");
- assertExpression(SetValueExpression.class, "1");
+ assertExpression(ConstantExpression.class, "1");
assertExpression(SetVarExpression.class, "set_var myvar1");
assertExpression(SplitExpression.class, "split '1'");
assertExpression(StatementExpression.class, "1 | 2");
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/FieldNameTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/FieldNameTestCase.java
index 41b8731995e..d021a633a36 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/FieldNameTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/FieldNameTestCase.java
@@ -23,7 +23,7 @@ public class FieldNameTestCase {
public void requireThatCatDotIsNotConfusedWithFieldName() throws ParseException {
assertEquals(new CatExpression(new InputExpression("foo"), new InputExpression("bar")),
Expression.fromString("input foo . input bar"));
- assertEquals(new CatExpression(new InputExpression("foo"), new SetValueExpression(new StringFieldValue("bar"))),
+ assertEquals(new CatExpression(new InputExpression("foo"), new ConstantExpression(new StringFieldValue("bar"))),
Expression.fromString("input foo . 'bar'"));
}
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ScriptTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ScriptTestCase.java
index ad2b9ebd6f9..978c827556b 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ScriptTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/parser/ScriptTestCase.java
@@ -1,8 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.indexinglanguage.parser;
+import com.yahoo.vespa.indexinglanguage.expressions.ConstantExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.SetValueExpression;
import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
import org.junit.Test;
@@ -17,7 +17,7 @@ public class ScriptTestCase {
@Test
public void requireThatRootProductionIsFlexible() throws ParseException {
- assertRoot(SetValueExpression.class, "1");
+ assertRoot(ConstantExpression.class, "1");
assertRoot(StatementExpression.class, "1 | echo");
assertRoot(StatementExpression.class, "{ 1 | echo }");
assertRoot(StatementExpression.class, "{ 1 | echo; }");
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java
new file mode 100644
index 00000000000..09cf2abdbd3
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java
@@ -0,0 +1,55 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.security.cloud;
+
+import com.yahoo.component.annotation.Inject;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients.Tokens;
+import com.yahoo.restapi.SlimeJsonResponse;
+import com.yahoo.slime.Cursor;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.Executor;
+
+import static java.util.stream.Collectors.flatMapping;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * @author jonmv
+ */
+public class CloudTokenDataPlaneHandler extends ThreadedHttpRequestHandler {
+
+ private final Map<String, Set<String>> tokens;
+
+ @Inject
+ public CloudTokenDataPlaneHandler(CloudTokenDataPlaneFilterConfig config, Executor executor) {
+ super(executor);
+ tokens = new TreeMap<>(config.clients().stream()
+ .flatMap(client -> client.tokens().stream())
+ .collect(groupingBy(Tokens::id,
+ flatMapping(token -> token.fingerprints().stream(),
+ toCollection(TreeSet::new)))));
+ }
+
+ @Override
+ public HttpResponse handle(HttpRequest request) {
+ return new SlimeJsonResponse() {{
+ Cursor tokensArray = slime.setObject().setArray("tokens");
+ tokens.forEach((id, fingerprints) -> {
+ Cursor tokenObject = tokensArray.addObject();
+ tokenObject.setString("id", id);
+ fingerprints.forEach(tokenObject.setArray("fingerprints")::addString);
+ });
+ }};
+ }
+
+}
diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java
new file mode 100644
index 00000000000..c066dae6dca
--- /dev/null
+++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java
@@ -0,0 +1,56 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.security.cloud;
+
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Builder;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients.Tokens;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import static com.yahoo.container.jdisc.HttpRequest.createTestRequest;
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author jonmv
+ */
+public class CloudTokenDataPlaneHandlerTest {
+
+ @Test
+ void testFingerprints() throws IOException {
+ CloudTokenDataPlaneHandler handler = new CloudTokenDataPlaneHandler(
+ new Builder().tokenContext("context")
+ .clients(new Clients.Builder().id("client1")
+ .permissions("read")
+ .tokens(new Tokens.Builder().id("id1")
+ .fingerprints(List.of("pinky", "ring", "middle", "index", "thumb"))
+ .checkAccessHashes(List.of("a", "b", "c", "d", "e"))
+ .expirations(List.of("<none>", "<none>", "<none>", "<none>", "<none>")))
+ .tokens(new Tokens.Builder().id("id2")
+ .fingerprints("toasty")
+ .checkAccessHashes("hash")
+ .expirations("<none>")))
+ .clients(new Clients.Builder().id("client2")
+ .permissions("write")
+ .tokens(new Tokens.Builder().id("id2")
+ .fingerprints("toasty")
+ .checkAccessHashes("hash")
+ .expirations("<none>")))
+ .build(),
+ Runnable::run
+ );
+
+ HttpResponse response = handler.handle(createTestRequest("", GET));
+ assertEquals(200,
+ response.getStatus());
+ assertEquals("""
+ {"tokens":[{"id":"id1","fingerprints":["index","middle","pinky","ring","thumb"]},{"id":"id2","fingerprints":["toasty"]}]}""",
+ new ByteArrayOutputStream() {{ response.render(this); }}.toString(UTF_8));
+ }
+
+}
diff --git a/lucene-linguistics/src/main/java/com/yahoo/language/lucene/AnalyzerFactory.java b/lucene-linguistics/src/main/java/com/yahoo/language/lucene/AnalyzerFactory.java
index 67a430a28dc..92ea77cdc13 100644
--- a/lucene-linguistics/src/main/java/com/yahoo/language/lucene/AnalyzerFactory.java
+++ b/lucene-linguistics/src/main/java/com/yahoo/language/lucene/AnalyzerFactory.java
@@ -11,9 +11,9 @@ import org.apache.lucene.analysis.custom.CustomAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
@@ -60,13 +60,15 @@ class AnalyzerFactory {
}
private Analyzer createAnalyzer(AnalyzerKey analyzerKey) {
- if (null != config.analysis(analyzerKey.languageCode())) {
+ LuceneAnalysisConfig.Analysis analysis = analysisConfig(analyzerKey);
+ if (null != analysis) {
log.config("Creating analyzer for " + analyzerKey + " from config");
- return createAnalyzer(analyzerKey, config.analysis(analyzerKey.languageCode()));
+ return createAnalyzer(analyzerKey, analysis);
}
- if (null != analyzerComponents.getComponent(analyzerKey.languageCode())) {
+ Analyzer analyzerFromComponents = fromComponents(analyzerKey);
+ if (null != analyzerFromComponents) {
log.config("Using analyzer for " + analyzerKey + " from components");
- return analyzerComponents.getComponent(analyzerKey.languageCode());
+ return analyzerFromComponents;
}
if (null != defaultAnalyzers.get(analyzerKey.language())) {
log.config("Using Analyzer for " + analyzerKey + " from a list of default language analyzers");
@@ -77,6 +79,24 @@ class AnalyzerFactory {
return defaultAnalyzer;
}
+ /**
+ * First, checks if more specific (language + stemMode) analysis is configured.
+ * Second, checks if analysis is configured only for a languageCode.
+ */
+ private LuceneAnalysisConfig.Analysis analysisConfig(AnalyzerKey analyzerKey) {
+ LuceneAnalysisConfig.Analysis analysis = config.analysis(analyzerKey.languageCodeAndStemMode());
+ return (null != analysis) ? analysis : config.analysis(analyzerKey.languageCode());
+ }
+
+ /**
+ * First, checks if a component is configured for a languageCode + StemMode.
+ * Second, checks if Analyzer is configured only for a languageCode.
+ */
+ private Analyzer fromComponents(AnalyzerKey analyzerKey) {
+ Analyzer analyzer = analyzerComponents.getComponent(analyzerKey.languageCodeAndStemMode());
+ return (null != analyzer) ? analyzer : analyzerComponents.getComponent(analyzerKey.languageCode());
+ }
+
private Analyzer createAnalyzer(AnalyzerKey analyzerKey, LuceneAnalysisConfig.Analysis analysis) {
try {
CustomAnalyzer.Builder builder = config.configDir()
@@ -143,9 +163,14 @@ class AnalyzerFactory {
private record AnalyzerKey(Language language, StemMode stemMode, boolean removeAccents) {
- // TODO: Identity here is determined by language only.
- // Would it make sense to combine language + stemMode + removeAccents to make
- // a composite key so we can have more variations possible?
+ /**
+ * Combines the languageCode and the stemMode.
+ * It allows to specify up to 6 (5 StemModes and only language code) analyzers per language.
+ * The `/` is used so that it doesn't conflict with ComponentRegistry keys.
+ */
+ public String languageCodeAndStemMode() {
+ return language.languageCode() + "/" + stemMode.toString();
+ }
public String languageCode() {
return language.languageCode();
@@ -155,12 +180,12 @@ class AnalyzerFactory {
public boolean equals(Object o) {
if (o == this) return true;
if ( ! (o instanceof AnalyzerKey other)) return false;
- return other.language == this.language;
+ return other.language == this.language && other.stemMode == this.stemMode;
}
@Override
public int hashCode() {
- return language.hashCode();
+ return Objects.hash(language, stemMode);
}
}
diff --git a/lucene-linguistics/src/test/java/com/yahoo/language/lucene/LuceneTokenizerTest.java b/lucene-linguistics/src/test/java/com/yahoo/language/lucene/LuceneTokenizerTest.java
index 92c369bc60c..fc29fcc0071 100644
--- a/lucene-linguistics/src/test/java/com/yahoo/language/lucene/LuceneTokenizerTest.java
+++ b/lucene-linguistics/src/test/java/com/yahoo/language/lucene/LuceneTokenizerTest.java
@@ -197,4 +197,33 @@ public class LuceneTokenizerTest {
.tokenize("Dogs and Cats", Language.ENGLISH, StemMode.ALL, false);
assertEquals(List.of("and", "Cat"), tokenStrings(tokens));
}
+
+ @Test
+ public void compositeConfigKey() {
+ String reversingAnalyzerKey = Language.ENGLISH.languageCode()
+ + "/"
+ + StemMode.ALL;
+ LuceneAnalysisConfig enConfig = new LuceneAnalysisConfig.Builder()
+ .analysis(
+ Map.of(reversingAnalyzerKey,
+ new LuceneAnalysisConfig.Analysis.Builder().tokenFilters(List.of(
+ new LuceneAnalysisConfig
+ .Analysis
+ .TokenFilters
+ .Builder()
+ .name("reverseString"))))
+ ).build();
+ LuceneLinguistics linguistics = new LuceneLinguistics(enConfig, new ComponentRegistry<>());
+ // Matching StemMode
+ Iterable<Token> tokens = linguistics
+ .getTokenizer()
+ .tokenize("Dogs and Cats", Language.ENGLISH, StemMode.ALL, false);
+ assertEquals(List.of("sgoD", "dna", "staC"), tokenStrings(tokens));
+ // StemMode is different
+ Iterable<Token> stemModeTokens = linguistics
+ .getTokenizer()
+ .tokenize("Dogs and Cats", Language.ENGLISH, StemMode.BEST, false);
+ assertEquals(List.of("dog", "cat"), tokenStrings(stemModeTokens));
+
+ }
}
diff --git a/maven-plugins/allowed-maven-dependencies.txt b/maven-plugins/allowed-maven-dependencies.txt
index 970bb6732a1..06f2f34964b 100644
--- a/maven-plugins/allowed-maven-dependencies.txt
+++ b/maven-plugins/allowed-maven-dependencies.txt
@@ -1,41 +1,31 @@
# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#[non-test]
-# Contains dependencies that are not used exclusively in 'test' scope
-aopalliance:aopalliance:1.0
-com.fasterxml.jackson.core:jackson-annotations:2.15.2
-com.fasterxml.jackson.core:jackson-core:2.15.2
-com.fasterxml.jackson.core:jackson-databind:2.15.2
-com.github.luben:zstd-jni:1.5.5-5
-com.google.errorprone:error_prone_annotations:2.21.1
+aopalliance:aopalliance:${aopalliance.vespa.version}
+com.fasterxml.jackson.core:jackson-annotations:${jackson2.vespa.version}
+com.fasterxml.jackson.core:jackson-core:${jackson2.vespa.version}
+com.fasterxml.jackson.core:jackson-databind:${jackson-databind.vespa.version}
+com.github.luben:zstd-jni:${luben.zstd.vespa.version}
+com.google.errorprone:error_prone_annotations:${error-prone-annotations.vespa.version}
com.google.guava:failureaccess:1.0.1
-com.google.guava:guava:32.1.2-jre
-com.google.inject:guice:6.0.0
+com.google.guava:guava:${guava.vespa.version}
+com.google.inject:guice:${guice.vespa.version}
com.google.j2objc:j2objc-annotations:2.8
-commons-codec:commons-codec:1.16.0
-commons-io:commons-io:2.13.0
-jakarta.inject:jakarta.inject-api:2.0.1
-javax.annotation:javax.annotation-api:1.2
-javax.inject:javax.inject:1
+commons-codec:commons-codec:${commons-codec.vespa.version}
+commons-io:commons-io:${commons-io.vespa.version}
+jakarta.inject:jakarta.inject-api:${jakarta.inject.vespa.version}
+javax.annotation:javax.annotation-api:${commons-logging.vespa.version}
+javax.inject:javax.inject:${javax.inject.vespa.version}
+junit:junit:${junit4.vespa.version}
+net.bytebuddy:byte-buddy-agent:${byte-buddy.vespa.version}
+net.bytebuddy:byte-buddy:${byte-buddy.vespa.version}
org.apache-extras.beanshell:bsh:2.0b6
org.apache.commons:commons-collections4:4.4
-org.apache.commons:commons-compress:1.24.0
-org.apache.commons:commons-lang3:3.13.0
-org.apache.maven:maven-archiver:3.6.1
-org.apache.maven:maven-artifact:3.9.4
-org.apache.maven:maven-builder-support:3.9.4
-org.apache.maven:maven-core:3.9.4
-org.apache.maven:maven-model:3.9.4
-org.apache.maven:maven-model-builder:3.9.4
-org.apache.maven:maven-plugin-api:3.9.4
-org.apache.maven:maven-repository-metadata:3.9.4
-org.apache.maven:maven-resolver-provider:3.9.4
-org.apache.maven:maven-settings:3.9.4
-org.apache.maven:maven-settings-builder:3.9.4
-org.apache.maven.enforcer:enforcer-api:3.4.1
-org.apache.maven.enforcer:enforcer-rules:3.4.1
-org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0
-org.apache.maven.plugins:maven-shade-plugin:3.5.0
+org.apache.commons:commons-compress:${commons-compress.vespa.version}
+org.apache.commons:commons-lang3:${commons-lang3.vespa.version}
+org.apache.maven.enforcer:enforcer-api:${maven-enforcer-plugin.vespa.version}
+org.apache.maven.enforcer:enforcer-rules:${maven-enforcer-plugin.vespa.version}
+org.apache.maven.plugin-tools:maven-plugin-annotations:${maven-plugin-tools.vespa.version}
+org.apache.maven.plugins:maven-shade-plugin:${maven-shade-plugin.vespa.version}
org.apache.maven.resolver:maven-resolver-api:1.9.14
org.apache.maven.resolver:maven-resolver-impl:1.9.14
org.apache.maven.resolver:maven-resolver-named-locks:1.9.14
@@ -43,42 +33,47 @@ org.apache.maven.resolver:maven-resolver-spi:1.9.14
org.apache.maven.resolver:maven-resolver-util:1.9.14
org.apache.maven.shared:maven-dependency-tree:3.2.1
org.apache.maven.shared:maven-shared-utils:3.3.4
+org.apache.maven:maven-archiver:${maven-archiver.vespa.version}
+org.apache.maven:maven-artifact:${maven-core.vespa.version}
+org.apache.maven:maven-builder-support:${maven-core.vespa.version}
+org.apache.maven:maven-core:${maven-core.vespa.version}
+org.apache.maven:maven-model-builder:${maven-core.vespa.version}
+org.apache.maven:maven-model:${maven-core.vespa.version}
+org.apache.maven:maven-plugin-api:${maven-plugin-api.vespa.version}
+org.apache.maven:maven-repository-metadata:${maven-core.vespa.version}
+org.apache.maven:maven-resolver-provider:${maven-core.vespa.version}
+org.apache.maven:maven-settings-builder:${maven-core.vespa.version}
+org.apache.maven:maven-settings:${maven-core.vespa.version}
+org.apiguardian:apiguardian-api:${apiguardian.vespa.version}
org.codehaus.plexus:plexus-archiver:4.8.0
org.codehaus.plexus:plexus-cipher:2.0
org.codehaus.plexus:plexus-classworlds:2.7.0
org.codehaus.plexus:plexus-component-annotations:2.1.0
org.codehaus.plexus:plexus-interpolation:1.26
-org.codehaus.plexus:plexus-io:3.4.1
+org.codehaus.plexus:plexus-io:${maven-enforcer-plugin.vespa.version}
org.codehaus.plexus:plexus-sec-dispatcher:2.0
-org.codehaus.plexus:plexus-utils:3.5.1
+org.codehaus.plexus:plexus-utils:${maven-shade-plugin.vespa.version}
org.eclipse.aether:aether-api:1.0.0.v20140518
org.eclipse.aether:aether-util:1.0.0.v20140518
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5
org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5
+org.hamcrest:hamcrest-core:${hamcrest.vespa.version}
+org.hamcrest:hamcrest:${hamcrest.vespa.version}
org.iq80.snappy:snappy:0.4
org.jdom:jdom2:2.0.6.1
-org.ow2.asm:asm:9.5
-org.ow2.asm:asm-commons:9.5
-org.ow2.asm:asm-tree:9.5
-org.slf4j:slf4j-api:1.7.36
-org.tukaani:xz:1.9
-org.twdata.maven:mojo-executor:2.4.0
-org.vafer:jdependency:2.8.0
-
-#[test-only]
-# Contains dependencies that are used exclusively in 'test' scope
-junit:junit:4.13.2
-net.bytebuddy:byte-buddy:1.14.8
-net.bytebuddy:byte-buddy-agent:1.14.8
-org.apiguardian:apiguardian-api:1.1.2
-org.hamcrest:hamcrest:2.2
-org.hamcrest:hamcrest-core:2.2
-org.junit.jupiter:junit-jupiter:5.10.0
-org.junit.jupiter:junit-jupiter-api:5.10.0
-org.junit.jupiter:junit-jupiter-engine:5.10.0
-org.junit.jupiter:junit-jupiter-params:5.10.0
-org.junit.platform:junit-platform-commons:1.10.0
-org.junit.platform:junit-platform-engine:1.10.0
-org.mockito:mockito-core:5.5.0
+org.junit.jupiter:junit-jupiter-api:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter-engine:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter-params:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter:${junit.vespa.version}
+org.junit.platform:junit-platform-commons:${junit.platform.vespa.version}
+org.junit.platform:junit-platform-engine:${junit.platform.vespa.version}
+org.mockito:mockito-core:${mockito.vespa.version}
org.objenesis:objenesis:3.3
-org.opentest4j:opentest4j:1.3.0
+org.opentest4j:opentest4j:${opentest4j.vespa.version}
+org.ow2.asm:asm-commons:${asm.vespa.version}
+org.ow2.asm:asm-tree:${asm.vespa.version}
+org.ow2.asm:asm:${asm.vespa.version}
+org.slf4j:slf4j-api:${slf4j.vespa.version}
+org.tukaani:xz:1.9
+org.twdata.maven:mojo-executor:${mojo-executor.vespa.version}
+org.vafer:jdependency:2.9.0
diff --git a/maven-plugins/pom.xml b/maven-plugins/pom.xml
index f12731ba9da..bc8ed090c3f 100644
--- a/maven-plugins/pom.xml
+++ b/maven-plugins/pom.xml
@@ -47,7 +47,7 @@
</goals>
<configuration>
<rules>
- <enforceDependencies implementation="com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects">
+ <enforceDependencies implementation="com.yahoo.vespa.maven.plugin.enforcer.AllowedDependencies">
<rootProjectId>com.yahoo.vespa:maven-plugins</rootProjectId>
<specFile>allowed-maven-dependencies.txt</specFile>
<ignored>
diff --git a/metrics/src/main/java/ai/vespa/metrics/docs/DocumentationGenerator.java b/metrics/src/main/java/ai/vespa/metrics/docs/DocumentationGenerator.java
new file mode 100644
index 00000000000..9d63d4af159
--- /dev/null
+++ b/metrics/src/main/java/ai/vespa/metrics/docs/DocumentationGenerator.java
@@ -0,0 +1,65 @@
+package ai.vespa.metrics.docs;
+
+import ai.vespa.metrics.ClusterControllerMetrics;
+import ai.vespa.metrics.ConfigServerMetrics;
+import ai.vespa.metrics.ContainerMetrics;
+import ai.vespa.metrics.DistributorMetrics;
+import ai.vespa.metrics.LogdMetrics;
+import ai.vespa.metrics.NodeAdminMetrics;
+import ai.vespa.metrics.SearchNodeMetrics;
+import ai.vespa.metrics.SentinelMetrics;
+import ai.vespa.metrics.SlobrokMetrics;
+import ai.vespa.metrics.StorageMetrics;
+import ai.vespa.metrics.Unit;
+import ai.vespa.metrics.VespaMetrics;
+import ai.vespa.metrics.set.DefaultMetrics;
+import ai.vespa.metrics.set.MetricSet;
+import ai.vespa.metrics.set.VespaMetricSet;
+
+import java.util.Map;
+import static ai.vespa.metrics.docs.MetricDocumentation.writeMetricDocumentation;
+import static ai.vespa.metrics.docs.MetricSetDocumentation.writeMetricSetDocumentation;
+
+/**
+ * @author olaa
+ *
+ * Helper class to generate metric reference documentation for docs.vespa.ai
+ */
+public class DocumentationGenerator {
+
+ public static void main(String[] args) {
+
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Expected exactly one argument: directory to write to");
+ }
+ var path = args[0];
+
+ var metrics = getMetrics();
+ metrics.forEach((metricType, metricArray) -> writeMetricDocumentation(path, metricArray, metricType));
+
+ var metricSets = getMetricSets();
+ metricSets.forEach((name, metricSet) -> writeMetricSetDocumentation(path, name, metricSet, metrics));
+
+ UnitDocumentation.writeUnitDocumentation(path, Unit.values());
+ }
+
+ private static Map<String, VespaMetrics[]> getMetrics() {
+ return Map.of(
+ "Container", ContainerMetrics.values(),
+ "SearchNode", SearchNodeMetrics.values(),
+ "Storage", StorageMetrics.values(),
+ "Distributor", DistributorMetrics.values(),
+ "ConfigServer", ConfigServerMetrics.values(),
+ "Logd", LogdMetrics.values(),
+ "NodeAdmin", NodeAdminMetrics.values(),
+ "Slobrok", SlobrokMetrics.values(),
+ "Sentinel", SentinelMetrics.values(),
+ "ClusterController", ClusterControllerMetrics.values()
+ );
+ }
+
+ private static Map<String, MetricSet> getMetricSets() {
+ return Map.of("Vespa", VespaMetricSet.vespaMetricSet,
+ "Default", DefaultMetrics.defaultMetricSet);
+ }
+}
diff --git a/metrics/src/main/java/ai/vespa/metrics/docs/MetricDocumentation.java b/metrics/src/main/java/ai/vespa/metrics/docs/MetricDocumentation.java
new file mode 100644
index 00000000000..fc46c785f0e
--- /dev/null
+++ b/metrics/src/main/java/ai/vespa/metrics/docs/MetricDocumentation.java
@@ -0,0 +1,57 @@
+package ai.vespa.metrics.docs;
+
+import ai.vespa.metrics.VespaMetrics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author olaa
+ */
+public class MetricDocumentation {
+
+ protected static void writeMetricDocumentation(String path, VespaMetrics[] metrics, String metricType) {
+ var referenceBuilder = new StringBuilder();
+ referenceBuilder.append(String.format("""
+ ---
+ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ title: "%s Metrics"
+ ---
+
+ <table class="table">
+ <thead>
+ <tr><th>Name</th><th>Description</th><th>Unit</th></tr>
+ </thead>
+ <tbody>
+ %s </tbody>
+ </table>
+ """, metricType, htmlRows(metrics)));
+
+ try (FileWriter fileWriter = new FileWriter(path + "/" + metricType.toLowerCase() + "-metrics-reference.html")) {
+ fileWriter.write(referenceBuilder.toString());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static String htmlRows(VespaMetrics[] metrics) {
+ return Stream.of(metrics)
+ .map(metric ->
+ String.format(
+ """
+ <tr>
+ <td><p id="%s">%s</p></td>
+ <td>%s</td>
+ <td>%s</td>
+ </tr>
+ """,
+ metric.baseName().replaceAll("\\.", "_"),
+ metric.baseName(),
+ metric.description(),
+ metric.unit().toString().toLowerCase())
+ ).collect(Collectors.joining());
+ }
+}
diff --git a/metrics/src/main/java/ai/vespa/metrics/docs/MetricSetDocumentation.java b/metrics/src/main/java/ai/vespa/metrics/docs/MetricSetDocumentation.java
new file mode 100644
index 00000000000..bafefbfedd6
--- /dev/null
+++ b/metrics/src/main/java/ai/vespa/metrics/docs/MetricSetDocumentation.java
@@ -0,0 +1,105 @@
+package ai.vespa.metrics.docs;
+
+import ai.vespa.metrics.Suffix;
+import ai.vespa.metrics.VespaMetrics;
+import ai.vespa.metrics.set.MetricSet;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author olaa
+ */
+public class MetricSetDocumentation {
+
+ protected static void writeMetricSetDocumentation(String path, String name, MetricSet metricSet, Map<String, VespaMetrics[]> metricsByType) {
+
+ var groupedBySuffix = metricSet.getMetrics()
+ .keySet()
+ .stream()
+ .map(MetricSetDocumentation::withSuffix)
+ .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toCollection(LinkedHashSet::new))));
+
+ var metricTypeByName = metricsByType.entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ entry -> Arrays.stream(entry.getValue())
+ .filter(val -> groupedBySuffix.containsKey(val.baseName()))
+ .collect(Collectors.toMap(
+ val -> val,
+ val -> groupedBySuffix.get(val.baseName()),
+ (a, b) -> a,
+ LinkedHashMap::new
+ )),
+ (a, b) -> a,
+ LinkedHashMap::new));
+
+ var referenceBuilder = new StringBuilder();
+ referenceBuilder.append(String.format("""
+ ---
+ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ title: "%s Metric Set"
+ ---""", name));
+ metricsByType.keySet()
+ .stream()
+ .sorted()
+ .filter(m -> !metricTypeByName.get(m).isEmpty())
+ .forEach(type ->
+ referenceBuilder.append(String.format("""
+
+ <h2 id="%s-metrics">%s Metrics</h2>
+ <table class="table">
+ <thead>
+ <tr><th>Name</th><th>Description</th><th>Unit</th><th>Suffixes</th></tr>
+ </thead>
+ <tbody>
+ %s </tbody>
+ </table>
+ """, type.toLowerCase(), type, htmlRows(metricTypeByName.get(type))))
+ );
+ try (FileWriter fileWriter = new FileWriter(path + "/" + metricSet.getId().toLowerCase() + "-set-metrics-reference.html")) {
+ fileWriter.write(referenceBuilder.toString());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static String htmlRows(Map<VespaMetrics, LinkedHashSet<String>> metrics) {
+ return metrics.entrySet()
+ .stream()
+ .map(entry ->
+ String.format(
+ """
+ <tr>
+ <td><p id="%s">%s</p></td>
+ <td>%s</td>
+ <td>%s</td>
+ <td>%s</td>
+ </tr>
+ """,
+ entry.getKey().baseName().replaceAll("\\.", "_"),
+ entry.getKey().baseName(),
+ entry.getKey().description(),
+ entry.getKey().unit().toString().toLowerCase(),
+ String.join(", ", entry.getValue()))
+
+ ).collect(Collectors.joining());
+ }
+
+ private static Map.Entry<String, String> withSuffix(String metricName) {
+ try {
+ var suffixIndex = metricName.lastIndexOf(".");
+ var suffix = Suffix.valueOf(metricName.substring(suffixIndex + 1));
+ return Map.entry(metricName.substring(0, suffixIndex), suffix.toString());
+ } catch (Exception e) {
+ return Map.entry(metricName, "N/A");
+ }
+ }
+
+}
diff --git a/metrics/src/main/java/ai/vespa/metrics/docs/UnitDocumentation.java b/metrics/src/main/java/ai/vespa/metrics/docs/UnitDocumentation.java
new file mode 100644
index 00000000000..03f99a076b1
--- /dev/null
+++ b/metrics/src/main/java/ai/vespa/metrics/docs/UnitDocumentation.java
@@ -0,0 +1,56 @@
+package ai.vespa.metrics.docs;
+
+
+import ai.vespa.metrics.Unit;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author olaa
+ */
+public class UnitDocumentation {
+
+ protected static void writeUnitDocumentation(String path, Unit[] units) {
+ var referenceBuilder = new StringBuilder();
+ referenceBuilder.append(String.format("""
+ ---
+ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ title: "Metric Units Reference"
+ ---
+
+
+ <table class="table">
+ <thead>
+ <tr><th>Unit</th><th>Description</th></tr>
+ </thead>
+ <tbody>
+ %s </tbody>
+ </table>
+ """, htmlRows(units)));
+
+ try (FileWriter fileWriter = new FileWriter(path + "/unit-metrics-reference.html")) {
+ fileWriter.write(referenceBuilder.toString());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static String htmlRows(Unit[] units) {
+ return Stream.of(units)
+ .map(unit ->
+ String.format(
+ """
+ <tr>
+ <td>%s</td>
+ <td>%s</td>
+ </tr>
+ """,
+ unit.fullName(),
+ unit.getDescription())
+ ).collect(Collectors.joining());
+ }
+}
diff --git a/model-integration/src/main/java/ai/vespa/embedding/ColBertEmbedder.java b/model-integration/src/main/java/ai/vespa/embedding/ColBertEmbedder.java
index aafb9877c27..4bb7bcc9225 100644
--- a/model-integration/src/main/java/ai/vespa/embedding/ColBertEmbedder.java
+++ b/model-integration/src/main/java/ai/vespa/embedding/ColBertEmbedder.java
@@ -27,7 +27,7 @@ import java.util.Arrays;
import static com.yahoo.language.huggingface.ModelInfo.TruncationStrategy.LONGEST_FIRST;
/**
- * A ColBERT embedder implementation that maps text to multiple vectors, one vector per subword id.
+ * A ColBERT embedder implementation that maps text to multiple vectors, one vector per token subword id.
* This embedder uses a HuggingFace tokenizer to produce a token sequence that is then input to a transformer model.
*
* See col-bert-embedder.def for configurable parameters.
@@ -60,10 +60,8 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
attentionMaskName = config.transformerAttentionMask();
outputName = config.transformerOutput();
maxTransformerTokens = config.transformerMaxTokens();
- if(config.maxDocumentTokens() > maxTransformerTokens)
- throw new IllegalArgumentException("maxDocumentTokens must be less than or equal to transformerMaxTokens");
- maxDocumentTokens = config.maxDocumentTokens();
- maxQueryTokens = config.maxQueryTokens();
+ maxDocumentTokens = Math.min(config.maxDocumentTokens(), maxTransformerTokens);
+ maxQueryTokens = Math.min(config.maxQueryTokens(), maxTransformerTokens);
startSequenceToken = config.transformerStartSequenceToken();
endSequenceToken = config.transformerEndSequenceToken();
maskSequenceToken = config.transformerMaskToken();
@@ -75,7 +73,8 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
.setPadding(false);
var info = HuggingFaceTokenizer.getModelInfo(tokenizerPath);
if (info.maxLength() == -1 || info.truncation() != LONGEST_FIRST) {
- // Force truncation to max token vector length accepted by model if tokenizer.json contains no valid truncation configuration
+ // Force truncation
+ // to max length accepted by model if tokenizer.json contains no valid truncation configuration
int maxLength = info.maxLength() > 0 && info.maxLength() <= config.transformerMaxTokens()
? info.maxLength()
: config.transformerMaxTokens();
@@ -115,8 +114,8 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
@Override
public Tensor embed(String text, Context context, TensorType tensorType) {
if(!verifyTensorType(tensorType)) {
- throw new IllegalArgumentException("Invalid ColBERT embedder tensor destination." +
- "Wanted a mixed 2-d mapped-indexed tensor, got " + tensorType.toString());
+ throw new IllegalArgumentException("Invalid ColBERT embedder tensor destination. " +
+ "Wanted a mixed 2-d mapped-indexed tensor, got " + tensorType);
}
if (context.getDestination().startsWith("query")) {
return embedQuery(text, context, tensorType);
@@ -152,6 +151,7 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
inputIds.add(Q_TOKEN_ID);
inputIds.addAll(ids);
inputIds.add(endSequenceToken);
+
int length = inputIds.size();
int padding = maxQueryTokens - length;
@@ -177,12 +177,9 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
throw new IllegalArgumentException("Token dimensionality does not" +
" match indexed dimensionality of " + dims);
}
- Tensor.Builder builder = Tensor.Builder.of(tensorType);
- for (int token = 0; token < result.shape()[0]; token++)
- for (int d = 0; d < result.shape()[1]; d++)
- builder.cell(TensorAddress.of(token, d), result.get(TensorAddress.of(token, d)));
+ Tensor resultTensor = toFloatTensor(result, tensorType, inputIds.size());
runtime.sampleEmbeddingLatency((System.nanoTime() - start) / 1_000_000d, context);
- return builder.build();
+ return resultTensor;
}
protected Tensor embedDocument(String text, Context context, TensorType tensorType) {
@@ -193,7 +190,6 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
List<Long> ids = encoding.ids().stream().filter(token
-> !PUNCTUATION_TOKEN_IDS.contains(token)).toList();
- ;
if (ids.size() > maxDocumentTokens - 3)
ids = ids.subList(0, maxDocumentTokens - 3);
@@ -216,29 +212,29 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
Tensor tokenEmbeddings = outputs.get(outputName);
IndexedTensor result = (IndexedTensor) tokenEmbeddings.reduce(Reduce.Aggregator.min, "d0");
Tensor contextualEmbeddings;
+ int retainedTokens = inputIds.size() -1; //Do not retain last PAD
if(tensorType.valueType() == TensorType.Value.INT8) {
- contextualEmbeddings = toBitTensor(result, tensorType);
+ contextualEmbeddings = toBitTensor(result, tensorType, retainedTokens);
} else {
- contextualEmbeddings = toFloatTensor(result, tensorType);
+ contextualEmbeddings = toFloatTensor(result, tensorType, retainedTokens);
}
-
runtime.sampleEmbeddingLatency((System.nanoTime() - start) / 1_000_000d, context);
return contextualEmbeddings;
}
- public static Tensor toFloatTensor(IndexedTensor result, TensorType type) {
+ public static Tensor toFloatTensor(IndexedTensor result, TensorType type, int nTokens) {
int size = type.indexedSubtype().dimensions().size();
if (size != 1)
throw new IllegalArgumentException("Indexed tensor must have one dimension");
- int dims = type.indexedSubtype().dimensions().get(0).size().get().intValue();
- int resultDim = (int)result.shape()[1];
- if(resultDim != dims) {
- throw new IllegalArgumentException("Not possible to map token vector embedding with " + resultDim
- + " + dimensions into tensor with " + dims);
+ int wantedDimensionality = type.indexedSubtype().dimensions().get(0).size().get().intValue();
+ int resultDimensionality = (int)result.shape()[1];
+ if(resultDimensionality != wantedDimensionality) {
+ throw new IllegalArgumentException("Not possible to map token vector embedding with " + resultDimensionality
+ + " + dimensions into tensor with " + wantedDimensionality);
}
Tensor.Builder builder = Tensor.Builder.of(type);
- for (int token = 0; token < result.shape()[0]; token++) {
- for (int d = 0; d < result.shape()[1]; d++) {
+ for (int token = 0; token < nTokens; token++) {
+ for (int d = 0; d < resultDimensionality; d++) {
var value = result.get(TensorAddress.of(token, d));
builder.cell(TensorAddress.of(token,d),value);
}
@@ -246,21 +242,21 @@ public class ColBertEmbedder extends AbstractComponent implements Embedder {
return builder.build();
}
- public static Tensor toBitTensor(IndexedTensor result, TensorType type) {
+ public static Tensor toBitTensor(IndexedTensor result, TensorType type, int nTokens) {
if (type.valueType() != TensorType.Value.INT8)
throw new IllegalArgumentException("Only a int8 tensor type can be" +
" the destination of bit packing");
int size = type.indexedSubtype().dimensions().size();
if (size != 1)
throw new IllegalArgumentException("Indexed tensor must have one dimension");
- int dims = type.indexedSubtype().dimensions().get(0).size().get().intValue();
- int resultDim = (int)result.shape()[1];
- if(resultDim/8 != dims) {
- throw new IllegalArgumentException("Not possible to pack " + resultDim
- + " + dimensions into " + dims);
+ int wantedDimensionality = type.indexedSubtype().dimensions().get(0).size().get().intValue();
+ int resultDimensionality = (int)result.shape()[1];
+ if(resultDimensionality/8 != wantedDimensionality) {
+ throw new IllegalArgumentException("Not possible to pack " + resultDimensionality
+ + " + dimensions into " + wantedDimensionality + " dimensions");
}
Tensor.Builder builder = Tensor.Builder.of(type);
- for (int token = 0; token < result.shape()[0]; token++) {
+ for (int token = 0; token < nTokens; token++) {
BitSet bitSet = new BitSet(8);
int key = 0;
for (int d = 0; d < result.shape()[1]; d++) {
diff --git a/model-integration/src/test/java/ai/vespa/embedding/ColBertEmbedderTest.java b/model-integration/src/test/java/ai/vespa/embedding/ColBertEmbedderTest.java
index 8516f6e6689..4e398f7245d 100644
--- a/model-integration/src/test/java/ai/vespa/embedding/ColBertEmbedderTest.java
+++ b/model-integration/src/test/java/ai/vespa/embedding/ColBertEmbedderTest.java
@@ -31,7 +31,7 @@ public class ColBertEmbedderTest {
"[1, 1, 1, 1, 1, 1, 1, 1]" +
"]",
TensorType.fromSpec("tensor<int8>(dt{},x[1])"),
- "tensor<int8>(dt{},x[1]):{0:1.0, 1:5.0, 2:3.0, 3:127.0, 4:-128.0, 5:-1.0}"
+ "tensor<int8>(dt{},x[1]):{0:1.0, 1:5.0, 2:3.0, 3:127.0, 4:-128.0, 5:-1.0}", 6
);
assertPackedRight(
"" +
@@ -41,7 +41,7 @@ public class ColBertEmbedderTest {
"[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1]" +
"]",
TensorType.fromSpec("tensor<int8>(dt{},x[2])"),
- "tensor<int8>(dt{},x[2]):{0:[1.0, -128.0], 1:[5.0, 1.0]}"
+ "tensor<int8>(dt{},x[2]):{0:[1.0, -128.0], 1:[5.0, 1.0]}",2
);
}
@@ -75,18 +75,18 @@ public class ColBertEmbedderTest {
}
String text = sb.toString();
Tensor fullFloat = assertEmbed("tensor<float>(dt{},x[128])", text, indexingContext);
- assertEquals(512*128,fullFloat.size());
+ assertEquals(511*128,fullFloat.size());
Tensor query = assertEmbed("tensor<float>(dt{},x[128])", text, queryContext);
assertEquals(32*128,query.size());
Tensor binaryRep = assertEmbed("tensor<int8>(dt{},x[16])", text, indexingContext);
- assertEquals(512*16,binaryRep.size());
+ assertEquals(511*16,binaryRep.size());
Tensor shortDoc = assertEmbed("tensor<int8>(dt{},x[16])", "annoyance", indexingContext);
- // 4 tokens, 16 bytes each = 64 bytes
- //because of CLS, special, sequence, SEP
- assertEquals(4*16,shortDoc.size());;
+ // 3 tokens, 16 bytes each = 48 bytes
+ //CLS [unused1] sequence
+ assertEquals(3*16,shortDoc.size());;
}
static Tensor assertEmbed(String tensorSpec, String text, Embedder.Context context) {
@@ -100,8 +100,8 @@ public class ColBertEmbedderTest {
return result;
}
- static void assertPackedRight(String numbers, TensorType destination,String expected) {
- Tensor packed = ColBertEmbedder.toBitTensor((IndexedTensor) Tensor.from(numbers), destination);
+ static void assertPackedRight(String numbers, TensorType destination,String expected, int size) {
+ Tensor packed = ColBertEmbedder.toBitTensor((IndexedTensor) Tensor.from(numbers), destination, size);
assertEquals(expected,packed.toString());
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java
index 0300d7e92ff..d902fb7b3c4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.vespa.hosted.node.admin.task.util.file.DiskSize;
import java.net.URI;
@@ -73,9 +74,7 @@ public class NodeSpec {
private final List<TrustStoreItem> trustStore;
- private final Optional<WireguardKey> wireguardPubkey;
-
- private final Optional<Instant> wireguardKeyTimestamp;
+ private final Optional<WireguardKeyWithTimestamp> wireguardKeyWithTimestamp;
private final boolean wantToRebuild;
@@ -112,8 +111,7 @@ public class NodeSpec {
Optional<URI> archiveUri,
Optional<ApplicationId> exclusiveTo,
List<TrustStoreItem> trustStore,
- Optional<WireguardKey> wireguardPubkey,
- Optional<Instant> wireguardKeyTimestamp,
+ Optional<WireguardKeyWithTimestamp> wireguardPubkey,
boolean wantToRebuild) {
if (state == NodeState.active) {
@@ -157,8 +155,7 @@ public class NodeSpec {
this.archiveUri = Objects.requireNonNull(archiveUri);
this.exclusiveTo = Objects.requireNonNull(exclusiveTo);
this.trustStore = Objects.requireNonNull(trustStore);
- this.wireguardPubkey = Objects.requireNonNull(wireguardPubkey);
- this.wireguardKeyTimestamp = Objects.requireNonNull(wireguardKeyTimestamp);
+ this.wireguardKeyWithTimestamp = Objects.requireNonNull(wireguardPubkey);
this.wantToRebuild = wantToRebuild;
}
@@ -313,9 +310,7 @@ public class NodeSpec {
return trustStore;
}
- public Optional<WireguardKey> wireguardPubkey() { return wireguardPubkey; }
-
- public Optional<Instant> wireguardKeyTimestamp() { return wireguardKeyTimestamp; }
+ public Optional<WireguardKeyWithTimestamp> wireguardKeyWithTimestamp() { return wireguardKeyWithTimestamp; }
public boolean wantToRebuild() {
return wantToRebuild;
@@ -358,8 +353,7 @@ public class NodeSpec {
Objects.equals(archiveUri, that.archiveUri) &&
Objects.equals(exclusiveTo, that.exclusiveTo) &&
Objects.equals(trustStore, that.trustStore) &&
- Objects.equals(wireguardPubkey, that.wireguardPubkey) &&
- Objects.equals(wireguardKeyTimestamp, that.wireguardKeyTimestamp) &&
+ Objects.equals(wireguardKeyWithTimestamp, that.wireguardKeyWithTimestamp) &&
Objects.equals(wantToRebuild, that.wantToRebuild);
}
@@ -398,8 +392,7 @@ public class NodeSpec {
archiveUri,
exclusiveTo,
trustStore,
- wireguardPubkey,
- wireguardKeyTimestamp,
+ wireguardKeyWithTimestamp,
wantToRebuild);
}
@@ -438,8 +431,7 @@ public class NodeSpec {
+ " archiveUri=" + archiveUri
+ " exclusiveTo=" + exclusiveTo
+ " trustStore=" + trustStore
- + " wireguardPubkey=" + wireguardPubkey
- + " wireguardKeyTimestamp=" + wireguardKeyTimestamp
+ + " wireguardPubkey=" + wireguardKeyWithTimestamp
+ " wantToRebuild=" + wantToRebuild
+ " }";
}
@@ -477,8 +469,7 @@ public class NodeSpec {
private Optional<URI> archiveUri = Optional.empty();
private Optional<ApplicationId> exclusiveTo = Optional.empty();
private List<TrustStoreItem> trustStore = List.of();
- private Optional<WireguardKey> wireguardPubkey = Optional.empty();
- private Optional<Instant> wireguardKeyTimestamp = Optional.empty();
+ private Optional<WireguardKeyWithTimestamp> wireguardPubkey = Optional.empty();
private boolean wantToRebuild = false;
public Builder() {}
@@ -514,8 +505,7 @@ public class NodeSpec {
node.archiveUri.ifPresent(this::archiveUri);
node.exclusiveTo.ifPresent(this::exclusiveTo);
trustStore(node.trustStore);
- node.wireguardPubkey.ifPresent(this::wireguardPubkey);
- node.wireguardKeyTimestamp.ifPresent(this::wireguardKeyTimestamp);
+ node.wireguardKeyWithTimestamp.ifPresent(this::wireguardKeyWithTimestamp);
wantToRebuild(node.wantToRebuild);
}
@@ -704,13 +694,13 @@ public class NodeSpec {
return this;
}
- public Builder wireguardPubkey(WireguardKey wireguardPubKey) {
- this.wireguardPubkey = Optional.of(wireguardPubKey);
+ public Builder wireguardPubkey(WireguardKey wireguardPubkey) {
+ this.wireguardPubkey = Optional.of(new WireguardKeyWithTimestamp(wireguardPubkey, Instant.EPOCH));
return this;
}
- public Builder wireguardKeyTimestamp(Instant wireguardKeyTimestamp) {
- this.wireguardKeyTimestamp = Optional.of(wireguardKeyTimestamp);
+ public Builder wireguardKeyWithTimestamp(WireguardKeyWithTimestamp wireguardPubKey) {
+ this.wireguardPubkey = Optional.of(wireguardPubKey);
return this;
}
@@ -846,7 +836,7 @@ public class NodeSpec {
wantedFirmwareCheck, currentFirmwareCheck, modelName,
resources, realResources, ipAddresses, additionalIpAddresses,
reports, events, parentHostname, archiveUri, exclusiveTo, trustStore,
- wireguardPubkey, wireguardKeyTimestamp, wantToRebuild);
+ wireguardPubkey, wantToRebuild);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
index a9cc2d698e9..17d3b51398f 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
import com.yahoo.vespa.hosted.node.admin.configserver.HttpException;
@@ -139,26 +140,28 @@ public class RealNodeRepository implements NodeRepository {
return response.nodes.stream()
.mapMulti((NodeRepositoryNode node, Consumer<WireguardPeer> consumer) -> {
- if (node.wireguardPubkey == null || node.wireguardPubkey.isEmpty()) return;
- List<VersionedIpAddress> ipAddresses = node.ipAddresses.stream()
- .map(InetAddresses::forString)
- .filter(address -> !address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isSiteLocalAddress())
- .map(VersionedIpAddress::from)
- .toList();
- if (ipAddresses.isEmpty()) return;
+ var keyWithTimestamp = createWireguardKeyWithTimestamp(node.wireguardKeyWithTimestamp,
+ node.wireguardPubkey,
+ node.wireguardKeyTimestamp);
+ if (keyWithTimestamp == null) return;
- // Unbox to prevent NPE
- long keyTimestamp = node.wireguardKeyTimestamp == null ? 0L : node.wireguardKeyTimestamp;
+ List<VersionedIpAddress> ipAddresses = getIpAddresses(node);
+ if (ipAddresses.isEmpty()) return;
- consumer.accept(new WireguardPeer(HostName.of(node.hostname),
- ipAddresses,
- WireguardKey.from(node.wireguardPubkey),
- Instant.ofEpochMilli(keyTimestamp)));
+ consumer.accept(new WireguardPeer(HostName.of(node.hostname), ipAddresses, keyWithTimestamp));
})
.sorted()
.toList();
}
+ private static List<VersionedIpAddress> getIpAddresses(NodeRepositoryNode node) {
+ return node.ipAddresses.stream()
+ .map(InetAddresses::forString)
+ .filter(address -> !address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isSiteLocalAddress())
+ .map(VersionedIpAddress::from)
+ .toList();
+ }
+
@Override
public List<WireguardPeer> getConfigserverPeers() {
GetWireguardResponse response = configServerApi.get("/nodes/v2/wireguard", GetWireguardResponse.class);
@@ -246,8 +249,9 @@ public class RealNodeRepository implements NodeRepository {
Optional.ofNullable(node.archiveUri).map(URI::create),
Optional.ofNullable(node.exclusiveTo).map(ApplicationId::fromSerializedForm),
trustStore,
- Optional.ofNullable(node.wireguardPubkey).map(WireguardKey::from),
- Optional.ofNullable(node.wireguardKeyTimestamp).map(Instant::ofEpochMilli),
+ Optional.ofNullable(createWireguardKeyWithTimestamp(node.wireguardKeyWithTimestamp,
+ node.wireguardPubkey,
+ node.wireguardKeyTimestamp)),
node.wantToRebuild);
}
@@ -364,20 +368,39 @@ public class RealNodeRepository implements NodeRepository {
node.trustStore = nodeAttributes.getTrustStore().stream()
.map(item -> new NodeRepositoryNode.TrustStoreItem(item.fingerprint(), item.expiry().toEpochMilli()))
.toList();
- node.wireguardPubkey = nodeAttributes.getWireguardPubkey().map(WireguardKey::value).orElse(null);
+ // This is used for patching, and timestamp must only be set on the server side, hence sending EPOCH.
+ node.wireguardKeyWithTimestamp = nodeAttributes.getWireguardPubkey()
+ .map(key -> new NodeRepositoryNode.WireguardKeyWithTimestamp(key.value(), 0L))
+ .orElse(null);
Map<String, JsonNode> reports = nodeAttributes.getReports();
node.reports = reports == null || reports.isEmpty() ? null : new TreeMap<>(reports);
+ // TODO wg: remove when all nodes are using new key+timestamp format
+ node.wireguardPubkey = nodeAttributes.getWireguardPubkey().map(WireguardKey::value).orElse(null);
return node;
}
private static WireguardPeer createConfigserverPeer(GetWireguardResponse.Configserver configServer) {
- // Unbox to prevent NPE
- long keyTimestamp = configServer.wireguardKeyTimestamp == null ? 0L : configServer.wireguardKeyTimestamp;
-
return new WireguardPeer(HostName.of(configServer.hostname),
configServer.ipAddresses.stream().map(VersionedIpAddress::from).toList(),
- WireguardKey.from(configServer.wireguardPubkey),
- Instant.ofEpochMilli(keyTimestamp));
+ createWireguardKeyWithTimestamp(configServer.wireguardKeyWithTimestamp,
+ configServer.wireguardPubkey,
+ configServer.wireguardKeyTimestamp));
+ }
+
+ private static WireguardKeyWithTimestamp createWireguardKeyWithTimestamp(NodeRepositoryNode.WireguardKeyWithTimestamp wirguardJson,
+ String oldKeyJson, Long oldTimestampJson) {
+ if (wirguardJson != null && wirguardJson.key != null && ! wirguardJson.key.isEmpty()) {
+ return new WireguardKeyWithTimestamp(WireguardKey.from(wirguardJson.key),
+ Instant.ofEpochMilli(wirguardJson.timestamp));
+ // TODO wg: remove when all nodes are using new key+timestamp format
+ } else if (oldKeyJson != null) {
+ var timestamp = oldTimestampJson != null ? oldTimestampJson : 0L;
+ return new WireguardKeyWithTimestamp(WireguardKey.from(oldKeyJson),
+ Instant.ofEpochMilli(timestamp));
+ // TODO END
+ } else return null;
+
}
+
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetWireguardResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetWireguardResponse.java
index dcbf4cc163f..47903795ef7 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetWireguardResponse.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetWireguardResponse.java
@@ -27,27 +27,23 @@ public class GetWireguardResponse {
public static class Configserver {
@JsonProperty("hostname")
- public final String hostname;
+ public String hostname;
@JsonProperty("ipAddresses")
- public final List<String> ipAddresses;
+ public List<String> ipAddresses;
+
+ @JsonProperty("wireguard")
+ public NodeRepositoryNode.WireguardKeyWithTimestamp wireguardKeyWithTimestamp;
- @JsonProperty("wireguardPubkey")
- public final String wireguardPubkey;
+ // TODO wg: remove when all nodes use new key+timestamp format
+ @JsonProperty("wireguardPubkey")
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public String wireguardPubkey;
@JsonProperty("wireguardKeyTimestamp")
- public final Long wireguardKeyTimestamp;
-
- @JsonCreator
- public Configserver(@JsonProperty("hostname") String hostname,
- @JsonProperty("ipAddresses") List<String> ipAddresses,
- @JsonProperty("wireguardPubkey") String wireguardPubkey,
- @JsonProperty("wireguardKeyTimestamp") Long wireguardKeyTimestamp) {
- this.hostname = hostname;
- this.ipAddresses = ipAddresses;
- this.wireguardPubkey = wireguardPubkey;
- this.wireguardKeyTimestamp = wireguardKeyTimestamp;
- }
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public Long wireguardKeyTimestamp;
+
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java
index 3d0d052a877..35ca757ebbe 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java
@@ -92,6 +92,10 @@ public class NodeRepositoryNode {
@JsonProperty("trustStore")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public List<TrustStoreItem> trustStore;
+ @JsonProperty("wireguard")
+ public WireguardKeyWithTimestamp wireguardKeyWithTimestamp;
+
+ // TODO wg: remove separate key and timestamp when all nodes use new keyWithTimestamp
@JsonProperty("wireguardPubkey")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String wireguardPubkey;
@@ -141,13 +145,25 @@ public class NodeRepositoryNode {
", exclusiveTo='" + exclusiveTo + '\'' +
", history=" + history +
", trustStore=" + trustStore +
- ", wireguardPubkey=" + wireguardPubkey +
- ", wireguardKeyTimestamp=" + wireguardKeyTimestamp +
+ ", wireguard=" + wireguardKeyTimestamp +
", reports=" + reports +
'}';
}
@JsonIgnoreProperties(ignoreUnknown = true)
+ public static class WireguardKeyWithTimestamp {
+ @JsonProperty("key")
+ public String key;
+ @JsonProperty("timestamp")
+ public long timestamp;
+
+ public WireguardKeyWithTimestamp(@JsonProperty("key") String key, @JsonProperty("timestamp") long timestamp) {
+ this.key = key;
+ this.timestamp = timestamp;
+ }
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
public static class Owner {
@JsonProperty("tenant")
public String tenant;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeer.java
index b5428f57f08..e5ab9a1ce31 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeer.java
@@ -1,10 +1,9 @@
package com.yahoo.vespa.hosted.node.admin.wireguard;
import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.vespa.hosted.node.admin.task.util.network.VersionedIpAddress;
-import java.time.Instant;
import java.util.List;
/**
@@ -15,8 +14,7 @@ import java.util.List;
*/
public record WireguardPeer(HostName hostname,
List<VersionedIpAddress> ipAddresses,
- WireguardKey publicKey,
- Instant wireguardKeyTimestamp) implements Comparable<WireguardPeer> {
+ WireguardKeyWithTimestamp keyWithTimestamp) implements Comparable<WireguardPeer> {
public WireguardPeer {
if (ipAddresses.isEmpty()) throw new IllegalArgumentException("No IP addresses for peer node " + hostname.value());
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepositoryTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepositoryTest.java
index 98e65d03f2f..ee3eac22d02 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepositoryTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepositoryTest.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl;
@@ -140,6 +141,7 @@ public class RealNodeRepositoryTest {
var dockerImage = "registry.example.com/repo/image-1:6.2.3";
var wireguardKey = WireguardKey.from("111122223333444455556666777788889999000042c=");
var wireguardKeyTimestamp = Instant.ofEpochMilli(123L); // Instant from clock in MockNodeRepository
+ var keyWithTimestamp = new WireguardKeyWithTimestamp(wireguardKey, wireguardKeyTimestamp);
nodeRepositoryApi.updateNodeAttributes(
hostname,
@@ -151,8 +153,7 @@ public class RealNodeRepositoryTest {
NodeSpec hostSpec = nodeRepositoryApi.getOptionalNode(hostname).orElseThrow();
assertEquals(1, hostSpec.currentRestartGeneration().orElseThrow());
assertEquals(dockerImage, hostSpec.currentDockerImage().orElseThrow().asString());
- assertEquals(wireguardKey.value(), hostSpec.wireguardPubkey().orElseThrow().value());
- assertEquals(wireguardKeyTimestamp, hostSpec.wireguardKeyTimestamp().orElseThrow());
+ assertEquals(keyWithTimestamp, hostSpec.wireguardKeyWithTimestamp().orElseThrow());
}
@Test
@@ -215,7 +216,7 @@ public class RealNodeRepositoryTest {
assertWireguardPeer(cfgPeers.get(0), "cfg1.yahoo.com",
"::201:1",
"lololololololololololololololololololololoo=",
- Instant.ofEpochMilli(456L));
+ 456L);
//// Exclave nodes ////
@@ -227,16 +228,17 @@ public class RealNodeRepositoryTest {
assertWireguardPeer(exclavePeers.get(0), "dockerhost2.yahoo.com",
"::101:1",
"000011112222333344445555666677778888999900c=",
- Instant.ofEpochMilli(123L));
+ 123L);
}
private void assertWireguardPeer(WireguardPeer peer, String hostname, String ipv6,
- String publicKey, Instant keyTimestamp) {
+ String publicKey, long keyTimestamp) {
assertEquals(hostname, peer.hostname().value());
assertEquals(1, peer.ipAddresses().size());
assertIp(peer.ipAddresses().get(0), ipv6, 6);
- assertEquals(publicKey, peer.publicKey().value());
- assertEquals(keyTimestamp, peer.wireguardKeyTimestamp());
+ var expectedKeyWithTimestamp = new WireguardKeyWithTimestamp(WireguardKey.from(publicKey),
+ Instant.ofEpochMilli(keyTimestamp));
+ assertEquals(expectedKeyWithTimestamp, peer.keyWithTimestamp());
}
private void assertIp(VersionedIpAddress ip, String expectedIp, int expectedVersion) {
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeerTest.java
index cd76b221c9e..6ee896e3db6 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/wireguard/WireguardPeerTest.java
@@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.node.admin.wireguard;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.vespa.hosted.node.admin.task.util.network.VersionedIpAddress;
import org.junit.jupiter.api.Test;
@@ -31,6 +32,7 @@ public class WireguardPeerTest {
private static WireguardPeer peer(String hostname) {
return new WireguardPeer(HostName.of(hostname), List.of(VersionedIpAddress.from("::1:1")),
- WireguardKey.generateRandomForTesting(), Instant.EPOCH);
+ new WireguardKeyWithTimestamp(WireguardKey.generateRandomForTesting(), Instant.EPOCH));
}
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
index 24159b88a9b..d5e891a33c7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
@@ -10,7 +10,7 @@ import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancers;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -64,8 +64,7 @@ public final class Node implements Nodelike {
private final CloudAccount cloudAccount;
/** Only set for configservers and exclave nodes */
- private final Optional<WireguardKey> wireguardPubKey;
- private final Optional<Instant> wireguardKeyTimestamp;
+ private final Optional<WireguardKeyWithTimestamp> wireguardPubKey;
/** Record of the last event of each type happening to this node */
private final History history;
@@ -96,8 +95,8 @@ public final class Node implements Nodelike {
NodeType type, Reports reports, Optional<String> modelName, Optional<TenantName> reservedTo,
Optional<ApplicationId> exclusiveToApplicationId, Optional<Duration> hostTTL, Optional<Instant> hostEmptyAt,
Optional<ClusterSpec.Type> exclusiveToClusterType, Optional<String> switchHostname,
- List<TrustStoreItem> trustStoreItems, CloudAccount cloudAccount, Optional<WireguardKey> wireguardPubKey,
- Optional<Instant> wireguardKeyTimestamp) {
+ List<TrustStoreItem> trustStoreItems, CloudAccount cloudAccount,
+ Optional<WireguardKeyWithTimestamp> wireguardPubKey) {
this.id = Objects.requireNonNull(id, "A node must have an ID");
this.extraId = Objects.requireNonNull(extraId, "Extra ID cannot be null");
this.hostname = requireNonEmptyString(hostname, "A node must have a hostname");
@@ -120,7 +119,6 @@ public final class Node implements Nodelike {
this.trustStoreItems = Objects.requireNonNull(trustStoreItems).stream().distinct().toList();
this.cloudAccount = Objects.requireNonNull(cloudAccount);
this.wireguardPubKey = Objects.requireNonNull(wireguardPubKey);
- this.wireguardKeyTimestamp = Objects.requireNonNull(wireguardKeyTimestamp);
if (state == State.active)
requireNonEmpty(ipConfig.primary(), "Active node " + hostname + " must have at least one valid IP address");
@@ -264,15 +262,10 @@ public final class Node implements Nodelike {
}
/** Returns the wireguard public key of this node. Only relevant for enclave nodes. */
- public Optional<WireguardKey> wireguardPubKey() {
+ public Optional<WireguardKeyWithTimestamp> wireguardPubKey() {
return wireguardPubKey;
}
- /** Returns the timestamp of the wireguard key of this node. Only relevant for enclave nodes. */
- public Optional<Instant> wireguardKeyTimestamp() {
- return wireguardKeyTimestamp;
- }
-
/**
* Returns a copy of this where wantToFail is set to true and history is updated to reflect this.
*/
@@ -367,16 +360,14 @@ public final class Node implements Nodelike {
public Node with(Status status) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type,
reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a node with the type assigned to the given value */
public Node with(NodeType type) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type,
reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a node with the flavor assigned to the given value */
@@ -385,40 +376,35 @@ public final class Node implements Nodelike {
History updateHistory = history.with(new History.Event(History.Event.Type.resized, agent, instant));
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, updateHistory, type,
reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with the reboot generation set to generation */
public Node withReboot(Generation generation) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status.withReboot(generation), state, allocation,
history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with given id set */
public Node withId(String id) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation,
history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with model name set to given value */
public Node withModelName(String modelName) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, Optional.of(modelName), reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with model name cleared */
public Node withoutModelName() {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, Optional.empty(), reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with a history record saying it was detected to be down at this instant */
@@ -460,24 +446,21 @@ public final class Node implements Nodelike {
public Node with(Allocation allocation) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, Optional.of(allocation), history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node with IP config set to the given value. */
public Node with(IP.Config ipConfig) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node with the parent hostname assigned to the given value. */
public Node withParentHostname(String parentHostname) {
return new Node(id, extraId, ipConfig, hostname, Optional.of(parentHostname), flavor, status, state, allocation,
history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withReservedTo(TenantName tenant) {
@@ -485,73 +468,59 @@ public final class Node implements Nodelike {
throw new IllegalArgumentException("Only host nodes can be reserved, " + hostname + " has type " + type);
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, Optional.of(tenant), exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node which is not reserved to a tenant */
public Node withoutReservedTo() {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, Optional.empty(), exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExclusiveToApplicationId(ApplicationId exclusiveTo) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, Optional.ofNullable(exclusiveTo), hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExtraId(Optional<String> extraId) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withHostTTL(Duration hostTTL) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, Optional.ofNullable(hostTTL), hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withHostEmptyAt(Instant hostEmptyAt) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, Optional.ofNullable(hostEmptyAt),
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExclusiveToClusterType(ClusterSpec.Type exclusiveTo) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
- public Node withWireguardPubkey(WireguardKey wireguardPubkey) {
+ public Node withWireguardPubkey(WireguardKeyWithTimestamp wireguardPubkey) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, Optional.ofNullable(wireguardPubkey),
- wireguardKeyTimestamp);
- }
-
- public Node withWireguardKeyTimestamp(Instant wireguardKeyTimestamp) {
- return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- Optional.ofNullable(wireguardKeyTimestamp));
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount,
+ Optional.ofNullable(wireguardPubkey));
}
/** Returns a copy of this node with switch hostname set to given value */
public Node withSwitchHostname(String switchHostname) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount,
+ wireguardPubKey);
}
/** Returns a copy of this node with switch hostname unset */
@@ -604,22 +573,19 @@ public final class Node implements Nodelike {
public Node with(History history) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node with(Reports reports) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node with(List<TrustStoreItem> trustStoreItems) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
- wireguardKeyTimestamp);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
private static Optional<String> requireNonEmptyString(Optional<String> value, String message) {
@@ -767,8 +733,7 @@ public final class Node implements Nodelike {
private History history;
private List<TrustStoreItem> trustStoreItems;
private CloudAccount cloudAccount = CloudAccount.empty;
- private WireguardKey wireguardPubKey;
- private Instant wireguardKeyTimestamp;
+ private WireguardKeyWithTimestamp wireguardPubKey;
private Builder(String id, String hostname, Flavor flavor, State state, NodeType type) {
this.id = id;
@@ -858,16 +823,11 @@ public final class Node implements Nodelike {
return this;
}
- public Builder wireguardPubKey(WireguardKey wireguardPubKey) {
+ public Builder wireguardKey(WireguardKeyWithTimestamp wireguardPubKey) {
this.wireguardPubKey = wireguardPubKey;
return this;
}
- public Builder wireguardKeyTimestamp(Instant wireguardKeyTimestamp) {
- this.wireguardKeyTimestamp = wireguardKeyTimestamp;
- return this;
- }
-
public Node build() {
return new Node(id, Optional.empty(), Optional.ofNullable(ipConfig).orElse(IP.Config.EMPTY), hostname, Optional.ofNullable(parentHostname),
flavor, Optional.ofNullable(status).orElseGet(Status::initial), state, Optional.ofNullable(allocation),
@@ -875,7 +835,7 @@ public final class Node implements Nodelike {
Optional.ofNullable(modelName), Optional.ofNullable(reservedTo), Optional.ofNullable(exclusiveToApplicationId),
Optional.ofNullable(hostTTL), Optional.ofNullable(hostEmptyAt), Optional.ofNullable(exclusiveToClusterType),
Optional.ofNullable(switchHostname), Optional.ofNullable(trustStoreItems).orElseGet(List::of), cloudAccount,
- Optional.ofNullable(wireguardPubKey), Optional.ofNullable(wireguardKeyTimestamp));
+ Optional.ofNullable(wireguardPubKey));
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java
index 8069c9c089b..286ec2451f8 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java
@@ -160,7 +160,7 @@ public class AllocatableResources {
for (Node node : nodes) {
sum = sum.add(nodeRepository.resourcesCalculator().realResourcesOf(node, nodeRepository).justNumbers());
}
- return nodes.get(0).allocation().get().requestedResources().justNonNumbers()
+ return nodes.get(0).allocation().get().requestedResources()
.withVcpu(sum.vcpu() / nodes.size())
.withMemoryGb(sum.memoryGb() / nodes.size())
.withDiskGb(sum.diskGb() / nodes.size())
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java
index e228d31384c..f42d1ce9bd3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java
@@ -21,7 +21,8 @@ import java.util.Set;
public class LoadBalancerInstance {
private final Optional<DomainName> hostname;
- private final Optional<String> ipAddress;
+ private final Optional<String> ip4Address;
+ private final Optional<String> ip6Address;
private final Optional<DnsZone> dnsZone;
private final Set<Integer> ports;
private final Set<String> networks;
@@ -30,11 +31,12 @@ public class LoadBalancerInstance {
private final List<PrivateServiceId> serviceIds;
private final CloudAccount cloudAccount;
- public LoadBalancerInstance(Optional<DomainName> hostname, Optional<String> ipAddress,
+ public LoadBalancerInstance(Optional<DomainName> hostname, Optional<String> ip4Address, Optional<String> ip6Address,
Optional<DnsZone> dnsZone, Set<Integer> ports, Set<String> networks, Set<Real> reals,
ZoneEndpoint settings, List<PrivateServiceId> serviceIds, CloudAccount cloudAccount) {
this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null");
- this.ipAddress = Objects.requireNonNull(ipAddress, "ip must be non-null");
+ this.ip4Address = Objects.requireNonNull(ip4Address, "ip4Address must be non-null");
+ this.ip6Address = Objects.requireNonNull(ip6Address, "ip6Address must be non-null");
this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null");
this.ports = ImmutableSortedSet.copyOf(requirePorts(ports));
this.networks = ImmutableSortedSet.copyOf(Objects.requireNonNull(networks, "networks must be non-null"));
@@ -43,9 +45,9 @@ public class LoadBalancerInstance {
this.serviceIds = List.copyOf(Objects.requireNonNull(serviceIds, "private service id must be non-null"));
this.cloudAccount = Objects.requireNonNull(cloudAccount, "cloudAccount must be non-null");
- if (hostname.isEmpty() == ipAddress.isEmpty()) {
- throw new IllegalArgumentException("Exactly 1 of hostname=%s and ipAddress=%s must be set".formatted(
- hostname.map(DomainName::value).orElse("<empty>"), ipAddress.orElse("<empty>")));
+ if (hostname.isEmpty() == ip4Address.isEmpty()) {
+ throw new IllegalArgumentException("Exactly 1 of hostname=%s and ip4Address=%s must be set".formatted(
+ hostname.map(DomainName::value).orElse("<empty>"), ip4Address.orElse("<empty>")));
}
}
@@ -54,9 +56,14 @@ public class LoadBalancerInstance {
return hostname;
}
- /** IP address of this (public) load balancer */
- public Optional<String> ipAddress() {
- return ipAddress;
+ /** IPv4 address of this (public) load balancer */
+ public Optional<String> ip4Address() {
+ return ip4Address;
+ }
+
+ /** IPv6 address of this (public) load balancer */
+ public Optional<String> ip6Address() {
+ return ip6Address;
}
/** ID of the DNS zone associated with this */
@@ -114,7 +121,7 @@ public class LoadBalancerInstance {
public LoadBalancerInstance with(Set<Real> reals, ZoneEndpoint settings, Optional<PrivateServiceId> serviceId) {
List<PrivateServiceId> ids = new ArrayList<>(serviceIds);
serviceId.filter(id -> ! ids.contains(id)).ifPresent(ids::add);
- return new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks,
+ return new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks,
reals, settings, ids,
cloudAccount);
}
@@ -123,7 +130,7 @@ public class LoadBalancerInstance {
public LoadBalancerInstance withServiceIds(List<PrivateServiceId> serviceIds) {
List<PrivateServiceId> ids = new ArrayList<>(serviceIds);
for (PrivateServiceId id : this.serviceIds) if ( ! ids.contains(id)) ids.add(id);
- return new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks, reals, settings, ids, cloudAccount);
+ return new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks, reals, settings, ids, cloudAccount);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
index a79766a577d..c79ccc2aece 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
@@ -57,6 +57,7 @@ public class LoadBalancerServiceMock implements LoadBalancerService {
var instance = new LoadBalancerInstance(
Optional.of(DomainName.of("lb-" + spec.application().toShortString() + "-" + spec.cluster().value())),
Optional.empty(),
+ Optional.empty(),
Optional.of(new DnsZone("zone-id-1")),
Collections.singleton(4443),
ImmutableSet.of("10.2.3.0/24", "10.4.5.0/24"),
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
index e49d1b302cf..073662b39fe 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
@@ -45,6 +45,7 @@ public class SharedLoadBalancerService implements LoadBalancerService {
return new LoadBalancerInstance(Optional.of(DomainName.of(vipHostname)),
Optional.empty(),
Optional.empty(),
+ Optional.empty(),
Set.of(4443),
Set.of(),
spec.reals(),
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
index 3c3868bfeb8..e4e08e5a15c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
@@ -47,6 +47,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
import static com.yahoo.stream.CustomCollectors.toLinkedMap;
import static java.util.stream.Collectors.collectingAndThen;
@@ -222,7 +223,7 @@ public class CuratorDb {
node.type(), node.reports(), node.modelName(), node.reservedTo(),
node.exclusiveToApplicationId(), node.hostTTL(), node.hostEmptyAt(),
node.exclusiveToClusterType(), node.switchHostname(), node.trustedCertificates(),
- node.cloudAccount(), node.wireguardPubKey(), node.wireguardKeyTimestamp());
+ node.cloudAccount(), node.wireguardPubKey());
curatorTransaction.add(createOrSet(nodePath(newNode), nodeSerializer.toJson(newNode)));
writtenNodes.add(newNode);
}
@@ -456,7 +457,12 @@ public class CuratorDb {
transaction.onCommitted(() -> {
for (var lb : loadBalancers) {
if (lb.state() == fromState) continue;
- Optional<String> target = lb.instance().flatMap(instance -> instance.hostname().map(DomainName::value).or(instance::ipAddress));
+ Optional<String> target = lb.instance()
+ .flatMap(instance -> instance.hostname()
+ .map(DomainName::value)
+ .or(() -> Optional.of(Stream.concat(instance.ip4Address().stream(),
+ instance.ip6Address().stream())
+ .collect(Collectors.joining(",")))));
if (fromState == null) {
log.log(Level.INFO, () -> "Creating " + lb.id() + target.map(t -> " (" + t + ")").orElse("") +
" in " + lb.state());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
index b85d96c6b54..d329676f842 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
@@ -45,6 +45,7 @@ public class LoadBalancerSerializer {
private static final String idField = "id";
private static final String hostnameField = "hostname";
private static final String lbIpAddressField = "ipAddress";
+ private static final String lbIp6AddressField = "ip6Address";
private static final String stateField = "state";
private static final String changedAtField = "changedAt";
private static final String dnsZoneField = "dnsZone";
@@ -69,7 +70,8 @@ public class LoadBalancerSerializer {
root.setString(idField, loadBalancer.id().serializedForm());
loadBalancer.instance().flatMap(LoadBalancerInstance::hostname).ifPresent(hostname -> root.setString(hostnameField, hostname.value()));
- loadBalancer.instance().flatMap(LoadBalancerInstance::ipAddress).ifPresent(ip -> root.setString(lbIpAddressField, ip));
+ loadBalancer.instance().flatMap(LoadBalancerInstance::ip4Address).ifPresent(ip -> root.setString(lbIpAddressField, ip));
+ loadBalancer.instance().flatMap(LoadBalancerInstance::ip6Address).ifPresent(ip -> root.setString(lbIp6AddressField, ip));
root.setString(stateField, asString(loadBalancer.state()));
root.setLong(changedAtField, loadBalancer.changedAt().toEpochMilli());
loadBalancer.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> root.setString(dnsZoneField, dnsZone.id()));
@@ -123,7 +125,8 @@ public class LoadBalancerSerializer {
object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString()));
Optional<DomainName> hostname = optionalString(object.field(hostnameField), Function.identity()).filter(s -> !s.isEmpty()).map(DomainName::of);
- Optional<String> ipAddress = optionalString(object.field(lbIpAddressField), Function.identity()).filter(s -> !s.isEmpty());
+ Optional<String> ip4Address = optionalString(object.field(lbIpAddressField), Function.identity()).filter(s -> !s.isEmpty());
+ Optional<String> ip6Address = optionalString(object.field(lbIp6AddressField), Function.identity()).filter(s -> !s.isEmpty());
Optional<DnsZone> dnsZone = optionalString(object.field(dnsZoneField), DnsZone::new);
ZoneEndpoint settings = zoneEndpoint(object.field(settingsField));
Optional<PrivateServiceId> serviceId = optionalString(object.field(serviceIdField), PrivateServiceId::of);
@@ -131,9 +134,9 @@ public class LoadBalancerSerializer {
object.field(serviceIdsField).traverse((ArrayTraverser) (__, serviceIdObject) -> serviceIds.add(PrivateServiceId.of(serviceIdObject.asString())));
if (serviceIds.isEmpty()) serviceId.ifPresent(serviceIds::add); // TODO: remove after winter vacation '23
CloudAccount cloudAccount = optionalString(object.field(cloudAccountField), CloudAccount::from).orElse(CloudAccount.empty);
- Optional<LoadBalancerInstance> instance = hostname.isEmpty() && ipAddress.isEmpty()
+ Optional<LoadBalancerInstance> instance = hostname.isEmpty() && ip4Address.isEmpty() && ip6Address.isEmpty()
? Optional.empty()
- : Optional.of(new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks, reals, settings, serviceIds, cloudAccount));
+ : Optional.of(new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks, reals, settings, serviceIds, cloudAccount));
return new LoadBalancer(LoadBalancerId.fromSerializedForm(object.field(idField).asString()),
instance,
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
index 870e678a250..73531d650d5 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
@@ -16,6 +16,7 @@ import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.config.provision.serialization.NetworkPortsSerializer;
import com.yahoo.slime.ArrayTraverser;
@@ -188,8 +189,10 @@ public class NodeSerializer {
if (!node.cloudAccount().isUnspecified()) {
object.setString(cloudAccountKey, node.cloudAccount().value());
}
- node.wireguardPubKey().ifPresent(pubKey -> object.setString(wireguardPubKeyKey, pubKey.value()));
- node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong(wireguardKeyTimestampKey, timestamp.toEpochMilli()));
+ node.wireguardPubKey().ifPresent(pubKey -> {
+ object.setString(wireguardPubKeyKey, pubKey.key().value());
+ object.setLong(wireguardKeyTimestampKey, pubKey.timestamp().toEpochMilli());
+ });
}
private void toSlime(Flavor flavor, Cursor object) {
@@ -284,8 +287,7 @@ public class NodeSerializer {
SlimeUtils.optionalString(object.field(switchHostnameKey)),
trustedCertificatesFromSlime(object),
SlimeUtils.optionalString(object.field(cloudAccountKey)).map(CloudAccount::from).orElse(CloudAccount.empty),
- SlimeUtils.optionalString(object.field(wireguardPubKeyKey)).map(WireguardKey::from),
- SlimeUtils.optionalInstant(object.field(wireguardKeyTimestampKey)));
+ wireguardKeyWithTimestampFromSlime(object.field(wireguardPubKeyKey), object.field(wireguardKeyTimestampKey)));
}
private Status statusFromSlime(Inspector object) {
@@ -397,6 +399,13 @@ public class NodeSerializer {
.toList();
}
+ private Optional<WireguardKeyWithTimestamp> wireguardKeyWithTimestampFromSlime(Inspector keyObject, Inspector timestampObject) {
+ if ( ! keyObject.valid()) return Optional.empty();
+ return SlimeUtils.optionalString(keyObject).map(
+ key -> new WireguardKeyWithTimestamp(WireguardKey.from(key),
+ SlimeUtils.optionalInstant(timestampObject).orElse(null)));
+ }
+
// ----------------- Enum <-> string mappings ----------------------------------------
/** Returns the event type, or null if this event type should be ignored */
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 09f947503f6..20aa7d8181e 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
@@ -57,7 +57,8 @@ public class LoadBalancersResponse extends SlimeJsonResponse {
lbObject.setString("instance", lb.id().application().instance().value());
lbObject.setString("cluster", lb.id().cluster().value());
lb.instance().flatMap(LoadBalancerInstance::hostname).ifPresent(hostname -> lbObject.setString("hostname", hostname.value()));
- lb.instance().flatMap(LoadBalancerInstance::ipAddress).ifPresent(ipAddress -> lbObject.setString("ipAddress", ipAddress));
+ lb.instance().flatMap(LoadBalancerInstance::ip4Address).ifPresent(ip -> lbObject.setString("ipAddress", ip));
+ lb.instance().flatMap(LoadBalancerInstance::ip6Address).ifPresent(ip -> lbObject.setString("ip6Address", ip));
lb.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> lbObject.setString("dnsZone", dnsZone.id()));
Cursor networkArray = lbObject.setArray("networks");
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 9f1ab3dc3d5..cad034e01aa 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.ObjectTraverser;
@@ -108,7 +109,8 @@ public class NodePatcher {
"reports",
"trustStore",
"vespaVersion",
- "wireguardPubkey"));
+ "wireguardPubkey", // TODO wg: remove when all nodes use new key+timestamp format
+ "wireguard"));
if (!disallowedFields.isEmpty()) {
throw new IllegalArgumentException("Patching fields not supported: " + disallowedFields);
}
@@ -271,9 +273,13 @@ public class NodePatcher {
return value.type() == Type.NIX ? node.withoutSwitchHostname() : node.withSwitchHostname(value.asString());
case "trustStore":
return nodeWithTrustStore(node, value);
- case "wireguardPubkey":
- return node.withWireguardPubkey(SlimeUtils.optionalString(value).map(WireguardKey::new).orElse(null))
- .withWireguardKeyTimestamp(clock.instant());
+ case "wireguard":
+ // This is where we set the key timestamp.
+ var key = SlimeUtils.optionalString(value.field("key")).map(WireguardKey::new).orElse(null);
+ return node.withWireguardPubkey(new WireguardKeyWithTimestamp(key, clock.instant()));
+ case "wireguardPubkey": // TODO wg: remove when all nodes use new key+timestamp format
+ var oldKey = SlimeUtils.optionalString(value).map(WireguardKey::new).orElse(null);
+ return node.withWireguardPubkey(new WireguardKeyWithTimestamp(oldKey, clock.instant()));
default:
throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field");
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
index a8f526544d7..05bb0a27d69 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.serialization.NetworkPortsSerializer;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.restapi.SlimeJsonResponse;
@@ -192,8 +193,13 @@ class NodesResponse extends SlimeJsonResponse {
if (!node.cloudAccount().isUnspecified()) {
object.setString("cloudAccount", node.cloudAccount().value());
}
- node.wireguardPubKey().ifPresent(key -> object.setString("wireguardPubkey", key.value()));
- node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong("wireguardKeyTimestamp", timestamp.toEpochMilli()));
+ node.wireguardPubKey().ifPresent(key -> toSlime(key, object.setObject("wireguard")));
+
+ // TODO wg: remove when all nodes have upgraded to new key+timestamp format
+ node.wireguardPubKey().ifPresent(key -> {
+ object.setString("wireguardPubkey", key.key().value());
+ object.setLong("wireguardKeyTimestamp", key.timestamp().toEpochMilli());
+ });
}
private Version resolveVersionFlag(StringFlag flag, Node node, Allocation allocation) {
@@ -237,6 +243,11 @@ class NodesResponse extends SlimeJsonResponse {
}
}
+ static void toSlime(WireguardKeyWithTimestamp keyWithTimestamp, Cursor object) {
+ object.setString("key", keyWithTimestamp.key().value());
+ object.setLong("timestamp", keyWithTimestamp.timestamp().toEpochMilli());
+ }
+
private Optional<DockerImage> currentContainerImage(Node node) {
if (node.status().containerImage().isPresent()) {
return node.status().containerImage();
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java
index 16e85dfa48a..e29c4f1b87a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java
@@ -1,7 +1,7 @@
package com.yahoo.vespa.hosted.provision.restapi;
import com.yahoo.config.provision.NodeType;
-import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.vespa.hosted.provision.Node;
@@ -10,9 +10,9 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.IP;
import java.net.InetAddress;
-import java.time.Instant;
import java.util.List;
-import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.provision.restapi.NodesResponse.toSlime;
/**
* A response containing the wireguard peer config for each configserver that has a public key.
@@ -36,17 +36,20 @@ public class WireguardResponse extends SlimeJsonResponse {
.toList();
if (ipAddresses.isEmpty()) continue;
- addConfigserver(cfgArray.addObject(), cfg.hostname(), cfg.wireguardPubKey().get(),
- cfg.wireguardKeyTimestamp(), ipAddresses);
+ addConfigserver(cfgArray.addObject(), cfg.hostname(), cfg.wireguardPubKey().get(), ipAddresses);
}
}
- private void addConfigserver(Cursor cfgEntry, String hostname, WireguardKey key, Optional<Instant> keyTimestamp,
+ private void addConfigserver(Cursor cfgEntry, String hostname, WireguardKeyWithTimestamp keyWithTimestamp,
List<String> ipAddresses) {
cfgEntry.setString("hostname", hostname);
- cfgEntry.setString("wireguardPubkey", key.value());
- cfgEntry.setLong("wireguardKeyTimestamp", keyTimestamp.orElse(Instant.EPOCH).toEpochMilli());
+
+ // TODO wg: remove when all nodes are using new key+timestamp format
+ cfgEntry.setString("wireguardPubkey", keyWithTimestamp.key().value());
+ cfgEntry.setLong("wireguardKeyTimestamp", keyWithTimestamp.timestamp().toEpochMilli());
+
NodesResponse.ipAddressesToSlime(ipAddresses, cfgEntry.setArray("ipAddresses"));
+ toSlime(keyWithTimestamp, cfgEntry.setObject("wireguard"));
}
private static boolean isPublicIp(String ipAddress) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
index 5cdb08d6fc6..a72c2fb0b9c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
@@ -79,7 +79,7 @@ public class MockHostProvisioner implements HostProvisioner {
if (hostFlavor == null)
hostFlavor = flavors.stream()
.filter(f -> request.sharing() == HostSharing.exclusive ? compatible(f, request.resources())
- : f.resources().satisfies(request.resources()))
+ : satisfies(f, request.resources()))
.filter(f -> realHostResourcesWithinLimits.test(f.resources()))
.findFirst()
.orElseThrow(() -> new NodeAllocationException("No host flavor matches " + request.resources(), true));
@@ -223,6 +223,10 @@ public class MockHostProvisioner implements HostProvisioner {
return flavor.resources().compatibleWith(resourcesToVerify);
}
+ public boolean satisfies(Flavor flavor, NodeResources resources) {
+ return flavor.resources().satisfies(resources);
+ }
+
private List<HostName> createHostnames(NodeType hostType, Flavor flavor, int hostIndex) {
long numAddresses = Math.max(2, Math.round(flavor.resources().bandwidthGbps()));
return IntStream.range(1, (int) numAddresses)
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 72225763381..2fb549acc11 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -21,6 +21,7 @@ import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provision.ZoneEndpoint;
import com.yahoo.config.provision.ZoneEndpoint.AccessType;
@@ -161,8 +162,8 @@ public class MockNodeRepository extends NodeRepository {
// Emulate host in tenant account
nodes.add(Node.create("dockerhost2", ipConfig(101, 1, 3), "dockerhost2.yahoo.com",
flavors.getFlavorOrThrow("large"), NodeType.host)
- .wireguardPubKey(WireguardKey.from("000011112222333344445555666677778888999900c="))
- .wireguardKeyTimestamp(Instant.ofEpochMilli(123L))
+ .wireguardKey(new WireguardKeyWithTimestamp(WireguardKey.from("000011112222333344445555666677778888999900c="),
+ Instant.ofEpochMilli(123L)))
.cloudAccount(tenantAccount).build());
nodes.add(Node.create("dockerhost3", ipConfig(102, 1, 3), "dockerhost3.yahoo.com",
flavors.getFlavorOrThrow("large"), NodeType.host).cloudAccount(defaultCloudAccount).build());
@@ -176,8 +177,8 @@ public class MockNodeRepository extends NodeRepository {
// Config servers
nodes.add(Node.create("cfg1", ipConfig(201), "cfg1.yahoo.com", flavors.getFlavorOrThrow("default"), NodeType.config)
.cloudAccount(defaultCloudAccount)
- .wireguardPubKey(WireguardKey.from("lololololololololololololololololololololoo="))
- .wireguardKeyTimestamp(Instant.ofEpochMilli(456L))
+ .wireguardKey(new WireguardKeyWithTimestamp(WireguardKey.from("lololololololololololololololololololololoo="),
+ Instant.ofEpochMilli(456L)))
.build());
nodes.add(Node.create("cfg2", ipConfig(202), "cfg2.yahoo.com", flavors.getFlavorOrThrow("default"), NodeType.config)
.cloudAccount(defaultCloudAccount)
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java
index f64e50310bb..3f66b2c94d8 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationTransaction;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.Cloud;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -25,6 +26,7 @@ import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.config.ConfigPayload;
import com.yahoo.vespa.hosted.provision.maintenance.SwitchRebalancer;
import com.yahoo.vespa.hosted.provision.node.Agent;
+import com.yahoo.vespa.hosted.provision.persistence.ApplicationSerializer;
import com.yahoo.vespa.hosted.provision.persistence.DnsNameResolver;
import com.yahoo.vespa.hosted.provision.persistence.NameResolver;
import com.yahoo.vespa.hosted.provision.persistence.NodeSerializer;
@@ -45,9 +47,10 @@ import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
+import java.util.Map;
+import java.util.function.BiConsumer;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
@@ -75,8 +78,8 @@ public class RealDataScenarioTest {
@Ignore
@Test
- public void test() {
- ProvisioningTester tester = tester(SystemName.Public, CloudName.AWS, Environment.prod, parseFlavors(Path.of("/tmp/node-flavors.xml")));
+ public void test() throws Exception {
+ ProvisioningTester tester = tester(SystemName.Public, CloudName.AWS, Environment.prod, CloudAccount.empty, parseFlavors(Path.of("/tmp/node-flavors.xml")));
initFromZk(tester.nodeRepository(), Path.of("/tmp/snapshot"));
ApplicationId app = ApplicationId.from("tenant", "app", "default");
@@ -132,41 +135,34 @@ public class RealDataScenarioTest {
}
}
- private static void initFromZk(NodeRepository nodeRepository, Path pathToZkSnapshot) {
+ private static void initFromZk(NodeRepository nodeRepository, Path pathToZkSnapshot) throws Exception {
NodeSerializer nodeSerializer = new NodeSerializer(nodeRepository.flavors());
- AtomicBoolean nodeNext = new AtomicBoolean(false);
- Pattern zkNodePathPattern = Pattern.compile(".?/provision/v1/nodes/[a-z0-9.-]+\\.(com|cloud).?");
- Consumer<String> consumer = input -> {
- if (nodeNext.get()) {
- String json = input.substring(input.indexOf("{\""), input.lastIndexOf('}') + 1);
- Node node = nodeSerializer.fromJson(json.getBytes(UTF_8));
- nodeRepository.database().addNodesInState(new LockedNodeList(List.of(node), () -> { }), node.state(), Agent.system);
- nodeNext.set(false);
- } else {
- if (!zkNodePathPattern.matcher(input).matches()) return;
- if (nodeNext.getAndSet(true))
- throw new IllegalStateException("Expected to find node JSON, but found another node path: " + input);
+ Map<Pattern, BiConsumer<byte[], NestedTransaction>> jsonConsumerByPathPattern = Map.of(
+ Pattern.compile(".?/provision/v1/nodes/[a-z0-9.-]+\\.(com|cloud).?"), (json, transaction) -> {
+ Node node = nodeSerializer.fromJson(json);
+ nodeRepository.database().addNodesInState(new LockedNodeList(List.of(node), () -> { }), node.state(), Agent.system, transaction);
+ },
+ Pattern.compile(".?/provision/v1/applications/[a-z0-9:-]+.?"), (json, transaction) ->
+ nodeRepository.database().writeApplication(ApplicationSerializer.fromJson(json), transaction));
+
+ try (StringsIterator iterator = new StringsIterator(pathToZkSnapshot); NestedTransaction transaction = new NestedTransaction()) {
+ while (iterator.hasNext()) {
+ String s1 = iterator.next();
+ if (!iterator.hasNext()) break;
+ for (var entry : jsonConsumerByPathPattern.entrySet()) {
+ if (!entry.getKey().matcher(s1).matches()) continue;
+ String s2 = iterator.next();
+ byte[] json = s2.substring(s2.indexOf("{\""), s2.lastIndexOf('}') + 1).getBytes(UTF_8);
+ entry.getValue().accept(json, transaction);
+ break;
+ }
}
- };
-
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(Files.newInputStream(pathToZkSnapshot), UTF_8))) {
- StringBuilder sb = new StringBuilder(1000);
- for (int r; (r = reader.read()) != -1; ) {
- if (r < 0x20 || r >= 0x7F) {
- if (sb.length() > 0) {
- consumer.accept(sb.toString());
- sb.setLength(0);
- }
- } else sb.append((char) r);
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
+ transaction.commit();
}
}
- private static ProvisioningTester tester(SystemName systemName, CloudName cloudName, Environment environment, List<Flavor> flavors) {
- Cloud cloud = Cloud.builder().name(cloudName).dynamicProvisioning(cloudName != CloudName.YAHOO).build();
+ private static ProvisioningTester tester(SystemName systemName, CloudName cloudName, Environment environment, CloudAccount cloudAccount, List<Flavor> flavors) {
+ Cloud cloud = Cloud.builder().name(cloudName).dynamicProvisioning(cloudName != CloudName.YAHOO).account(cloudAccount).build();
NameResolver nameResolver = cloudName == CloudName.YAHOO ? new DnsNameResolver() : new MockNameResolver().mockAnyLookup();
ProvisioningTester.Builder builder = new ProvisioningTester.Builder()
.zone(new Zone(cloud, systemName, environment, RegionName.defaultName()))
@@ -179,4 +175,42 @@ public class RealDataScenarioTest {
return builder.build();
}
+ /** Extracts sequences longer than 5 printable characters from a binary file, similar to `strings` command */
+ private static class StringsIterator implements Iterator<String>, AutoCloseable {
+ private final BufferedReader reader;
+ private final StringBuilder sb = new StringBuilder(1000);
+ private StringsIterator(Path path) throws IOException {
+ this.reader = new BufferedReader(new InputStreamReader(Files.newInputStream(path), UTF_8));
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!sb.isEmpty()) return true;
+ try {
+ for (int r; (r = reader.read()) != -1; ) {
+ if (r < 0x20 || r >= 0x7F) {
+ if (sb.isEmpty()) continue; // Still haven't encountered any real data
+ if (sb.length() > 5) break; // We (probably) found some real data
+ sb.setLength(0); // Probably some random binary data that happened to be a printable character, reset
+ } else sb.append((char) r);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return !sb.isEmpty();
+ }
+
+ @Override
+ public String next() {
+ String next = sb.toString();
+ sb.setLength(0);
+ return next;
+ }
+
+ @Override
+ public void close() throws Exception {
+ reader.close();
+ }
+ }
+
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
index 52d4c85bcaf..aa8ff1245d7 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
@@ -32,6 +32,25 @@ import static org.junit.Assert.assertTrue;
public class AutoscalingTest {
@Test
+ public void test_autoscaling_with_gpu() {
+ var resources = new NodeResources(8, 32, 225, 0.1, fast, StorageType.local, NodeResources.Architecture.x86_64, new NodeResources.GpuResources(1, 16));
+ var min = new ClusterResources( 8, 1, resources);
+ var now = new ClusterResources(12, 1, resources);
+ var max = new ClusterResources(12, 1, resources);
+ var fixture = DynamicProvisioningTester.fixture()
+ .awsProdSetup(true)
+ .clusterType(ClusterSpec.Type.container)
+ .initialResources(Optional.of(now))
+ .capacity(Capacity.from(min, max))
+ .build();
+ fixture.tester.clock().advance(Duration.ofDays(2));
+ fixture.loader().applyLoad(new Load(0.8f, 0.17, 0.12), 1, true, true, 100);
+ var result = fixture.autoscale();
+ assertTrue(result.resources().isEmpty());
+ assertEquals(Autoscaling.Status.insufficient, result.status());
+ }
+
+ @Test
public void test_autoscaling_nodes_only() {
var resources = new NodeResources(16, 32, 200, 0.1);
var min = new ClusterResources( 8, 1, resources);
@@ -112,7 +131,7 @@ public class AutoscalingTest {
fixture.loader().applyLoad(new Load(0.1, 0.1, 0.1), 3);
fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 1);
fixture.tester().assertResources("Scaling up since resource usage is too high",
- 8, 1, 5.3, 17.5, 75.4,
+ 8, 1, 5.3, 17.0, 75.1,
fixture.autoscale());
}
@@ -173,7 +192,7 @@ public class AutoscalingTest {
fixture.setScalingDuration(Duration.ofHours(12)); // Fixture sets last completion to be 1 day into the past
fixture.loader().applyLoad(new Load(1.0, 0.1, 1.0), 10);
fixture.tester().assertResources("Scaling up (only) since resource usage is too high",
- 5, 1, 11.7, 15.4, 132.0,
+ 5, 1, 11.7, 14.9, 131.5,
fixture.autoscale());
}
@@ -185,7 +204,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(1.0, 0.1, 1.0), 10);
fixture.tester().assertResources("Scaling cpu and disk up and memory down",
- 5, 1, 11.7, 4.0, 132.0,
+ 5, 1, 11.7, 4.0, 131.5,
fixture.autoscale());
}
@@ -208,7 +227,7 @@ public class AutoscalingTest {
fixture.loader().applyCpuLoad(0.70, 1);
fixture.loader().applyCpuLoad(0.01, 100);
fixture.tester().assertResources("Scaling up since peak resource usage is too high",
- 5, 1, 7.1, 12.3, 50.7,
+ 5, 1, 7.1, 11.9, 50.5,
fixture.autoscale());
}
@@ -355,7 +374,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(0.05f, 0.05f, 0.05f), 120);
fixture.tester().assertResources("Scaling down to limit since resource usage is low",
- 4, 1, 1.8, 7.4, 23.5,
+ 4, 1, 1.8, 7.4, 23.4,
fixture.autoscale());
}
@@ -459,7 +478,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyCpuLoad(1.0, 120);
fixture.tester().assertResources("Suggesting above capacity limit",
- 5, 1, 10.2, 12.3, 50.7,
+ 5, 1, 10.2, 11.9, 50.5,
fixture.tester().suggest(fixture.applicationId, fixture.clusterSpec.id(), min, min));
}
@@ -663,7 +682,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofHours(12 * 3 + 1));
fixture.loader().applyCpuLoad(0.02, 5);
fixture.tester().assertResources("Scaling down since enough time has passed",
- 5, 1, 1.0, 12.3, 50.7,
+ 5, 1, 1.0, 11.9, 50.5,
fixture.autoscale());
}
@@ -707,7 +726,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.25, 200);
fixture.tester().assertResources("Scale up since we assume we need 2x cpu for growth when no scaling time data",
- 5, 1, 2.6, 12.3, 50.7,
+ 5, 1, 2.6, 11.9, 50.5,
fixture.autoscale());
fixture.setScalingDuration(Duration.ofHours(8));
@@ -716,7 +735,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.20, 200);
fixture.tester().assertResources("Scale down since observed growth is slower than scaling time",
- 5, 1, 1.6, 12.3, 50.7,
+ 5, 1, 1.6, 11.9, 50.5,
fixture.autoscale());
fixture.setScalingDuration(Duration.ofHours(8));
@@ -727,7 +746,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.25, 200);
fixture.tester().assertResources("Scale up since observed growth is faster than scaling time",
- 5, 1, 2.4, 12.3, 50.7,
+ 5, 1, 2.4, 11.9, 50.5,
fixture.autoscale());
}
@@ -744,7 +763,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.7, 200);
fixture.tester().assertResources("Scale up slightly since observed growth is faster than scaling time, but we are not confident",
- 5, 1, 2.2, 12.3, 50.7,
+ 5, 1, 2.2, 11.9, 50.5,
fixture.autoscale());
}
@@ -763,7 +782,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.4, 200);
fixture.tester.assertResources("Query and write load is equal -> scale up somewhat",
- 5, 1, 2.9, 12.3, 50.7,
+ 5, 1, 2.9, 11.9, 50.5,
fixture.autoscale());
fixture.tester().clock().advance(Duration.ofDays(2));
@@ -772,7 +791,7 @@ public class AutoscalingTest {
fixture.loader().addCpuMeasurements(0.4, 200);
// TODO: Ackhually, we scale up less here - why?
fixture.tester().assertResources("Query load is 4x write load -> scale up more",
- 5, 1, 2.2, 12.3, 50.7,
+ 5, 1, 2.2, 11.9, 50.5,
fixture.autoscale());
fixture.tester().clock().advance(Duration.ofDays(2));
@@ -780,7 +799,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.4, 200);
fixture.tester().assertResources("Write load is 10x query load -> scale down",
- 5, 1, 1.3, 12.3, 50.7,
+ 5, 1, 1.3, 11.9, 50.5,
fixture.autoscale());
fixture.tester().clock().advance(Duration.ofDays(2));
@@ -788,7 +807,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.4, 200);
fixture.tester().assertResources("Query only -> larger",
- 5, 1, 3.5, 12.3, 50.7,
+ 5, 1, 3.5, 11.9, 50.5,
fixture.autoscale());
fixture.tester().clock().advance(Duration.ofDays(2));
@@ -796,7 +815,7 @@ public class AutoscalingTest {
fixture.tester.clock().advance(timeAdded.negated());
fixture.loader().addCpuMeasurements(0.4, 200);
fixture.tester().assertResources("Write only -> smallest possible",
- 5, 1, 1.0, 12.3, 50.7,
+ 5, 1, 1.0, 11.9, 50.5,
fixture.autoscale());
}
@@ -825,7 +844,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 200);
fixture.tester().assertResources("Scale only to a single node and group since this is dev",
- 1, 1, 0.1, 23.6, 105.6,
+ 1, 1, 0.1, 22.9, 105.2,
fixture.autoscale());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java
index be7bc3c44a8..f2b80adc513 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java
@@ -32,7 +32,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.1, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 3.4, 7.4, 29.0,
+ 8, 1, 3.4, 7.2, 28.8,
fixture.autoscale());
// Higher query rate
@@ -40,7 +40,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(200, 1.1, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 6.8, 7.4, 29.0,
+ 8, 1, 6.8, 7.2, 28.8,
fixture.autoscale());
// Higher headroom
@@ -48,7 +48,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.3, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 4.0, 7.4, 29.0,
+ 8, 1, 4.0, 7.2, 28.8,
fixture.autoscale());
// Higher per query cost
@@ -56,7 +56,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.1, 0.45));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 5.1, 7.4, 29.0,
+ 8, 1, 5.1, 7.2, 28.8,
fixture.autoscale());
// Bcp elsewhere is 0 - use local only
@@ -64,7 +64,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(0, 1.1, 0.45));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling using local info",
- 8, 1, 1, 7.4, 29.0,
+ 8, 1, 1, 7.2, 28.8,
fixture.autoscale());
}
@@ -85,7 +85,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.1, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 3, 3, 11.7, 43.2, 190.0,
+ 3, 3, 11.7, 41.8, 189.3,
fixture.autoscale());
// Higher query rate
@@ -93,7 +93,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(200, 1.1, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 3, 3, 23.1, 43.2, 190.0,
+ 3, 3, 23.1, 41.8, 189.3,
fixture.autoscale());
// Higher headroom
@@ -101,7 +101,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.3, 0.3));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 3, 3, 13.8, 43.2, 190.0,
+ 3, 3, 13.8, 41.8, 189.3,
fixture.autoscale());
// Higher per query cost
@@ -109,7 +109,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(100, 1.1, 0.45));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 3, 3, 17.4, 43.2, 190.0,
+ 3, 3, 17.4, 41.8, 189.3,
fixture.autoscale());
}
@@ -186,7 +186,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.store(new BcpGroupInfo(200, 1.3, 0.45));
fixture.loader().addCpuMeasurements(0.7f, 10);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 11.9, 7.4, 29.0,
+ 8, 1, 11.9, 7.2, 28.8,
fixture.autoscale());
// Some local traffic
@@ -196,7 +196,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration1.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 10.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 6.8, 7.4, 29.0,
+ 8, 1, 6.8, 7.2, 28.8,
fixture.autoscale());
// Enough local traffic to get half the votes
@@ -206,7 +206,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration2.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 50.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 3.0, 7.4, 29.0,
+ 8, 1, 3.0, 7.2, 28.8,
fixture.autoscale());
// Mostly local
@@ -216,7 +216,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration3.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 90.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 2.2, 7.4, 29.0,
+ 8, 1, 2.2, 7.2, 28.8,
fixture.autoscale());
// Local only
@@ -226,7 +226,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration4.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 100.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 2.1, 7.4, 29.0,
+ 8, 1, 2.1, 7.2, 28.8,
fixture.autoscale());
// No group info, should be the same as the above
@@ -236,7 +236,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration5.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 100.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 2.1, 7.4, 29.0,
+ 8, 1, 2.1, 7.2, 28.8,
fixture.autoscale());
// 40 query rate, no group info (for reference to the below)
@@ -246,7 +246,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration6.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 40.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 1.5, 7.4, 29.0,
+ 8, 1, 1.5, 7.2, 28.8,
fixture.autoscale());
// Local query rate is too low but global is even lower so disregard it, giving the same as above
@@ -256,7 +256,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration7.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 40.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 1.5, 7.4, 29.0,
+ 8, 1, 1.5, 7.2, 28.8,
fixture.autoscale());
// Local query rate is too low to be fully confident, and so is global but as it is slightly larger, incorporate it slightly
@@ -266,7 +266,7 @@ public class AutoscalingUsingBcpGroupInfoTest {
fixture.tester().clock().advance(duration8.negated());
fixture.loader().addQueryRateMeasurements(10, __ -> 40.0);
fixture.tester().assertResources("Scaling up cpu using bcp group cpu info",
- 8, 1, 1.8, 7.4, 29.0,
+ 8, 1, 1.8, 7.2, 28.8,
fixture.autoscale());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java
index df7c468f035..6225982af42 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java
@@ -11,11 +11,12 @@ import java.util.Optional;
import static com.yahoo.config.provision.NodeResources.Architecture.arm64;
import static com.yahoo.config.provision.NodeResources.Architecture.x86_64;
import static com.yahoo.config.provision.NodeResources.DiskSpeed.fast;
+import static com.yahoo.config.provision.NodeResources.GpuResources;
import static com.yahoo.config.provision.NodeResources.StorageType.local;
import static com.yahoo.config.provision.NodeResources.StorageType.remote;
/**
- * Returns the information about all AWS node types supported on Vespa Cloud as of 2022-10-31.
+ * Returns the information about all AWS node types supported on Vespa Cloud as of 2023-09-28.
*
* @author bratseth
*/
@@ -45,13 +46,34 @@ public class AwsNodeTypes {
new VespaFlavor("m5d_16xlarge", 64.0, 64.0, 256.0, 246.0, 2400.0, 10.0, fast, local, x86_64),
new VespaFlavor("m5_24xlarge", 96.0, 96.0, 384.0, 370.0, 16384.0, 10.0, fast, remote, x86_64),
new VespaFlavor("m5d_24xlarge", 96.0, 96.0, 384.0, 370.0, 3600.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_large", 2.0, 2.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_large", 2.0, 2.0, 8.0, 7.3, 118.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_xlarge", 4.0, 4.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_xlarge", 4.0, 4.0, 16.0, 15.1, 237.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_2xlarge", 8.0, 8.0, 32.0, 30.5, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_2xlarge", 8.0, 8.0, 32.0, 30.5, 474.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_4xlarge", 16.0, 16.0, 64.0, 61.3, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_4xlarge", 16.0, 16.0, 64.0, 61.3, 950.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_8xlarge", 32.0, 32.0, 128.0, 123.0, 1900.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_12xlarge", 48.0, 48.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_12xlarge", 48.0, 48.0, 192.0, 185.0, 2850.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m6i_16xlarge", 64.0, 64.0, 256.0, 246.0, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("m6id_16xlarge", 64.0, 64.0, 256.0, 246.0, 3800.0, 10.0, fast, local, x86_64),
new VespaFlavor("m6g_large", 2.0, 2.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_large", 2.0, 2.0, 8.0, 7.3, 118.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_xlarge", 4.0, 4.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_xlarge", 4.0, 4.0, 16.0, 15.1, 237.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_2xlarge", 8.0, 8.0, 32.0, 30.5, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_2xlarge", 8.0, 8.0, 32.0, 30.5, 474.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_4xlarge", 16.0, 16.0, 64.0, 61.3, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_4xlarge", 16.0, 16.0, 64.0, 61.3, 950.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_8xlarge", 32.0, 32.0, 128.0, 123.0, 1900.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_12xlarge", 48.0, 48.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_12xlarge", 48.0, 48.0, 192.0, 185.0, 2850.0, 10.0, fast, local, arm64),
new VespaFlavor("m6g_16xlarge", 64.0, 64.0, 256.0, 246.0, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("m6gd_16xlarge", 64.0, 64.0, 256.0, 246.0, 3800.0, 10.0, fast, local, arm64),
new VespaFlavor("c5_large", 2.0, 2.0, 4.0, 3.5, 16384.0, 10.0, fast, remote, x86_64),
new VespaFlavor("c5d_large", 2.0, 2.0, 4.0, 3.5, 50.0, 10.0, fast, local, x86_64),
new VespaFlavor("c5_xlarge", 4.0, 4.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, x86_64),
@@ -68,10 +90,44 @@ public class AwsNodeTypes {
new VespaFlavor("c5d_18xlarge", 72.0, 72.0, 144.0, 137.0, 1800.0, 10.0, fast, local, x86_64),
new VespaFlavor("c5_24xlarge", 96.0, 96.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, x86_64),
new VespaFlavor("c5d_24xlarge", 96.0, 96.0, 192.0, 185.0, 3600.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_large", 2.0, 2.0, 4.0, 3.5, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_large", 2.0, 2.0, 4.0, 3.5, 118.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_xlarge", 4.0, 4.0, 8.0, 7.4, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_xlarge", 4.0, 4.0, 8.0, 7.4, 237.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_2xlarge", 8.0, 8.0, 16.0, 15.1, 474.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_4xlarge", 16.0, 16.0, 32.0, 30.6, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_4xlarge", 16.0, 16.0, 32.0, 30.6, 950.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_8xlarge", 32.0, 32.0, 64.0, 61.7, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_8xlarge", 32.0, 32.0, 64.0, 61.7, 1900.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_12xlarge", 48.0, 48.0, 96.0, 92.6, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_12xlarge", 48.0, 48.0, 96.0, 92.6, 2850.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_16xlarge", 64.0, 64.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_16xlarge", 64.0, 64.0, 128.0, 123.0, 3800.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6i_24xlarge", 96.0, 96.0, 192.0, 185.6, 16384.0, 10.0, fast, remote, x86_64),
+ new VespaFlavor("c6id_24xlarge", 96.0, 96.0, 192.0, 185.6, 5700.0, 10.0, fast, local, x86_64),
new VespaFlavor("c5ad_8xlarge", 32.0, 32.0, 64.0, 62.0, 1200.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("c6g_large", 2.0, 2.0, 4.0, 3.35, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c6gd_large", 2.0, 2.0, 4.0, 3.35, 118.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_xlarge", 4.0, 4.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c6gd_xlarge", 4.0, 4.0, 8.0, 7.3, 237.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c6gd_2xlarge", 8.0, 8.0, 16.0, 15.1, 475.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_4xlarge", 16.0, 16.0, 32.0, 30.8, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c6gd_4xlarge", 16.0, 16.0, 32.0, 30.8, 950.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_8xlarge", 32.0, 32.0, 64.0, 62.3, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c6gd_8xlarge", 32.0, 32.0, 64.0, 62.3, 1900.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_12xlarge", 48.0, 48.0, 96.0, 93.0, 16384.0, 10.0, fast, remote, arm64),
new VespaFlavor("c6gd_12xlarge", 48.0, 48.0, 96.0, 93.0, 2850.0, 10.0, fast, local, arm64),
+ new VespaFlavor("c6g_16xlarge", 64.0, 64.0, 128.0, 125.0, 16384.0, 10.0, fast, remote, arm64),
new VespaFlavor("c6gd_16xlarge", 64.0, 64.0, 128.0, 125.0, 3800.0, 10.0, fast, local, arm64),
- new VespaFlavor("c6id_16xlarge", 64.0, 64.0, 128.0, 123.0, 3800.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("m7g_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_large", 2.0, 2.0, 4.0, 3.35, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_xlarge", 4.0, 4.0, 8.0, 7.2, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_4xlarge", 16.0, 16.0, 32.0, 30.8, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_8xlarge", 32.0, 32.0, 64.0, 62.3, 16384.0, 10.0, fast, remote, arm64),
+ new VespaFlavor("c7g_12xlarge", 48.0, 48.0, 96.0, 92.6, 16384.0, 10.0, fast, remote, arm64),
new VespaFlavor("c7g_16xlarge", 64.0, 64.0, 128.0, 125.0, 16384.0, 10.0, fast, remote, arm64),
new VespaFlavor("r5_large", 2.0, 2.0, 16.0, 15.2, 16384.0, 10.0, fast, remote, x86_64),
new VespaFlavor("r5d_large", 2.0, 2.0, 16.0, 15.2, 75.0, 10.0, fast, local, x86_64),
@@ -89,14 +145,7 @@ public class AwsNodeTypes {
new VespaFlavor("r5d_16xlarge", 64.0, 64.0, 512.0, 498.0, 2400.0, 10.0, fast, local, x86_64),
new VespaFlavor("r5_24xlarge", 96.0, 96.0, 768.0, 748.0, 16384.0, 10.0, fast, remote, x86_64),
new VespaFlavor("r5d_24xlarge", 96.0, 96.0, 768.0, 748.0, 3600.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1_16xlarge", 64.0, 64.0, 976.0, 946.0, 1920.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1_32xlarge", 128.0, 128.0, 1952.0, 1893.0, 3840.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_xlarge", 4.0, 4.0, 122.0, 118.0, 120.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_2xlarge", 8.0, 8.0, 244.0, 237.0, 240.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_4xlarge", 16.0, 16.0, 488.0, 474.0, 480.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_8xlarge", 32.0, 32.0, 976.0, 946.0, 960.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_16xlarge", 64.0, 64.0, 1952.0, 1893.0, 1920.0, 10.0, fast, local, x86_64),
- new VespaFlavor("x1e_32xlarge", 128.0, 128.0, 3904.0, 3786.0, 3840.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("r6id_xlarge", 4.0, 4.0, 32.0, 30.8, 237.0, 10.0, fast, local, x86_64),
new VespaFlavor("z1d_6xlarge", 24.0, 24.0, 192.0, 185.0, 900.0, 10.0, fast, local, x86_64),
new VespaFlavor("i4i_large", 2.0, 2.0, 16.0, 15.1, 468.0, 10.0, fast, local, x86_64),
new VespaFlavor("i4i_xlarge", 4.0, 4.0, 32.0, 30.5, 937.0, 10.0, fast, local, x86_64),
@@ -104,7 +153,9 @@ public class AwsNodeTypes {
new VespaFlavor("i4i_4xlarge", 16.0, 16.0, 128.0, 123.0, 3750.0, 10.0, fast, local, x86_64),
new VespaFlavor("i4i_8xlarge", 32.0, 32.0, 256.0, 246.0, 7500.0, 10.0, fast, local, x86_64),
new VespaFlavor("i4i_16xlarge", 64.0, 64.0, 512.0, 498.0, 15000.0, 10.0, fast, local, x86_64),
- new VespaFlavor("i4i_32xlarge", 128.0, 128.0, 1024.0, 996.0, 30000.0, 10.0, fast, local, x86_64));
+ new VespaFlavor("i4i_32xlarge", 128.0, 128.0, 1024.0, 996.0, 30000.0, 10.0, fast, local, x86_64),
+ new VespaFlavor("g4dn_xlarge", 4.0, 4.0, 16.0, 15.1, 125.0, 10.0, fast, local, x86_64, new GpuResources(1, 16.0)),
+ new VespaFlavor("g4dn_2xlarge", 8.0, 8.0, 32.0, 30.5, 225.0, 10.0, fast, local, x86_64, new GpuResources(1, 16.0)));
public static List<VespaFlavor> asVespaFlavors() { return sorted(hostFlavors); }
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java
index c42b61988e9..2e0a92a38af 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java
@@ -23,9 +23,23 @@ public class VespaFlavor {
NodeResources.DiskSpeed diskSpeed,
NodeResources.StorageType storageType,
NodeResources.Architecture architecture) {
+ this(name, advertisedVcpu, realVcpu, advertisedMemoryGb, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, NodeResources.GpuResources.zero());
+ }
+
+ public VespaFlavor(String name,
+ double advertisedVcpu,
+ double realVcpu,
+ double advertisedMemoryGb,
+ double realMemoryGb,
+ double diskGb,
+ double bandwidthGbps,
+ NodeResources.DiskSpeed diskSpeed,
+ NodeResources.StorageType storageType,
+ NodeResources.Architecture architecture,
+ NodeResources.GpuResources gpuResources) {
this.name = name;
- this.realResources = new NodeResources(realVcpu, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture);
- this.advertisedResources = new NodeResources(advertisedVcpu, advertisedMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture);
+ this.realResources = new NodeResources(realVcpu, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, gpuResources);
+ this.advertisedResources = new NodeResources(advertisedVcpu, advertisedMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, gpuResources);
}
public String name() { return name; }
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
index 6dc681ae5c8..b5257e23d9e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
@@ -40,6 +40,7 @@ public class LoadBalancerSerializerTest {
Optional.of(new LoadBalancerInstance(
Optional.of(DomainName.of("lb-host")),
Optional.empty(),
+ Optional.empty(),
Optional.of(new DnsZone("zone-id-1")),
Set.of(4080, 4443),
Set.of("10.2.3.4/24"),
@@ -73,6 +74,7 @@ public class LoadBalancerSerializerTest {
Optional.of(new LoadBalancerInstance(
Optional.empty(),
Optional.of("1.2.3.4"),
+ Optional.of("fd00::1"),
Optional.of(new DnsZone("zone-id-1")),
Set.of(4443),
Set.of("10.2.3.4/24", "12.3.2.1/30"),
@@ -86,6 +88,8 @@ public class LoadBalancerSerializerTest {
var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer));
assertEquals(loadBalancer.id(), serialized.id());
assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname());
+ assertEquals(loadBalancer.instance().get().ip4Address(), serialized.instance().get().ip4Address());
+ assertEquals(loadBalancer.instance().get().ip6Address(), serialized.instance().get().ip6Address());
assertEquals(loadBalancer.instance().get().dnsZone(), serialized.instance().get().dnsZone());
assertEquals(loadBalancer.instance().get().ports(), serialized.instance().get().ports());
assertEquals(loadBalancer.instance().get().networks(), serialized.instance().get().networks());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java
index ebac6071a14..1ef89f2d53d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java
@@ -289,6 +289,11 @@ public class DynamicProvisioningTester {
return flavorResources.compatibleWith(resources);
}
+ @Override
+ public boolean satisfies(Flavor flavor, NodeResources resources) {
+ return hostResourcesCalculator.advertisedResourcesOf(flavor).satisfies(resources);
+ }
+
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
index 928e91861a2..54a0e7e9757 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
@@ -119,6 +119,10 @@
],
"additionalIpAddresses": [],
"cloudAccount": "aws:111222333444",
- "wireguardPubkey":"lololololololololololololololololololololoo=",
+ "wireguard": {
+ "key": "lololololololololololololololololololololoo=",
+ "timestamp": 456
+ },
+ "wireguardPubkey": "lololololololololololololololololololololoo=",
"wireguardKeyTimestamp": 456
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
index 72b5483d849..d3f1a8082ae 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
@@ -117,6 +117,10 @@
"ipAddresses": ["127.0.101.1", "::101:1"],
"additionalIpAddresses": ["::101:2", "::101:3", "::101:4"],
"cloudAccount": "aws:777888999000",
+ "wireguard": {
+ "key": "000011112222333344445555666677778888999900c=",
+ "timestamp": 123
+ },
"wireguardPubkey": "000011112222333344445555666677778888999900c=",
"wireguardKeyTimestamp": 123
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
index d0d6df71fc1..404cf9a9a80 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
@@ -118,6 +118,10 @@
"ipAddresses": ["127.0.4.1", "::4:1"],
"additionalIpAddresses": [],
"cloudAccount": "aws:111222333444",
+ "wireguard": {
+ "key": "lololololololololololololololololololololoo=",
+ "timestamp": 123
+ },
"wireguardPubkey": "lololololololololololololololololololololoo=",
"wireguardKeyTimestamp": 123
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json
index 7bee06adc87..8e9af7f680f 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json
@@ -4,7 +4,11 @@
"hostname": "cfg1.yahoo.com",
"wireguardPubkey": "lololololololololololololololololololololoo=",
"wireguardKeyTimestamp":456,
- "ipAddresses": ["::201:1"]
+ "ipAddresses": ["::201:1"],
+ "wireguard": {
+ "key": "lololololololololololololololololololololoo=",
+ "timestamp": 456
+ }
}
]
}
diff --git a/parent/pom.xml b/parent/pom.xml
index 50222402b68..d23d12c72c9 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -317,7 +317,7 @@
-->
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
- <version>5.5.2</version>
+ <version>5.7.1</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.testing.junit5.JUnit5BestPractices</recipe>
diff --git a/searchcore/src/tests/proton/matching/query_test.cpp b/searchcore/src/tests/proton/matching/query_test.cpp
index 575a52d01fb..d098bdde8b6 100644
--- a/searchcore/src/tests/proton/matching/query_test.cpp
+++ b/searchcore/src/tests/proton/matching/query_test.cpp
@@ -711,7 +711,7 @@ void Test::requireThatQueryGluesEverythingTogether() {
EXPECT_EQUAL(1u, md->getNumTermFields());
query.optimize();
- query.fetchPostings();
+ query.fetchPostings(requestContext.getDoom());
SearchIterator::UP search = query.createSearch(*md);
ASSERT_TRUE(search.get());
}
@@ -744,7 +744,7 @@ void checkQueryAddsLocation(const string &loc_in, const string &loc_out) {
MatchData::UP md = mdl.createMatchData();
EXPECT_EQUAL(2u, md->getNumTermFields());
- query.fetchPostings();
+ query.fetchPostings(requestContext.getDoom());
SearchIterator::UP search = query.createSearch(*md);
ASSERT_TRUE(search.get());
if (!EXPECT_NOT_EQUAL(string::npos, search->asString().find(loc_out))) {
@@ -966,7 +966,7 @@ Test::requireThatWhiteListBlueprintCanBeUsed()
MatchData::UP md = mdl.createMatchData();
query.optimize();
- query.fetchPostings();
+ query.fetchPostings(requestContext.getDoom());
SearchIterator::UP search = query.createSearch(*md);
SimpleResult exp = SimpleResult().addHit(1).addHit(5).addHit(7).addHit(11);
SimpleResult act;
diff --git a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
index be1c8941f65..e1820ece0e3 100644
--- a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
+++ b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
@@ -52,22 +52,11 @@ DocsumContext::initState()
_docsumState._args.initFromDocsumRequest(req);
_docsumState._docsumbuf.clear();
_docsumState._docsumbuf.reserve(req.hits.size());
- for (uint32_t i = 0; i < req.hits.size(); i++) {
- _docsumState._docsumbuf.push_back(req.hits[i].docid);
+ for (const auto & hit : req.hits) {
+ _docsumState._docsumbuf.push_back(hit.docid);
}
}
-namespace {
-
-vespalib::Slime::Params
-makeSlimeParams(size_t chunkSize) {
- Slime::Params params;
- params.setChunkSize(chunkSize);
- return params;
-}
-
-}
-
vespalib::Slime::UP
DocsumContext::createSlimeReply()
{
@@ -75,11 +64,11 @@ DocsumContext::createSlimeReply()
_docsumState._args.get_fields());
_docsumWriter.initState(_attrMgr, _docsumState, rci);
const size_t estimatedChunkSize(std::min(0x200000ul, _docsumState._docsumbuf.size()*0x400ul));
- vespalib::Slime::UP response(std::make_unique<vespalib::Slime>(makeSlimeParams(estimatedChunkSize)));
+ auto response = std::make_unique<vespalib::Slime>(Slime::Params(estimatedChunkSize));
Cursor & root = response->setObject();
Cursor & array = root.setArray(DOCSUMS);
const Symbol docsumSym = response->insert(DOCSUM);
- _docsumState._omit_summary_features = (rci.res_class != nullptr) ? rci.res_class->omit_summary_features() : true;
+ _docsumState._omit_summary_features = (rci.res_class == nullptr) || rci.res_class->omit_summary_features();
uint32_t num_ok(0);
for (uint32_t docId : _docsumState._docsumbuf) {
if (_request.expired() ) { break; }
diff --git a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
index 1528b327747..b8027bff04a 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
@@ -6,6 +6,7 @@
#include <vespa/searchlib/fef/matchdatalayout.h>
#include <vespa/searchlib/queryeval/searchable.h>
#include <vespa/searchlib/queryeval/blueprint.h>
+#include <vespa/searchlib/queryeval/irequestcontext.h>
#include <vespa/searchlib/query/tree/range.h>
#include <vespa/searchlib/query/tree/simplequery.h>
@@ -98,7 +99,7 @@ AttributeLimiter::create_search(size_t want_hits, size_t max_group_size, bool st
FieldSpecList field; // single field API is protected
field.add(FieldSpec(_attribute_name, my_field_id, my_handle));
_blueprint = _searchable_attributes.createBlueprint(_requestContext, field, node);
- _blueprint->fetchPostings(ExecuteInfo::create(strictSearch));
+ _blueprint->fetchPostings(ExecuteInfo::create(strictSearch, &_requestContext.getDoom()));
_estimatedHits.store(_blueprint->getState().estimate().estHits, std::memory_order_relaxed);
_blueprint->freeze();
}
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_thread.cpp b/searchcore/src/vespa/searchcore/proton/matching/match_thread.cpp
index b57346611f1..14a238330ba 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_thread.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_thread.cpp
@@ -222,6 +222,8 @@ MatchThread::match_loop(MatchTools &tools, HitCollector &hits)
!docid_range.empty();
docid_range = scheduler.next_range(thread_id))
{
+ // Due to some schedulers communicating across threads, it is vital that all complete this
+ // loop. Do not break out.
if (!softDoomed) {
uint32_t lastCovered = inner_match_loop<Strategy, do_rank, do_limit, do_share_work, use_rank_drop_limit>(context, tools, docid_range);
softDoomed = (lastCovered < docid_range.end);
@@ -311,6 +313,41 @@ MatchThread::match_loop_helper(MatchTools &tools, HitCollector &hits)
}
}
+void
+MatchThread::secondPhase(MatchTools & tools, HitCollector & hits) {
+ trace->addEvent(4, "Start second phase rerank");
+ auto sorted_hit_seq = matchToolsFactory.should_diversify()
+ ? hits.getSortedHitSequence(matchParams.arraySize)
+ : hits.getSortedHitSequence(matchParams.heapSize);
+ trace->addEvent(5, "Synchronize before second phase rerank");
+ WaitTimer get_second_phase_work_timer(wait_time_s);
+ /**
+ * All, or none of the threads in the bundle should call communicator.get_second_phase_work and
+ * communicator.complete_second_phase.
+ * Avoid early return and handle doom with care.
+ */
+ auto my_work = communicator.get_second_phase_work(sorted_hit_seq, thread_id);
+ get_second_phase_work_timer.done();
+ if (tools.getDoom().hard_doom()) {
+ my_work.clear();
+ }
+ if (!my_work.empty()) {
+ tools.setup_second_phase(second_phase_profiler.get());
+ DocumentScorer scorer(tools.rank_program(), tools.search());
+ scorer.score(my_work);
+ }
+ thread_stats.docsReRanked(my_work.size());
+ trace->addEvent(5, "Synchronize before rank scaling");
+ WaitTimer complete_second_phase_timer(wait_time_s);
+ auto [kept_hits, ranges] = communicator.complete_second_phase(my_work, thread_id);
+ complete_second_phase_timer.done();
+ hits.setReRankedHits(std::move(kept_hits));
+ hits.setRanges(ranges);
+ if (auto onReRankTask = matchToolsFactory.createOnSecondPhaseTask()) {
+ onReRankTask->run(hits.getReRankedHits());
+ }
+}
+
search::ResultSet::UP
MatchThread::findMatches(MatchTools &tools)
{
@@ -332,34 +369,15 @@ MatchThread::findMatches(MatchTools &tools)
}
HitCollector hits(matchParams.numDocs, matchParams.arraySize);
trace->addEvent(4, "Start match and first phase rank");
+ /**
+ * All, or none of the threads in the bundle must execute the match loop.
+ * The same goes for secondPhase.
+ * This is due to all the threads in the bundle needs to meet up and exchange information.
+ * If not you will have deadlock.
+ */
match_loop_helper(tools, hits);
if (tools.has_second_phase_rank()) {
- trace->addEvent(4, "Start second phase rerank");
- auto sorted_hit_seq = matchToolsFactory.should_diversify()
- ? hits.getSortedHitSequence(matchParams.arraySize)
- : hits.getSortedHitSequence(matchParams.heapSize);
- trace->addEvent(5, "Synchronize before second phase rerank");
- WaitTimer get_second_phase_work_timer(wait_time_s);
- auto my_work = communicator.get_second_phase_work(sorted_hit_seq, thread_id);
- get_second_phase_work_timer.done();
- if (tools.getDoom().hard_doom()) {
- my_work.clear();
- }
- if (!my_work.empty()) {
- tools.setup_second_phase(second_phase_profiler.get());
- DocumentScorer scorer(tools.rank_program(), tools.search());
- scorer.score(my_work);
- }
- thread_stats.docsReRanked(my_work.size());
- trace->addEvent(5, "Synchronize before rank scaling");
- WaitTimer complete_second_phase_timer(wait_time_s);
- auto [kept_hits, ranges] = communicator.complete_second_phase(my_work, thread_id);
- complete_second_phase_timer.done();
- hits.setReRankedHits(std::move(kept_hits));
- hits.setRanges(ranges);
- if (auto onReRankTask = matchToolsFactory.createOnSecondPhaseTask()) {
- onReRankTask->run(hits.getReRankedHits());
- }
+ secondPhase(tools, hits);
}
trace->addEvent(4, "Create result set");
return hits.getResultSet(fallback_rank_value());
@@ -463,6 +481,11 @@ MatchThread::run()
auto capture_issues = vespalib::Issue::listen(my_issues);
trace->addEvent(4, "Start MatchThread::run");
MatchTools::UP matchTools = matchToolsFactory.createMatchTools();
+ /**
+ * All, or none of the threads in the bundle must call findMatches.
+ * All, or none of the threads in the bundle must call mergeDirector.dualMerge.
+ * Avoid early return and handle doom with care.
+ */
search::ResultSet::UP result = findMatches(*matchTools);
match_time_s = vespalib::to_s(match_time.elapsed());
resultContext = resultProcessor.createThreadContext(matchTools->getDoom(), thread_id, _distributionKey);
@@ -475,6 +498,7 @@ MatchThread::run()
result->getNumHits(),
resultContext->sort->hasSortData(),
bool(resultContext->grouping)));
+ (void) processToken; // Avoid unused warning
get_token_timer.done();
trace->addEvent(5, "Start result processing");
processResult(matchTools->getDoom(), std::move(result), *resultContext);
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_thread.h b/searchcore/src/vespa/searchcore/proton/matching/match_thread.h
index 03ba34eca1f..ad864a98227 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_thread.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_thread.h
@@ -113,6 +113,7 @@ private:
void match_loop_helper(MatchTools &tools, HitCollector &hits);
search::ResultSet::UP findMatches(MatchTools &tools);
+ void secondPhase(MatchTools & tools, HitCollector & hits);
void processResult(const Doom & doom, search::ResultSet::UP result, ResultProcessor::Context &context);
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp b/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp
index 5ae671b88cb..758ef35ebc9 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp
@@ -201,9 +201,9 @@ MatchToolsFactory(QueryLimiter & queryLimiter,
trace.addEvent(5, "Optimize query execution plan");
_query.optimize();
trace.addEvent(4, "Perform dictionary lookups and posting lists initialization");
- _query.fetchPostings();
+ _query.fetchPostings(_requestContext.getDoom());
if (is_search) {
- _query.handle_global_filter(searchContext.getDocIdLimit(),
+ _query.handle_global_filter(_requestContext.getDoom(), searchContext.getDocIdLimit(),
_attribute_blueprint_params.global_filter_lower_limit,
_attribute_blueprint_params.global_filter_upper_limit,
thread_bundle, trace);
diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.cpp b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
index d0738f1857f..22f6ec9cc88 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/query.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
@@ -247,13 +247,14 @@ Query::optimize()
}
void
-Query::fetchPostings()
+Query::fetchPostings(const vespalib::Doom & doom)
{
- _blueprint->fetchPostings(search::queryeval::ExecuteInfo::create(true, 1.0));
+ _blueprint->fetchPostings(search::queryeval::ExecuteInfo::create(true, &doom));
}
void
-Query::handle_global_filter(uint32_t docid_limit, double global_filter_lower_limit, double global_filter_upper_limit,
+Query::handle_global_filter(const vespalib::Doom & doom, uint32_t docid_limit,
+ double global_filter_lower_limit, double global_filter_upper_limit,
vespalib::ThreadBundle &thread_bundle, search::engine::Trace& trace)
{
if (!handle_global_filter(*_blueprint, docid_limit, global_filter_lower_limit, global_filter_upper_limit, thread_bundle, &trace)) {
@@ -264,7 +265,7 @@ Query::handle_global_filter(uint32_t docid_limit, double global_filter_lower_lim
_blueprint = Blueprint::optimize(std::move(_blueprint));
LOG(debug, "blueprint after handle_global_filter:\n%s\n", _blueprint->asString().c_str());
// strictness may change if optimized order changed:
- fetchPostings();
+ fetchPostings(doom);
}
bool
diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.h b/searchcore/src/vespa/searchcore/proton/matching/query.h
index b0299307e92..1a3136042a7 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/query.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/query.h
@@ -98,9 +98,10 @@ public:
* test to verify the original query without optimization.
**/
void optimize();
- void fetchPostings();
+ void fetchPostings(const vespalib::Doom & doom);
- void handle_global_filter(uint32_t docid_limit, double global_filter_lower_limit, double global_filter_upper_limit,
+ void handle_global_filter(const vespalib::Doom & doom, uint32_t docid_limit,
+ double global_filter_lower_limit, double global_filter_upper_limit,
vespalib::ThreadBundle &thread_bundle, search::engine::Trace& trace);
/**
diff --git a/searchlib/src/tests/attribute/dfa_fuzzy_matcher/dfa_fuzzy_matcher_test.cpp b/searchlib/src/tests/attribute/dfa_fuzzy_matcher/dfa_fuzzy_matcher_test.cpp
index c7bd0e917f3..c2a39779061 100644
--- a/searchlib/src/tests/attribute/dfa_fuzzy_matcher/dfa_fuzzy_matcher_test.cpp
+++ b/searchlib/src/tests/attribute/dfa_fuzzy_matcher/dfa_fuzzy_matcher_test.cpp
@@ -8,6 +8,7 @@
#include <vespa/vespalib/fuzzy/levenshtein_dfa.h>
#include <vespa/vespalib/gtest/gtest.h>
#include <vespa/vespalib/util/time.h>
+#include <vespa/vespalib/text/utf8.h>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -26,13 +27,24 @@ using namespace search::attribute;
using namespace search;
using vespalib::FuzzyMatcher;
using vespalib::datastore::AtomicEntryRef;
+using vespalib::datastore::EntryRef;
using vespalib::fuzzy::LevenshteinDfa;
+using vespalib::Utf8Reader;
+using vespalib::Utf8Writer;
using StringEnumStore = EnumStoreT<const char*>;
using DictionaryEntry = std::pair<std::string, size_t>;
using RawDictionary = std::vector<DictionaryEntry>;
using StringVector = std::vector<std::string>;
+namespace {
+
+const char* char_from_u8(const char8_t* p) {
+ return reinterpret_cast<const char*>(p);
+}
+
+}
+
RawDictionary
read_dictionary()
{
@@ -109,11 +121,11 @@ struct MatchStats {
template <bool collect_matches>
void
-brute_force_fuzzy_match_in_dictionary(std::string_view target, const StringEnumStore& store, MatchStats& stats, StringVector& matched_words)
+brute_force_fuzzy_match_in_dictionary(std::string_view target, const StringEnumStore& store, uint32_t prefix_size, bool cased, MatchStats& stats, StringVector& matched_words)
{
auto view = store.get_dictionary().get_posting_dictionary().getFrozenView();
vespalib::Timer timer;
- FuzzyMatcher matcher(target, 2, 0, false);
+ FuzzyMatcher matcher(target, 2, prefix_size, cased);
auto itr = view.begin();
size_t matches = 0;
size_t seeks = 0;
@@ -133,15 +145,33 @@ brute_force_fuzzy_match_in_dictionary(std::string_view target, const StringEnumS
template <bool collect_matches>
void
-dfa_fuzzy_match_in_dictionary(std::string_view target, const StringEnumStore& store, MatchStats& stats, StringVector& matched_words)
+dfa_fuzzy_match_in_dictionary(std::string_view target, const StringEnumStore& store, uint32_t prefix_size, bool cased, MatchStats& stats, StringVector& matched_words)
{
auto view = store.get_dictionary().get_posting_dictionary().getFrozenView();
vespalib::Timer timer;
- DfaFuzzyMatcher matcher(target, 2, false, LevenshteinDfa::DfaType::Explicit);
- auto itr = view.begin();
+ DfaFuzzyMatcher matcher(target, 2, prefix_size, cased, LevenshteinDfa::DfaType::Explicit);
+ Utf8Reader reader(vespalib::stringref(target.data(), target.size()));
+ std::string target_copy;
+ Utf8Writer<std::string> writer(target_copy);
+ for (size_t pos = 0; pos < prefix_size && reader.hasMore(); ++pos) {
+ auto code_point = reader.getChar();
+ writer.putChar(code_point);
+ }
+ auto prefix_cmp = store.make_folded_comparator_prefix(target_copy.c_str());
+ auto itr = prefix_size > 0 ? view.lowerBound(AtomicEntryRef(), prefix_cmp) : view.begin();
+ auto itr_end = itr;
+ if (itr_end.valid()) {
+ if (prefix_size > 0) {
+ if (!prefix_cmp.less(EntryRef(), itr_end.getKey().load_relaxed())) {
+ itr_end.seekPast(AtomicEntryRef(), prefix_cmp);
+ }
+ } else {
+ itr_end.end();
+ }
+ }
size_t matches = 0;
size_t seeks = 0;
- while (itr.valid()) {
+ while (itr != itr_end) {
auto word = store.get_value(itr.getKey().load_relaxed());
if (matcher.is_match(word, itr, store.get_data_store())) {
++itr;
@@ -156,10 +186,58 @@ dfa_fuzzy_match_in_dictionary(std::string_view target, const StringEnumStore& st
stats.add_sample(matches, seeks, timer.elapsed());
}
-struct DfaFuzzyMatcherTest : public ::testing::Test {
+template <bool collect_matches>
+void
+dfa_fuzzy_match_in_dictionary_no_skip(std::string_view target, const StringEnumStore& store, uint32_t prefix_size, bool cased, MatchStats& stats, StringVector& matched_words)
+{
+ auto view = store.get_dictionary().get_posting_dictionary().getFrozenView();
+ vespalib::Timer timer;
+ DfaFuzzyMatcher matcher(target, 2, prefix_size, cased, LevenshteinDfa::DfaType::Explicit);
+ auto itr = view.begin();
+ size_t matches = 0;
+ size_t seeks = 0;
+ for (;itr.valid(); ++itr) {
+ auto word = store.get_value(itr.getKey().load_relaxed());
+ if (matcher.is_match(word)) {
+ ++matches;
+ if (collect_matches) {
+ matched_words.push_back(word);
+ }
+ } else {
+ ++seeks;
+ }
+ }
+ stats.add_sample(matches, seeks, timer.elapsed());
+}
+
+struct TestParam
+{
+ vespalib::string _name;
+ bool _cased;
+
+ TestParam(vespalib::string name, bool cased)
+ : _name(std::move(name)),
+ _cased(cased)
+ {
+ }
+ TestParam(const TestParam&);
+ ~TestParam();
+};
+
+TestParam::TestParam(const TestParam&) = default;
+
+TestParam::~TestParam() = default;
+
+std::ostream& operator<<(std::ostream& os, const TestParam& param)
+{
+ os << param._name;
+ return os;
+}
+
+struct DfaFuzzyMatcherTest : public ::testing::TestWithParam<TestParam> {
StringEnumStore store;
DfaFuzzyMatcherTest()
- : store(true, DictionaryConfig(DictionaryConfig::Type::BTREE, DictionaryConfig::Match::UNCASED))
+ : store(true, DictionaryConfig(DictionaryConfig::Type::BTREE, GetParam()._cased ? DictionaryConfig::Match::CASED : DictionaryConfig::Match::UNCASED))
{}
void populate_dictionary(const StringVector& words) {
auto updater = store.make_batch_updater();
@@ -170,18 +248,31 @@ struct DfaFuzzyMatcherTest : public ::testing::Test {
updater.commit();
store.freeze_dictionary();
}
- void expect_matches(std::string_view target, const StringVector& exp_matches) {
+ void expect_prefix_matches(std::string_view target, uint32_t prefix_size, const StringVector& exp_matches) {
MatchStats stats;
StringVector brute_force_matches;
StringVector dfa_matches;
- brute_force_fuzzy_match_in_dictionary<true>(target, store, stats, brute_force_matches);
- dfa_fuzzy_match_in_dictionary<true>(target, store, stats, dfa_matches);
+ StringVector dfa_no_skip_matches;
+ bool cased = GetParam()._cased;
+ SCOPED_TRACE(target);
+ brute_force_fuzzy_match_in_dictionary<true>(target, store, prefix_size, cased, stats, brute_force_matches);
+ dfa_fuzzy_match_in_dictionary<true>(target, store, prefix_size, cased, stats, dfa_matches);
+ dfa_fuzzy_match_in_dictionary_no_skip<true>(target, store, prefix_size, cased, stats, dfa_no_skip_matches);
EXPECT_EQ(exp_matches, brute_force_matches);
EXPECT_EQ(exp_matches, dfa_matches);
+ EXPECT_EQ(exp_matches, dfa_no_skip_matches);
+ }
+ void expect_matches(std::string_view target, const StringVector& exp_matches) {
+ expect_prefix_matches(target, 0, exp_matches);
}
};
-TEST_F(DfaFuzzyMatcherTest, fuzzy_match_in_dictionary)
+INSTANTIATE_TEST_SUITE_P(DfaFuzzyMatcherMultiTest,
+ DfaFuzzyMatcherTest,
+ testing::Values(TestParam("uncased", false), TestParam("cased", true)),
+ testing::PrintToStringParamName());
+
+TEST_P(DfaFuzzyMatcherTest, fuzzy_match_in_dictionary)
{
StringVector words = { "board", "boat", "bob", "door", "food", "foot", "football", "foothill",
"for", "forbid", "force", "ford", "forearm", "forecast", "forest" };
@@ -194,23 +285,67 @@ TEST_F(DfaFuzzyMatcherTest, fuzzy_match_in_dictionary)
expect_matches("forcecast", {"forecast"});
}
+TEST_P(DfaFuzzyMatcherTest, fuzzy_match_in_dictionary_with_prefix_size)
+{
+ bool cased = GetParam()._cased;
+ StringVector words = { "board", "boat", "bob", "door", "food", "foot", "football", "foothill",
+ "for", "forbid", "force", "ford", "forearm", "forecast", "forest", "H", "HA", "h", "ha", char_from_u8(u8"Ørn"), char_from_u8(u8"øre"), char_from_u8(u8"Ås"), char_from_u8(u8"ås")};
+ populate_dictionary(words);
+ expect_prefix_matches("a", 1, {});
+ expect_prefix_matches("b", 1, {"bob"});
+ expect_prefix_matches("board", 1, {"board", "boat"});
+ expect_prefix_matches("c", 1, {});
+ expect_prefix_matches("food", 1, {"food", "foot", "for", "ford"});
+ expect_prefix_matches("food", 2, {"food", "foot", "for", "ford"});
+ expect_prefix_matches("food", 3, {"food", "foot"});
+ expect_prefix_matches("foothill", 1, {"football", "foothill"});
+ expect_prefix_matches("for", 1, {"food", "foot", "for", "force", "ford"});
+ expect_prefix_matches("for", 2, {"food", "foot", "for", "force", "ford"});
+ expect_prefix_matches("for", 3, {"for", "force", "ford"});
+ expect_prefix_matches("force", 1, {"for", "force", "ford"});
+ expect_prefix_matches("forcecast", 1, {"forecast"});
+ expect_prefix_matches("forcecast", 4, {});
+ expect_prefix_matches("z", 1, {});
+ if (cased) {
+ expect_prefix_matches("h", 1, {"h", "ha"});
+ expect_prefix_matches(char_from_u8(u8"Ø"), 1, {char_from_u8(u8"Ørn")});
+ expect_prefix_matches(char_from_u8(u8"ø"), 1, {char_from_u8(u8"øre")});
+ expect_prefix_matches(char_from_u8(u8"Ã¥"), 1, {char_from_u8(u8"Ã¥s")});
+ /* Corner case: prefix length > target length means exact match */
+ expect_prefix_matches("h", 2, {"h"});
+ } else {
+ expect_prefix_matches("h", 1, {"H", "h", "HA", "ha"});
+ expect_prefix_matches(char_from_u8(u8"ø"), 1, {char_from_u8(u8"øre"), char_from_u8(u8"Ørn")});
+ expect_prefix_matches(char_from_u8(u8"Ã¥"), 1, {char_from_u8(u8"Ã…s"), char_from_u8(u8"Ã¥s")});
+ /* Corner case: prefix length > target length means exact match */
+ expect_prefix_matches("h", 2, {"H", "h"});
+ }
+}
+
void
-benchmark_fuzzy_match_in_dictionary(const StringEnumStore& store, const RawDictionary& dict, size_t words_to_match, bool dfa_algorithm)
+benchmark_fuzzy_match_in_dictionary(const StringEnumStore& store, const RawDictionary& dict, size_t words_to_match, bool cased, bool dfa_algorithm)
{
MatchStats stats;
StringVector dummy;
for (size_t i = 0; i < std::min(words_to_match, dict.size()); ++i) {
const auto& entry = dict[i];
if (dfa_algorithm) {
- dfa_fuzzy_match_in_dictionary<false>(entry.first, store, stats, dummy);
+ dfa_fuzzy_match_in_dictionary<false>(entry.first, store, 0, cased, stats, dummy);
} else {
- brute_force_fuzzy_match_in_dictionary<false>(entry.first, store, stats, dummy);
+ brute_force_fuzzy_match_in_dictionary<false>(entry.first, store, 0, cased, stats, dummy);
}
}
std::cout << (dfa_algorithm ? "DFA:" : "Brute force:") << " samples=" << stats.samples << ", avg_matches=" << stats.avg_matches() << ", avg_seeks=" << stats.avg_seeks() << ", avg_elapsed_ms=" << stats.avg_elapsed_ms() << std::endl;
}
-TEST_F(DfaFuzzyMatcherTest, benchmark_fuzzy_match_in_dictionary)
+using DfaFuzzyMatcherBenchmarkTest = DfaFuzzyMatcherTest;
+
+INSTANTIATE_TEST_SUITE_P(DfaFuzzyMatcherBenchmarkMultiTest,
+ DfaFuzzyMatcherBenchmarkTest,
+ testing::Values(TestParam("uncased", false)),
+ testing::PrintToStringParamName());
+
+TEST_P(DfaFuzzyMatcherBenchmarkTest, benchmark_fuzzy_match_in_dictionary)
{
if (!benchmarking_enabled()) {
GTEST_SKIP() << "benchmarking not enabled";
@@ -219,8 +354,9 @@ TEST_F(DfaFuzzyMatcherTest, benchmark_fuzzy_match_in_dictionary)
populate_dictionary(to_string_vector(dict));
std::cout << "Unique words: " << store.get_num_uniques() << std::endl;
sort_by_freq(dict);
- benchmark_fuzzy_match_in_dictionary(store, dict, dfa_words_to_match, true);
- benchmark_fuzzy_match_in_dictionary(store, dict, brute_force_words_to_match, false);
+ bool cased = GetParam()._cased;
+ benchmark_fuzzy_match_in_dictionary(store, dict, dfa_words_to_match, cased, true);
+ benchmark_fuzzy_match_in_dictionary(store, dict, brute_force_words_to_match, cased, false);
}
int
diff --git a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
index b9c70d76934..1fd9dde09c7 100644
--- a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
+++ b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
@@ -24,14 +24,14 @@ class DocumentWeightOrFilterSearchTest : public ::testing::Test {
uint32_t _range_end;
public:
DocumentWeightOrFilterSearchTest();
- ~DocumentWeightOrFilterSearchTest();
+ ~DocumentWeightOrFilterSearchTest() override;
void inc_generation();
size_t num_trees() const { return _trees.size(); }
Iterator get_tree(size_t idx) const {
if (idx < _trees.size()) {
return _postings.beginFrozen(_trees[idx]);
} else {
- return Iterator();
+ return {};
}
}
void ensure_tree(size_t idx) {
@@ -39,13 +39,13 @@ public:
_trees.resize(idx + 1);
}
}
- void add_tree(size_t idx, std::vector<uint32_t> keys) {
+ void add_tree(size_t idx, const std::vector<uint32_t>& keys) {
ensure_tree(idx);
std::vector<KeyData> adds;
std::vector<uint32_t> removes;
adds.reserve(keys.size());
for (auto& key : keys) {
- adds.emplace_back(KeyData(key, 1));
+ adds.emplace_back(key, 1);
}
_postings.apply(_trees[idx], adds.data(), adds.data() + adds.size(), removes.data(), removes.data() + removes.size());
}
@@ -67,7 +67,7 @@ public:
return result;
};
- std::vector<uint32_t> eval_daat(SearchIterator &iterator) {
+ std::vector<uint32_t> eval_daat(SearchIterator &iterator) const {
std::vector<uint32_t> result;
uint32_t doc_id = _range_start;
while (doc_id < _range_end) {
@@ -81,7 +81,7 @@ public:
return result;
}
- std::vector<uint32_t> frombv(const BitVector &bv) {
+ std::vector<uint32_t> frombv(const BitVector &bv) const {
std::vector<uint32_t> result;
uint32_t doc_id = _range_start;
doc_id = bv.getNextTrueBit(doc_id);
@@ -93,7 +93,7 @@ public:
return result;
}
- std::unique_ptr<BitVector> tobv(std::vector<uint32_t> values) {
+ std::unique_ptr<BitVector> tobv(const std::vector<uint32_t> & values) const {
auto bv = BitVector::create(_range_start, _range_end);
for (auto value : values) {
bv->setBit(value);
@@ -102,7 +102,7 @@ public:
return bv;
}
- void expect_result(std::vector<uint32_t> exp, std::vector<uint32_t> act)
+ static void expect_result(const std::vector<uint32_t> & exp, const std::vector<uint32_t> & act)
{
EXPECT_EQ(exp, act);
}
@@ -227,7 +227,7 @@ public:
}
_test.inc_generation();
}
- ~Verifier() {
+ ~Verifier() override {
for (uint32_t tree_id = 0; tree_id < _test.num_trees(); ++tree_id) {
_test.clear_tree(tree_id);
}
diff --git a/searchlib/src/tests/attribute/searchable/attribute_weighted_set_blueprint_test.cpp b/searchlib/src/tests/attribute/searchable/attribute_weighted_set_blueprint_test.cpp
index d7a854e0afc..6c6f05fd5e2 100644
--- a/searchlib/src/tests/attribute/searchable/attribute_weighted_set_blueprint_test.cpp
+++ b/searchlib/src/tests/attribute/searchable/attribute_weighted_set_blueprint_test.cpp
@@ -29,14 +29,14 @@ using namespace search::attribute::test;
namespace {
void
-setupAttributeManager(MockAttributeManager &manager)
+setupAttributeManager(MockAttributeManager &manager, bool isFilter)
{
AttributeVector::DocId docId;
{
- AttributeVector::SP attr_sp = AttributeFactory::createAttribute("integer", Config(BasicType("int64")));
+ AttributeVector::SP attr_sp = AttributeFactory::createAttribute("integer", Config(BasicType("int64")).setIsFilter(isFilter));
manager.addAttribute(attr_sp);
- IntegerAttribute *attr = (IntegerAttribute*)(attr_sp.get());
+ auto *attr = (IntegerAttribute*)(attr_sp.get());
for (size_t i = 1; i < 10; ++i) {
attr->addDoc(docId);
assert(i == docId);
@@ -45,10 +45,10 @@ setupAttributeManager(MockAttributeManager &manager)
}
}
{
- AttributeVector::SP attr_sp = AttributeFactory::createAttribute("string", Config(BasicType("string")));
+ AttributeVector::SP attr_sp = AttributeFactory::createAttribute("string", Config(BasicType("string")).setIsFilter(isFilter));
manager.addAttribute(attr_sp);
- StringAttribute *attr = (StringAttribute*)(attr_sp.get());
+ auto *attr = (StringAttribute*)(attr_sp.get());
for (size_t i = 1; i < 10; ++i) {
attr->addDoc(docId);
assert(i == docId);
@@ -58,9 +58,9 @@ setupAttributeManager(MockAttributeManager &manager)
}
{
AttributeVector::SP attr_sp = AttributeFactory::createAttribute(
- "multi", Config(BasicType("int64"), search::attribute::CollectionType("array")));
+ "multi", Config(BasicType("int64"), search::attribute::CollectionType("array")).setIsFilter(isFilter));
manager.addAttribute(attr_sp);
- IntegerAttribute *attr = (IntegerAttribute*)(attr_sp.get());
+ auto *attr = (IntegerAttribute*)(attr_sp.get());
for (size_t i = 1; i < 10; ++i) {
attr->addDoc(docId);
assert(i == docId);
@@ -78,35 +78,43 @@ struct WS {
TermFieldHandle handle;
std::vector<std::pair<std::string, uint32_t> > tokens;
- WS(IAttributeManager & manager) : attribute_manager(manager), layout(), handle(layout.allocTermField(fieldId)), tokens() {
+ explicit WS(IAttributeManager & manager)
+ : attribute_manager(manager),
+ layout(), handle(layout.allocTermField(fieldId)),
+ tokens()
+ {
MatchData::UP tmp = layout.createMatchData();
ASSERT_TRUE(tmp->resolveTermField(handle)->getFieldId() == fieldId);
}
WS &add(const std::string &token, uint32_t weight) {
- tokens.push_back(std::make_pair(token, weight));
+ tokens.emplace_back(token, weight);
return *this;
}
Node::UP createNode() const {
- SimpleWeightedSetTerm *node = new SimpleWeightedSetTerm(tokens.size(), "view", 0, Weight(0));
- for (size_t i = 0; i < tokens.size(); ++i) {
- node->addTerm(tokens[i].first, Weight(tokens[i].second));
+ auto *node = new SimpleWeightedSetTerm(tokens.size(), "view", 0, Weight(0));
+ for (const auto & token : tokens) {
+ node->addTerm(token.first, Weight(token.second));
}
return Node::UP(node);
}
- bool isGenericSearch(Searchable &searchable, const std::string &field, bool strict) const {
+ SearchIterator::UP
+ createSearch(Searchable &searchable, const std::string &field, bool strict) const {
AttributeContext ac(attribute_manager);
FakeRequestContext requestContext(&ac);
MatchData::UP md = layout.createMatchData();
Node::UP node = createNode();
FieldSpecList fields;
- fields.add(FieldSpec(field, fieldId, handle));
+ fields.add(FieldSpec(field, fieldId, handle, ac.getAttribute(field)->getIsFilter()));
queryeval::Blueprint::UP bp = searchable.createBlueprint(requestContext, fields, *node);
bp->fetchPostings(queryeval::ExecuteInfo::create(strict));
SearchIterator::UP sb = bp->createSearch(*md, strict);
- return (dynamic_cast<WeightedSetTermSearch*>(sb.get()) != 0);
+ return sb;
+ }
+ bool isWeightedSetTermSearch(Searchable &searchable, const std::string &field, bool strict) const {
+ return dynamic_cast<WeightedSetTermSearch *>(createSearch(searchable, field, strict).get()) != nullptr;
}
FakeResult search(Searchable &searchable, const std::string &field, bool strict) const {
@@ -140,23 +148,58 @@ struct WS {
} // namespace <unnamed>
+void test_tokens(bool isFilter, const std::vector<uint32_t> & docs) {
+ MockAttributeManager manager;
+ setupAttributeManager(manager, isFilter);
+ AttributeBlueprintFactory adapter;
+
+ FakeResult expect = FakeResult();
+ WS ws = WS(manager);
+ for (uint32_t doc : docs) {
+ auto docS = vespalib::stringify(doc);
+ int32_t weight = doc * 10;
+ expect.doc(doc).weight(weight).pos(0);
+ ws.add(docS, weight);
+ }
+
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "integer", true));
+ EXPECT_TRUE(!ws.isWeightedSetTermSearch(adapter, "integer", false));
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "string", true));
+ EXPECT_TRUE(!ws.isWeightedSetTermSearch(adapter, "string", false));
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "multi", true));
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "multi", false));
+
+ EXPECT_EQUAL(expect, ws.search(adapter, "integer", true));
+ EXPECT_EQUAL(expect, ws.search(adapter, "integer", false));
+ EXPECT_EQUAL(expect, ws.search(adapter, "string", true));
+ EXPECT_EQUAL(expect, ws.search(adapter, "string", false));
+ EXPECT_EQUAL(expect, ws.search(adapter, "multi", true));
+ EXPECT_EQUAL(expect, ws.search(adapter, "multi", false));
+}
TEST("attribute_weighted_set_test") {
+ test_tokens(false, {3, 5, 7});
+ test_tokens(true, {3, 5, 7});
+ test_tokens(false, {3});
+}
+
+TEST("attribute_weighted_set_single_token_filter_lifted_out") {
MockAttributeManager manager;
- setupAttributeManager(manager);
+ setupAttributeManager(manager, true);
AttributeBlueprintFactory adapter;
- FakeResult expect = FakeResult()
- .doc(3).elem(0).weight(30).pos(0)
- .doc(5).elem(0).weight(50).pos(0)
- .doc(7).elem(0).weight(70).pos(0);
- WS ws = WS(manager).add("7", 70).add("5", 50).add("3", 30);
-
- EXPECT_TRUE(ws.isGenericSearch(adapter, "integer", true));
- EXPECT_TRUE(!ws.isGenericSearch(adapter, "integer", false));
- EXPECT_TRUE(ws.isGenericSearch(adapter, "string", true));
- EXPECT_TRUE(!ws.isGenericSearch(adapter, "string", false));
- EXPECT_TRUE(ws.isGenericSearch(adapter, "multi", true));
- EXPECT_TRUE(ws.isGenericSearch(adapter, "multi", false));
+ FakeResult expect = FakeResult().doc(3).elem(0).weight(30).pos(0);
+ WS ws = WS(manager).add("3", 30);
+
+ EXPECT_EQUAL("search::FilterAttributeIteratorStrict<search::attribute::SingleNumericSearchContext<long, search::attribute::NumericMatcher<long> > >",
+ ws.createSearch(adapter, "integer", true)->getClassName());
+ EXPECT_EQUAL("search::FilterAttributeIteratorT<search::attribute::SingleNumericSearchContext<long, search::attribute::NumericMatcher<long> > >",
+ ws.createSearch(adapter, "integer", false)->getClassName());
+ EXPECT_EQUAL("search::FilterAttributeIteratorStrict<search::attribute::SingleEnumSearchContext<char const*, search::attribute::StringSearchContext> >",
+ ws.createSearch(adapter, "string", true)->getClassName());
+ EXPECT_EQUAL("search::FilterAttributeIteratorT<search::attribute::SingleEnumSearchContext<char const*, search::attribute::StringSearchContext> >",
+ ws.createSearch(adapter, "string", false)->getClassName());
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "multi", true));
+ EXPECT_TRUE(ws.isWeightedSetTermSearch(adapter, "multi", false));
EXPECT_EQUAL(expect, ws.search(adapter, "integer", true));
EXPECT_EQUAL(expect, ws.search(adapter, "integer", false));
diff --git a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
index 40d4b20aaf2..ac1042dda6c 100644
--- a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
+++ b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
@@ -4,6 +4,7 @@
#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/attribute/attributeiterators.h>
#include <vespa/searchlib/attribute/flagattribute.h>
+#include <vespa/searchlib/attribute/postinglistsearchcontext.h>
#include <vespa/searchlib/attribute/searchcontextelementiterator.h>
#include <vespa/searchlib/attribute/singleboolattribute.h>
#include <vespa/searchlib/attribute/stringbase.h>
@@ -1424,6 +1425,25 @@ SearchContextTest::testPrefixSearch(const vespalib::string& name, const Config&
}
}
}
+
+ // Long range of prefixes with unique strings that causes
+ // PostingListFoldedSearchContextT<DataT>::countHits() to populate
+ // partial vector of posting indexes, with scan resumed by
+ // fillArray or fillBitVector.
+ auto& vec = dynamic_cast<StringAttribute &>(*attr.get());
+ uint32_t old_size = attr->getNumDocs();
+ constexpr uint32_t longrange_values = search::attribute::PostingListFoldedSearchContextT<int32_t>::MAX_POSTING_INDEXES_SIZE + 100;
+ attr->addDocs(longrange_values);
+ DocSet exp_longrange;
+ for (uint32_t i = 0; i < longrange_values; ++i) {
+ vespalib::asciistream ss;
+ ss << "lpref" << i;
+ vespalib::string sss(ss.str());
+ exp_longrange.put(old_size + i);
+ vec.update(old_size + i, vespalib::string(ss.str()).c_str());
+ }
+ attr->commit();
+ performSearch(*attr, "lpref", exp_longrange, TermType::PREFIXTERM);
}
diff --git a/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
index e9d0f8cb736..52329f31ba7 100644
--- a/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
+++ b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
@@ -389,8 +389,8 @@ testSingleValue(Attribute & svsa, Config &cfg)
TEST("testSingleValue")
{
EXPECT_EQUAL(24u, sizeof(SearchContext));
- EXPECT_EQUAL(40u, sizeof(StringSearchHelper));
- EXPECT_EQUAL(96u, sizeof(attribute::SingleStringEnumSearchContext));
+ EXPECT_EQUAL(48u, sizeof(StringSearchHelper));
+ EXPECT_EQUAL(104u, sizeof(attribute::SingleStringEnumSearchContext));
{
Config cfg(BasicType::STRING, CollectionType::SINGLE);
SingleValueStringAttribute svsa("svsa", cfg);
diff --git a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
index ef0fd56840a..c617db871a7 100644
--- a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
+++ b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
@@ -128,9 +128,9 @@ TEST("test And propagates updated histestimate") {
const RememberExecuteInfo & child = dynamic_cast<const RememberExecuteInfo &>(bp.getChild(i));
EXPECT_EQUAL((i == 0), child.executeInfo.isStrict());
}
- EXPECT_EQUAL(1.0, dynamic_cast<const RememberExecuteInfo &>(bp.getChild(0)).executeInfo.hitRate());
- EXPECT_EQUAL(1.0/250, dynamic_cast<const RememberExecuteInfo &>(bp.getChild(1)).executeInfo.hitRate());
- EXPECT_EQUAL(1.0/(250*25), dynamic_cast<const RememberExecuteInfo &>(bp.getChild(2)).executeInfo.hitRate());
+ EXPECT_EQUAL(1.0f, dynamic_cast<const RememberExecuteInfo &>(bp.getChild(0)).executeInfo.hitRate());
+ EXPECT_EQUAL(1.0f/250, dynamic_cast<const RememberExecuteInfo &>(bp.getChild(1)).executeInfo.hitRate());
+ EXPECT_EQUAL(1.0f/(250*25), dynamic_cast<const RememberExecuteInfo &>(bp.getChild(2)).executeInfo.hitRate());
}
TEST("test And Blueprint") {
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
index 1519bb14554..71ea2a67299 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
@@ -337,10 +337,7 @@ public:
if (tfmda.size() == 1) {
// search in exactly one field
fef::TermFieldMatchData &tfmd = *tfmda[0];
- return search::common::create_location_iterator(tfmd,
- _attribute.getNumDocs(),
- strict,
- _location);
+ return common::create_location_iterator(tfmd, _attribute.getNumDocs(), strict, _location);
} else {
LOG(debug, "wrong size tfmda: %zu (fallback to old location iterator)\n", tfmda.size());
}
@@ -485,6 +482,9 @@ DirectWeightedSetBlueprint<SearchType>::createLeafSearch(const TermFieldMatchDat
_attr.create(r.posting_idx, iterators);
}
bool field_is_filter = getState().fields()[0].isFilter();
+ if (field_is_filter && tfmda[0]->isNotNeeded()) {
+ return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators));
+ }
return SearchType::create(*tfmda[0], field_is_filter, _weights, std::move(iterators));
}
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_weighted_set_blueprint.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_weighted_set_blueprint.cpp
index 108128eeb39..ea9dc9b1948 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_weighted_set_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_weighted_set_blueprint.cpp
@@ -30,7 +30,7 @@ protected:
const attribute::IAttributeVector &attribute() const { return _attr; }
public:
- UseAttr(const attribute::IAttributeVector & attr)
+ explicit UseAttr(const attribute::IAttributeVector & attr)
: _attr(attr) {}
};
@@ -40,7 +40,7 @@ class UseStringEnum : public UseAttr
{
public:
using TokenT = uint32_t;
- UseStringEnum(const IAttributeVector & attr)
+ explicit UseStringEnum(const IAttributeVector & attr)
: UseAttr(attr) {}
auto mapToken(const ISearchContext &context) const {
return attribute().findFoldedEnums(context.queryTerm()->getTerm());
@@ -56,7 +56,7 @@ class UseInteger : public UseAttr
{
public:
using TokenT = uint64_t;
- UseInteger(const IAttributeVector & attr) : UseAttr(attr) {}
+ explicit UseInteger(const IAttributeVector & attr) : UseAttr(attr) {}
std::vector<int64_t> mapToken(const ISearchContext &context) const {
std::vector<int64_t> result;
Int64Range range(context.getAsIntegerTerm());
@@ -157,6 +157,10 @@ AttributeWeightedSetBlueprint::createLeafSearch(const fef::TermFieldMatchDataArr
assert(tfmda.size() == 1);
assert(getState().numFields() == 1);
fef::TermFieldMatchData &tfmd = *tfmda[0];
+ bool field_is_filter = getState().fields()[0].isFilter();
+ if ((tfmd.isNotNeeded() || field_is_filter) && (_contexts.size() == 1)) {
+ return _contexts[0]->createIterator(&tfmd, strict);
+ }
if (strict) { // use generic weighted set search
fef::MatchDataLayout layout;
auto handle = layout.allocTermField(tfmd.getFieldId());
@@ -167,7 +171,6 @@ AttributeWeightedSetBlueprint::createLeafSearch(const fef::TermFieldMatchDataArr
// TODO: pass ownership with unique_ptr
children[i] = _contexts[i]->createIterator(child_tfmd, true).release();
}
- bool field_is_filter = getState().fields()[0].isFilter();
return queryeval::WeightedSetTermSearch::create(children, tfmd, field_is_filter, _weights, std::move(match_data));
} else { // use attribute filter optimization
bool isString = (_attr.isStringType() && _attr.hasEnum());
@@ -182,18 +185,16 @@ AttributeWeightedSetBlueprint::createLeafSearch(const fef::TermFieldMatchDataArr
}
queryeval::SearchIterator::UP
-AttributeWeightedSetBlueprint::createFilterSearch(bool strict, FilterConstraint constraint) const
+AttributeWeightedSetBlueprint::createFilterSearch(bool strict, FilterConstraint) const
{
- (void) constraint;
std::vector<std::unique_ptr<queryeval::SearchIterator>> children;
children.reserve(_contexts.size());
for (auto& context : _contexts) {
- auto wrapper = std::make_unique<search::queryeval::FilterWrapper>(1);
+ auto wrapper = std::make_unique<queryeval::FilterWrapper>(1);
wrapper->wrap(context->createIterator(wrapper->tfmda()[0], strict));
children.emplace_back(std::move(wrapper));
}
- search::queryeval::UnpackInfo unpack_info;
- return search::queryeval::OrSearch::create(std::move(children), strict, unpack_info);
+ return queryeval::OrSearch::create(std::move(children), strict, queryeval::UnpackInfo());
}
void
diff --git a/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.cpp b/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.cpp
index 580c34bd5d0..18f480eebcd 100644
--- a/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.cpp
@@ -1,17 +1,97 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "dfa_fuzzy_matcher.h"
+#include <vespa/vespalib/text/utf8.h>
+#include <vespa/vespalib/text/lowercase.h>
using vespalib::fuzzy::LevenshteinDfa;
+using vespalib::LowerCase;
+using vespalib::Utf8Reader;
+using vespalib::Utf8ReaderForZTS;
namespace search::attribute {
-DfaFuzzyMatcher::DfaFuzzyMatcher(std::string_view target, uint8_t max_edits, bool cased, LevenshteinDfa::DfaType dfa_type)
- : _dfa(vespalib::fuzzy::LevenshteinDfa::build(target, max_edits, (cased ? LevenshteinDfa::Casing::Cased : LevenshteinDfa::Casing::Uncased), dfa_type)),
- _successor()
+namespace {
+
+std::vector<uint32_t>
+extract_prefix(std::string_view target, uint32_t prefix_size, bool cased)
+{
+ std::vector<uint32_t> result;
+ result.reserve(prefix_size);
+ Utf8Reader reader(vespalib::stringref(target.data(), target.size()));
+ for (size_t pos = 0; pos < prefix_size && reader.hasMore(); ++pos) {
+ uint32_t code_point = reader.getChar();
+ if (!cased) {
+ code_point = LowerCase::convert(code_point);
+ }
+ result.emplace_back(code_point);
+ }
+ return result;
+}
+
+std::string_view
+extract_suffix(std::string_view target, uint32_t prefix_size)
{
+ Utf8Reader reader(vespalib::stringref(target.data(), target.size()));
+ for (size_t pos = 0; pos < prefix_size && reader.hasMore(); ++pos) {
+ (void) reader.getChar();
+ }
+ std::string_view result = target;
+ result.remove_prefix(reader.getPos());
+ return result;
+}
+
+}
+
+DfaFuzzyMatcher::DfaFuzzyMatcher(std::string_view target, uint8_t max_edits, uint32_t prefix_size, bool cased, LevenshteinDfa::DfaType dfa_type)
+ : _dfa(vespalib::fuzzy::LevenshteinDfa::build(extract_suffix(target, prefix_size), max_edits, (cased ? LevenshteinDfa::Casing::Cased : LevenshteinDfa::Casing::Uncased), dfa_type)),
+ _successor(),
+ _prefix(extract_prefix(target, prefix_size, cased)),
+ _prefix_size(prefix_size),
+ _cased(cased)
+{
+ _successor = _prefix;
}
DfaFuzzyMatcher::~DfaFuzzyMatcher() = default;
+const char*
+DfaFuzzyMatcher::skip_prefix(const char* word) const
+{
+ Utf8ReaderForZTS reader(word);
+ size_t pos = 0;
+ for (; pos < _prefix.size() && reader.hasMore(); ++pos) {
+ (void) reader.getChar();
+ }
+ assert(pos == _prefix.size());
+ return reader.get_current_ptr();
+}
+
+bool
+DfaFuzzyMatcher::is_match(const char* word) const
+{
+ if (_prefix_size > 0) {
+ Utf8ReaderForZTS reader(word);
+ size_t pos = 0;
+ for (; pos < _prefix.size() && reader.hasMore(); ++pos) {
+ uint32_t code_point = reader.getChar();
+ if (!_cased) {
+ code_point = LowerCase::convert(code_point);
+ }
+ if (code_point != _prefix[pos]) {
+ break;
+ }
+ }
+ if (!reader.hasMore() && pos == _prefix.size() && pos < _prefix_size) {
+ return true;
+ }
+ if (pos != _prefix_size) {
+ return false;
+ }
+ word = reader.get_current_ptr();
+ }
+ auto match = _dfa.match(word);
+ return match.matches();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.h b/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.h
index fcba13f85a4..8e5b3ce0ccd 100644
--- a/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.h
+++ b/searchlib/src/vespa/searchlib/attribute/dfa_fuzzy_matcher.h
@@ -5,6 +5,7 @@
#include "dfa_string_comparator.h"
#include <vespa/vespalib/datastore/atomic_entry_ref.h>
#include <vespa/vespalib/fuzzy/levenshtein_dfa.h>
+#include <iostream>
namespace search::attribute {
@@ -17,22 +18,53 @@ namespace search::attribute {
class DfaFuzzyMatcher {
private:
vespalib::fuzzy::LevenshteinDfa _dfa;
- std::vector<uint32_t> _successor;
+ std::vector<uint32_t> _successor;
+ std::vector<uint32_t> _prefix;
+ uint32_t _prefix_size;
+ bool _cased;
+ const char* skip_prefix(const char* word) const;
public:
- DfaFuzzyMatcher(std::string_view target, uint8_t max_edits, bool cased, vespalib::fuzzy::LevenshteinDfa::DfaType dfa_type);
+ DfaFuzzyMatcher(std::string_view target, uint8_t max_edits, uint32_t prefix_size, bool cased, vespalib::fuzzy::LevenshteinDfa::DfaType dfa_type);
~DfaFuzzyMatcher();
+ bool is_match(const char *word) const;
+
+ /*
+ * If prefix size is nonzero then this variant of is_match()
+ * should only be called with words that starts with the extracted
+ * prefix of the target word.
+ *
+ * Caller must position iterator at right location using lower bound
+ * functionality in the dictionary.
+ */
template <typename DictionaryConstIteratorType>
bool is_match(const char* word, DictionaryConstIteratorType& itr, const DfaStringComparator::DataStoreType& data_store) {
- auto match = _dfa.match(word, _successor);
- if (match.matches()) {
- return true;
+ if (_prefix_size > 0) {
+ word = skip_prefix(word);
+ if (_prefix.size() < _prefix_size) {
+ if (*word == '\0') {
+ return true;
+ }
+ _successor.resize(_prefix.size());
+ _successor.emplace_back(1);
+ } else {
+ _successor.resize(_prefix.size());
+ auto match = _dfa.match(word, _successor);
+ if (match.matches()) {
+ return true;
+ }
+ }
} else {
- DfaStringComparator cmp(data_store, _successor);
- itr.seek(vespalib::datastore::AtomicEntryRef(), cmp);
- return false;
+ _successor.clear();
+ auto match = _dfa.match(word, _successor);
+ if (match.matches()) {
+ return true;
+ }
}
+ DfaStringComparator cmp(data_store, _successor);
+ itr.seek(vespalib::datastore::AtomicEntryRef(), cmp);
+ return false;
}
};
diff --git a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp
index 3c0bae00047..97e725649d3 100644
--- a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp
@@ -2,21 +2,27 @@
#include "document_weight_or_filter_search.h"
#include "iterator_pack.h"
+#include <vespa/searchlib/fef/matchdata.h>
+#include <vespa/searchlib/queryeval/iterator_pack.h>
#include <vespa/searchlib/common/bitvector.h>
#include <vespa/searchlib/queryeval/emptysearch.h>
+using search::queryeval::SearchIteratorPack;
+
namespace search::attribute {
+template<typename IteratorPack>
class DocumentWeightOrFilterSearchImpl : public DocumentWeightOrFilterSearch
{
- AttributeIteratorPack _children;
+ IteratorPack _children;
+ void seek_all(uint32_t docId);
public:
- DocumentWeightOrFilterSearchImpl(AttributeIteratorPack&& children);
- ~DocumentWeightOrFilterSearchImpl();
+ explicit DocumentWeightOrFilterSearchImpl(IteratorPack&& children);
+ ~DocumentWeightOrFilterSearchImpl() override;
void doSeek(uint32_t docId) override;
- void doUnpack(uint32_t) override;
+ void doUnpack(uint32_t) override { }
void initRange(uint32_t begin, uint32_t end) override {
SearchIterator::initRange(begin, end);
@@ -32,48 +38,75 @@ public:
}
std::unique_ptr<BitVector> get_hits(uint32_t begin_id) override {
+ seek_all(getDocId());
return _children.get_hits(begin_id, getEndId());
}
Trinary is_strict() const override { return Trinary::True; }
};
-DocumentWeightOrFilterSearchImpl::DocumentWeightOrFilterSearchImpl(AttributeIteratorPack&& children)
+template<typename IteratorPack>
+DocumentWeightOrFilterSearchImpl<IteratorPack>::DocumentWeightOrFilterSearchImpl(IteratorPack&& children)
: DocumentWeightOrFilterSearch(),
_children(std::move(children))
{
}
-DocumentWeightOrFilterSearchImpl::~DocumentWeightOrFilterSearchImpl() = default;
+template<typename IteratorPack>
+DocumentWeightOrFilterSearchImpl<IteratorPack>::~DocumentWeightOrFilterSearchImpl() = default;
+template<typename IteratorPack>
void
-DocumentWeightOrFilterSearchImpl::doSeek(uint32_t docId)
-{
- if (_children.get_docid(0) < docId) {
- _children.seek(0, docId);
- }
- uint32_t min_doc_id = _children.get_docid(0);
- for (uint16_t i = 1; i < _children.size(); ++i) {
- if (_children.get_docid(i) < docId) {
+DocumentWeightOrFilterSearchImpl<IteratorPack>::seek_all(uint32_t docId) {
+ for (uint16_t i = 0; i < _children.size(); ++i) {
+ uint32_t next = _children.get_docid(i);
+ if (next < docId) {
_children.seek(i, docId);
}
- min_doc_id = std::min(min_doc_id, _children.get_docid(i));
- }
- setDocId(min_doc_id);
+ }
}
+template<typename IteratorPack>
void
-DocumentWeightOrFilterSearchImpl::doUnpack(uint32_t)
+DocumentWeightOrFilterSearchImpl<IteratorPack>::doSeek(uint32_t docId)
{
+ uint32_t min_doc_id = endDocId;
+ for (uint16_t i = 0; i < _children.size(); ++i) {
+ uint32_t next = _children.get_docid(i);
+ if (next < docId) {
+ next = _children.seek(i, docId);
+ }
+ if (next == docId) {
+ setDocId(next);
+ return;
+ }
+ min_doc_id = std::min(min_doc_id, next);
+ }
+ setDocId(min_doc_id);
}
-std::unique_ptr<search::queryeval::SearchIterator>
+std::unique_ptr<queryeval::SearchIterator>
DocumentWeightOrFilterSearch::create(std::vector<DocumentWeightIterator>&& children)
{
if (children.empty()) {
return std::make_unique<queryeval::EmptySearch>();
} else {
- return std::make_unique<DocumentWeightOrFilterSearchImpl>(AttributeIteratorPack(std::move(children)));
+ std::sort(children.begin(), children.end(),
+ [](const auto & a, const auto & b) { return a.size() > b.size(); });
+ using OrFilter = DocumentWeightOrFilterSearchImpl<AttributeIteratorPack>;
+ return std::make_unique<OrFilter>(AttributeIteratorPack(std::move(children)));
+ }
+}
+
+std::unique_ptr<queryeval::SearchIterator>
+DocumentWeightOrFilterSearch::create(const std::vector<SearchIterator *>& children,
+ std::unique_ptr<fef::MatchData> md)
+{
+ if (children.empty()) {
+ return std::make_unique<queryeval::EmptySearch>();
+ } else {
+ using OrFilter = DocumentWeightOrFilterSearchImpl<SearchIteratorPack>;
+ return std::make_unique<OrFilter>(SearchIteratorPack(children, std::move(md)));
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.h b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.h
index 62be883ab52..19fa20e2d51 100644
--- a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.h
+++ b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.h
@@ -3,21 +3,21 @@
#include "i_document_weight_attribute.h"
#include <vespa/searchlib/queryeval/searchiterator.h>
+namespace search::fef { class MatchData; }
namespace search::attribute {
/**
* Filter iterator on top of document weight iterators with OR semantics used during
* calculation of global filter for weighted set terms, wand terms and dot product terms.
*/
-class DocumentWeightOrFilterSearch : public search::queryeval::SearchIterator
+class DocumentWeightOrFilterSearch : public queryeval::SearchIterator
{
protected:
- DocumentWeightOrFilterSearch()
- : search::queryeval::SearchIterator()
- {
- }
+ DocumentWeightOrFilterSearch() = default;
public:
- static std::unique_ptr<search::queryeval::SearchIterator> create(std::vector<DocumentWeightIterator>&& children);
+ static std::unique_ptr<SearchIterator> create(std::vector<DocumentWeightIterator>&& children);
+ static std::unique_ptr<SearchIterator> create(const std::vector<SearchIterator *>& children,
+ std::unique_ptr<fef::MatchData> md);
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
index 147f56d6d47..3d9e3095536 100644
--- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
@@ -5,6 +5,14 @@
namespace search {
+AttributeIteratorPack::~AttributeIteratorPack() = default;
+
+AttributeIteratorPack::AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children)
+ : _children(std::move(children))
+{
+ assert(_children.size() < 0x10000);
+}
+
std::unique_ptr<BitVector>
AttributeIteratorPack::get_hits(uint32_t begin_id, uint32_t end_id) {
BitVector::UP result(BitVector::create(begin_id, end_id));
@@ -17,9 +25,9 @@ AttributeIteratorPack::or_hits_into(BitVector &result, uint32_t begin_id) {
for (size_t i = 0; i < size(); ++i) {
uint32_t docId = get_docid(i);
if (begin_id > docId) {
- seek(i, begin_id);
+ docId = seek(i, begin_id);
}
- for (docId = get_docid(i); docId < result.size(); docId = next(i)) {
+ for (uint32_t limit = result.size(); docId < limit; docId = next(i)) {
result.setBit(docId);
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
index e042aab5eae..80e4c227860 100644
--- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
+++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
@@ -15,12 +15,12 @@ private:
std::vector<DocumentWeightIterator> _children;
public:
- AttributeIteratorPack() : _children() {}
+ AttributeIteratorPack() noexcept : _children() {}
AttributeIteratorPack(AttributeIteratorPack &&rhs) noexcept = default;
AttributeIteratorPack &operator=(AttributeIteratorPack &&rhs) noexcept = default;
- explicit AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children)
- : _children(std::move(children)) {}
+ explicit AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children);
+ ~AttributeIteratorPack();
uint32_t get_docid(uint16_t ref) const {
return _children[ref].valid() ? _children[ref].getKey() : endDocId;
@@ -41,7 +41,7 @@ public:
std::unique_ptr<BitVector> get_hits(uint32_t begin_id, uint32_t end_id);
void or_hits_into(BitVector &result, uint32_t begin_id);
- size_t size() const { return _children.size(); }
+ uint16_t size() const noexcept { return _children.size(); }
void initRange(uint32_t begin, uint32_t end) {
(void) end;
for (auto &child: _children) {
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.cpp b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.cpp
index 12c887eb407..2b1d4fa3286 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.cpp
@@ -27,7 +27,8 @@ PostingListSearchContext(const IEnumStoreDictionary& dictionary, bool has_btree_
_FSTC(0.0),
_PLSTC(0.0),
_hasWeight(hasWeight),
- _useBitVector(useBitVector)
+ _useBitVector(useBitVector),
+ _counted_hits()
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
index 05ccedb39ec..8472d3897a0 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
@@ -10,9 +10,11 @@
#include <vespa/searchcommon/attribute/search_context_params.h>
#include <vespa/searchcommon/common/range.h>
#include <vespa/searchlib/query/query_term_ucs4.h>
-#include <vespa/vespalib/util/regexp.h>
+#include <vespa/searchlib/queryeval/executeinfo.h>
#include <vespa/vespalib/fuzzy/fuzzy_matcher.h>
+#include <vespa/vespalib/util/regexp.h>
#include <regex>
+#include <optional>
namespace search::attribute {
@@ -30,24 +32,30 @@ protected:
using Dictionary = EnumPostingTree;
using DictionaryConstIterator = Dictionary::ConstIterator;
using FrozenDictionary = Dictionary::FrozenView;
+ using EntryRef = vespalib::datastore::EntryRef;
using EnumIndex = IEnumStore::Index;
- const IEnumStoreDictionary & _dictionary;
- const ISearchContext &_baseSearchCtx;
- const BitVector *_bv; // bitvector if _useBitVector has been set
- const FrozenDictionary _frozenDictionary;
- DictionaryConstIterator _lowerDictItr;
- DictionaryConstIterator _upperDictItr;
- uint64_t _numValues; // attr.getStatus().getNumValues();
- uint32_t _uniqueValues;
- uint32_t _docIdLimit;
- uint32_t _dictSize;
- vespalib::datastore::EntryRef _pidx;
- vespalib::datastore::EntryRef _frozenRoot; // Posting list in tree form
- float _FSTC; // Filtering Search Time Constant
- float _PLSTC; // Posting List Search Time Constant
- bool _hasWeight;
- bool _useBitVector;
+ static constexpr long MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION = 100;
+ static constexpr long MIN_UNIQUE_VALUES_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION = 20;
+ static constexpr long MIN_APPROXHITS_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION = 10;
+
+ const IEnumStoreDictionary& _dictionary;
+ const ISearchContext& _baseSearchCtx;
+ const BitVector* _bv; // bitvector if _useBitVector has been set
+ const FrozenDictionary _frozenDictionary;
+ DictionaryConstIterator _lowerDictItr;
+ DictionaryConstIterator _upperDictItr;
+ uint64_t _numValues; // attr.getStatus().getNumValues();
+ uint32_t _uniqueValues;
+ uint32_t _docIdLimit;
+ uint32_t _dictSize;
+ EntryRef _pidx;
+ EntryRef _frozenRoot; // Posting list in tree form
+ float _FSTC; // Filtering Search Time Constant
+ float _PLSTC; // Posting List Search Time Constant
+ bool _hasWeight;
+ bool _useBitVector;
+ mutable std::optional<size_t> _counted_hits; // Snapshot of size of posting lists in range
PostingListSearchContext(const IEnumStoreDictionary& dictionary, bool has_btree_dictionary, uint32_t docIdLimit,
uint64_t numValues, bool hasWeight, bool useBitVector, const ISearchContext &baseSearchCtx);
@@ -89,6 +97,18 @@ protected:
return (numHits > 1000) &&
(calculateFilteringCost() < calculatePostingListCost(numHits));
}
+ virtual bool use_posting_list_when_non_strict(const queryeval::ExecuteInfo&) const {
+ return false;
+ }
+ virtual bool fallback_to_approx_num_hits() const {
+ return ((_uniqueValues > MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION) &&
+ ((_uniqueValues * MIN_UNIQUE_VALUES_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION > static_cast<int>(_docIdLimit)) ||
+ (calculateApproxNumHits() * MIN_APPROXHITS_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION > _docIdLimit) ||
+ (_uniqueValues > MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION*10)));
+ }
+ virtual size_t countHits() const = 0;
+ virtual void fillArray() = 0;
+ virtual void fillBitVector() = 0;
};
@@ -110,19 +130,15 @@ protected:
*/
PostingListMerger<DataT> _merger;
- static const long MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION = 100;
- static const long MIN_UNIQUE_VALUES_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION = 20;
- static const long MIN_APPROXHITS_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION = 10;
-
PostingListSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
bool hasWeight, const PostingList &postingList,
bool useBitVector, const ISearchContext &baseSearchCtx);
~PostingListSearchContextT() override;
void lookupSingle();
- size_t countHits() const;
- void fillArray();
- void fillBitVector();
+ size_t countHits() const override;
+ void fillArray() override;
+ void fillBitVector() override;
void fetchPostings(const queryeval::ExecuteInfo & strict) override;
// this will be called instead of the fetchPostings function in some cases
@@ -141,22 +157,41 @@ protected:
template <class DataT>
class PostingListFoldedSearchContextT : public PostingListSearchContextT<DataT>
{
+public:
+ static constexpr uint32_t MAX_POSTING_INDEXES_SIZE = 10000;
+
protected:
using Parent = PostingListSearchContextT<DataT>;
using Dictionary = typename Parent::Dictionary;
+ using DictionaryConstIterator = Dictionary::ConstIterator;
+ using EntryRef = vespalib::datastore::EntryRef;
using PostingList = typename Parent::PostingList;
+ using Parent::_counted_hits;
+ using Parent::_docIdLimit;
using Parent::_lowerDictItr;
- using Parent::_uniqueValues;
+ using Parent::_merger;
using Parent::_postingList;
- using Parent::_docIdLimit;
- using Parent::countHits;
+ using Parent::_uniqueValues;
+ using Parent::_upperDictItr;
using Parent::singleHits;
+ using Parent::use_dictionary_entry;
+
+ mutable DictionaryConstIterator _resume_scan_itr;
+ mutable std::vector<EntryRef> _posting_indexes;
PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
bool hasWeight, const PostingList &postingList,
bool useBitVector, const ISearchContext &baseSearchCtx);
-
- unsigned int approximateHits() const override;
+ ~PostingListFoldedSearchContextT() override;
+
+ bool fallback_to_approx_num_hits() const override;
+ size_t countHits() const override;
+ template <bool fill_array>
+ void fill_array_or_bitvector_helper(EntryRef pidx);
+ template <bool fill_array>
+ void fill_array_or_bitvector();
+ void fillArray() override;
+ void fillBitVector() override;
};
@@ -188,6 +223,7 @@ private:
bool use_single_dictionary_entry(PostingListSearchContext::DictionaryConstIterator it) const {
return use_dictionary_entry(it);
}
+ bool use_posting_list_when_non_strict(const queryeval::ExecuteInfo&) const override;
public:
StringPostingSearchContext(BaseSC&& base_sc, bool useBitVector, const AttrT &toBeSearched);
};
@@ -320,13 +356,45 @@ StringPostingSearchContext<BaseSC, AttrT, DataT>::use_dictionary_entry(PostingLi
++it;
return false;
} else if (this->isFuzzy()) {
- if (this->getFuzzyMatcher().isMatch(_enumStore.get_value(it.getKey().load_acquire()))) {
+ return this->is_fuzzy_match(_enumStore.get_value(it.getKey().load_acquire()), it, _enumStore.get_data_store());
+ }
+ return true;
+}
+
+template <typename BaseSC, typename AttrT, typename DataT>
+bool
+StringPostingSearchContext<BaseSC, AttrT, DataT>::use_posting_list_when_non_strict(const queryeval::ExecuteInfo& info) const
+{
+ if (this->isFuzzy()) {
+ uint32_t exp_doc_hits = this->_docIdLimit * info.hitRate();
+ constexpr uint32_t fuzzy_use_posting_list_doc_limit = 10000;
+ /**
+ * The above constant was derived after a query latency experiment with fuzzy matching
+ * on 2M documents with a dictionary size of 292070.
+ *
+ * Cost per document in dfa-based fuzzy matching (scanning the dictionary and merging posting lists) - strict iterator:
+ * 2.8 ms / 2k = 0.0014 ms
+ * 4.4 ms / 20k = 0.00022 ms
+ * 9.0 ms / 200k = 0.000045 ms
+ * 98 ms / 1M = 0.000098 ms
+ *
+ * Cost per document in lookup-based fuzzy matching - non-strict iterator:
+ * 7.6 ms / 2k = 0.0038 ms
+ * 54 ms / 20k = 0.0027 ms
+ * 529 ms / 200k = 0.0026 ms
+ *
+ * Based on this experiment, we observe that we should avoid lookup-based fuzzy matching
+ * when the number of documents to calculate this on exceeds a number between 2000 - 20000.
+ *
+ * Also note that the cost of scanning the dictionary and performing the fuzzy matching
+ * is already performed at this point.
+ * The only work remaining if returning true is merging the posting lists.
+ */
+ if (exp_doc_hits > fuzzy_use_posting_list_doc_limit) {
return true;
}
- ++it;
- return false;
}
- return true;
+ return false;
}
template <typename BaseSC, typename AttrT, typename DataT>
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
index bd1cc1191a7..e10cc8a6f41 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
@@ -9,7 +9,6 @@
#include "postingstore.hpp"
#include "posting_list_traverser.h"
#include <vespa/searchlib/queryeval/emptysearch.h>
-#include <vespa/searchlib/queryeval/executeinfo.h>
#include <vespa/searchlib/common/bitvectoriterator.h>
#include <vespa/searchlib/common/growablebitvector.h>
@@ -58,57 +57,48 @@ PostingListSearchContextT<DataT>::lookupSingle()
}
}
-
template <typename DataT>
size_t
PostingListSearchContextT<DataT>::countHits() const
{
+ if (_counted_hits.has_value()) {
+ return _counted_hits.value();
+ }
size_t sum(0);
- for (auto it(_lowerDictItr); it != _upperDictItr;) {
- if (use_dictionary_entry(it)) {
- sum += _postingList.frozenSize(it.getData().load_acquire());
- ++it;
- }
+ for (auto it(_lowerDictItr); it != _upperDictItr; ++it) {
+ sum += _postingList.frozenSize(it.getData().load_acquire());
}
+ _counted_hits = sum;
return sum;
}
-
template <typename DataT>
void
PostingListSearchContextT<DataT>::fillArray()
{
- for (auto it(_lowerDictItr); it != _upperDictItr;) {
- if (use_dictionary_entry(it)) {
- _merger.addToArray(PostingListTraverser<PostingList>(_postingList,
- it.getData().load_acquire()));
- ++it;
- }
+ for (auto it(_lowerDictItr); it != _upperDictItr; ++it) {
+ _merger.addToArray(PostingListTraverser<PostingList>(_postingList,
+ it.getData().load_acquire()));
}
_merger.merge();
}
-
template <typename DataT>
void
PostingListSearchContextT<DataT>::fillBitVector()
{
- for (auto it(_lowerDictItr); it != _upperDictItr;) {
- if (use_dictionary_entry(it)) {
- _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList,
- it.getData().load_acquire()));
- ++it;
- }
+ for (auto it(_lowerDictItr); it != _upperDictItr; ++it) {
+ _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList,
+ it.getData().load_acquire()));
}
}
-
template <typename DataT>
void
PostingListSearchContextT<DataT>::fetchPostings(const queryeval::ExecuteInfo & execInfo)
{
if (!_merger.merge_done() && _uniqueValues >= 2u) {
- if (execInfo.isStrict() && !fallbackToFiltering()) {
+ if ((execInfo.isStrict() || use_posting_list_when_non_strict(execInfo)) && !fallbackToFiltering()) {
size_t sum(countHits());
if (sum < _docIdLimit / 64) {
_merger.reserveArray(_uniqueValues, sum);
@@ -227,29 +217,20 @@ template <typename DataT>
unsigned int
PostingListSearchContextT<DataT>::approximateHits() const
{
- unsigned int numHits = 0;
+ size_t numHits = 0;
if (_uniqueValues == 0u) {
} else if (_uniqueValues == 1u) {
numHits = singleHits();
} else {
if (this->fallbackToFiltering()) {
numHits = _docIdLimit;
- } else if (_uniqueValues > MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION) {
- if ((_uniqueValues * MIN_UNIQUE_VALUES_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION > static_cast<int>(_docIdLimit)) ||
- (this->calculateApproxNumHits() * MIN_APPROXHITS_TO_NUMDOCS_RATIO_BEFORE_APPROXIMATION > _docIdLimit) ||
- (_uniqueValues > MIN_UNIQUE_VALUES_BEFORE_APPROXIMATION*10))
- {
- numHits = this->calculateApproxNumHits();
- } else {
- // XXX: Unsafe
- numHits = countHits();
- }
+ } else if (this->fallback_to_approx_num_hits()) {
+ numHits = this->calculateApproxNumHits();
} else {
- // XXX: Unsafe
numHits = countHits();
}
}
- return numHits;
+ return std::min(numHits, size_t(std::numeric_limits<uint32_t>::max()));
}
@@ -282,28 +263,98 @@ PostingListFoldedSearchContextT<DataT>::
PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
bool hasWeight, const PostingList &postingList,
bool useBitVector, const ISearchContext &searchContext)
- : Parent(dictionary, docIdLimit, numValues, hasWeight, postingList, useBitVector, searchContext)
+ : Parent(dictionary, docIdLimit, numValues, hasWeight, postingList, useBitVector, searchContext),
+ _resume_scan_itr(),
+ _posting_indexes()
{
}
+template <typename DataT>
+PostingListFoldedSearchContextT<DataT>::~PostingListFoldedSearchContextT() = default;
template <typename DataT>
-unsigned int
-PostingListFoldedSearchContextT<DataT>::approximateHits() const
+bool
+PostingListFoldedSearchContextT<DataT>::fallback_to_approx_num_hits() const
{
- unsigned int numHits = 0;
- if (_uniqueValues == 0u) {
- } else if (_uniqueValues == 1u) {
- numHits = singleHits();
+ return false;
+}
+
+template <typename DataT>
+size_t
+PostingListFoldedSearchContextT<DataT>::countHits() const
+{
+ if (_counted_hits.has_value()) {
+ return _counted_hits.value();
+ }
+ size_t sum(0);
+ bool overflow = false;
+ for (auto it(_lowerDictItr); it != _upperDictItr;) {
+ if (use_dictionary_entry(it)) {
+ auto pidx = it.getData().load_acquire();
+ if (pidx.valid()) {
+ sum += _postingList.frozenSize(pidx);
+ if (!overflow) {
+ if (_posting_indexes.size() < MAX_POSTING_INDEXES_SIZE) {
+ _posting_indexes.emplace_back(pidx);
+ } else {
+ overflow = true;
+ _resume_scan_itr = it;
+ }
+ }
+ }
+ ++it;
+ }
+ }
+ _counted_hits = sum;
+ return sum;
+}
+
+template <typename DataT>
+template <bool fill_array>
+void
+PostingListFoldedSearchContextT<DataT>::fill_array_or_bitvector_helper(EntryRef pidx)
+{
+ if constexpr (fill_array) {
+ _merger.addToArray(PostingListTraverser<PostingList>(_postingList, pidx));
} else {
- if (this->fallbackToFiltering()) {
- numHits = _docIdLimit;
- } else {
- // XXX: Unsafe
- numHits = countHits();
+ _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList, pidx));
+ }
+}
+
+template <typename DataT>
+template <bool fill_array>
+void
+PostingListFoldedSearchContextT<DataT>::fill_array_or_bitvector()
+{
+ for (auto pidx : _posting_indexes) {
+ fill_array_or_bitvector_helper<fill_array>(pidx);
+ }
+ if (_resume_scan_itr.valid()) {
+ for (auto it(_resume_scan_itr); it != _upperDictItr;) {
+ if (use_dictionary_entry(it)) {
+ auto pidx = it.getData().load_acquire();
+ if (pidx.valid()) {
+ fill_array_or_bitvector_helper<fill_array>(pidx);
+ }
+ ++it;
+ }
}
}
- return numHits;
+ _merger.merge();
+}
+
+template <typename DataT>
+void
+PostingListFoldedSearchContextT<DataT>::fillArray()
+{
+ fill_array_or_bitvector<true>();
+}
+
+template <typename DataT>
+void
+PostingListFoldedSearchContextT<DataT>::fillBitVector()
+{
+ fill_array_or_bitvector<false>();
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/string_matcher.h b/searchlib/src/vespa/searchlib/attribute/string_matcher.h
index 05089e1251a..09ba813cefe 100644
--- a/searchlib/src/vespa/searchlib/attribute/string_matcher.h
+++ b/searchlib/src/vespa/searchlib/attribute/string_matcher.h
@@ -32,6 +32,11 @@ protected:
const vespalib::Regex& getRegex() const { return _helper.getRegex(); }
const vespalib::FuzzyMatcher& getFuzzyMatcher() const { return _helper.getFuzzyMatcher(); }
const QueryTermUCS4* get_query_term_ptr() const noexcept { return _query_term.get(); }
+
+ template <typename DictionaryConstIteratorType>
+ bool is_fuzzy_match(const char* word, DictionaryConstIteratorType& itr, const DfaStringComparator::DataStoreType& data_store) const {
+ return _helper.is_fuzzy_match(word, itr, data_store);
+ }
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/string_search_helper.cpp b/searchlib/src/vespa/searchlib/attribute/string_search_helper.cpp
index 1efe39667b8..82709997228 100644
--- a/searchlib/src/vespa/searchlib/attribute/string_search_helper.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/string_search_helper.cpp
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "string_search_helper.h"
+#include "dfa_fuzzy_matcher.h"
+#include "i_enum_store_dictionary.h"
#include <vespa/searchlib/query/query_term_ucs4.h>
#include <vespa/vespalib/text/lowercase.h>
#include <vespa/vespalib/text/utf8.h>
@@ -9,9 +11,32 @@
namespace search::attribute {
+using FMA = vespalib::FuzzyMatchingAlgorithm;
+using LDT = vespalib::fuzzy::LevenshteinDfa::DfaType;
+
+namespace {
+
+LDT
+to_dfa_type(FMA algorithm)
+{
+ switch (algorithm) {
+ case FMA::DfaImplicit:
+ return LDT::Implicit;
+ case FMA::DfaExplicit:
+ return LDT::Explicit;
+ case FMA::DfaTable:
+ return LDT::Table;
+ default:
+ return LDT::Implicit;
+ }
+}
+
+}
+
StringSearchHelper::StringSearchHelper(QueryTermUCS4 & term, bool cased, vespalib::FuzzyMatchingAlgorithm fuzzy_matching_algorithm)
: _regex(),
_fuzzyMatcher(),
+ _dfa_fuzzy_matcher(),
_term(),
_termLen(),
_isPrefix(term.isPrefix()),
@@ -24,12 +49,18 @@ StringSearchHelper::StringSearchHelper(QueryTermUCS4 & term, bool cased, vespali
? vespalib::Regex::from_pattern(term.getTerm(), vespalib::Regex::Options::None)
: vespalib::Regex::from_pattern(term.getTerm(), vespalib::Regex::Options::IgnoreCase);
} else if (isFuzzy()) {
- (void) fuzzy_matching_algorithm;
- // TODO: Select implementation based on algorithm.
_fuzzyMatcher = std::make_unique<vespalib::FuzzyMatcher>(term.getTerm(),
term.getFuzzyMaxEditDistance(),
term.getFuzzyPrefixLength(),
isCased());
+ if ((fuzzy_matching_algorithm != FMA::BruteForce) &&
+ (term.getFuzzyMaxEditDistance() <= 2)) {
+ _dfa_fuzzy_matcher = std::make_unique<DfaFuzzyMatcher>(term.getTerm(),
+ term.getFuzzyMaxEditDistance(),
+ term.getFuzzyPrefixLength(),
+ isCased(),
+ to_dfa_type(fuzzy_matching_algorithm));
+ }
} else if (isCased()) {
_term = term.getTerm();
_termLen = strlen(_term);
@@ -48,7 +79,7 @@ StringSearchHelper::isMatch(const char *src) const noexcept {
return getRegex().valid() && getRegex().partial_match(std::string_view(src));
}
if (__builtin_expect(isFuzzy(), false)) {
- return getFuzzyMatcher().isMatch(src);
+ return _dfa_fuzzy_matcher ? _dfa_fuzzy_matcher->is_match(src) : getFuzzyMatcher().isMatch(src);
}
if (__builtin_expect(isCased(), false)) {
int res = strncmp(_term, src, _termLen);
@@ -67,4 +98,27 @@ StringSearchHelper::isMatch(const char *src) const noexcept {
return (_ucs4[j] == 0 && (val == 0 || isPrefix()));
}
+template <typename DictionaryConstIteratorType>
+bool
+StringSearchHelper::is_fuzzy_match(const char* word, DictionaryConstIteratorType& itr, const DfaStringComparator::DataStoreType& data_store) const
+{
+ if (_dfa_fuzzy_matcher) {
+ return _dfa_fuzzy_matcher->is_match(word, itr, data_store);
+ } else {
+ if (_fuzzyMatcher->isMatch(word)) {
+ return true;
+ }
+ ++itr;
+ return false;
+ }
+}
+
+template
+bool
+StringSearchHelper::is_fuzzy_match(const char*, EnumPostingTree::ConstIterator&, const DfaStringComparator::DataStoreType&) const;
+
+template
+bool
+StringSearchHelper::is_fuzzy_match(const char*, EnumTree::ConstIterator&, const DfaStringComparator::DataStoreType&) const;
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/string_search_helper.h b/searchlib/src/vespa/searchlib/attribute/string_search_helper.h
index 0e7a116a874..e59291e24a7 100644
--- a/searchlib/src/vespa/searchlib/attribute/string_search_helper.h
+++ b/searchlib/src/vespa/searchlib/attribute/string_search_helper.h
@@ -2,6 +2,7 @@
#pragma once
+#include "dfa_string_comparator.h"
#include <vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h>
#include <vespa/vespalib/regex/regex.h>
@@ -10,6 +11,8 @@ namespace search { class QueryTermUCS4; }
namespace search::attribute {
+class DfaFuzzyMatcher;
+
/**
* Helper class for search context when scanning string fields
* It handles different search settings like prefix, regex and cased/uncased.
@@ -29,11 +32,16 @@ public:
bool isCased() const noexcept { return _isCased; }
bool isFuzzy() const noexcept { return _isFuzzy; }
const vespalib::Regex & getRegex() const noexcept { return _regex; }
- const FuzzyMatcher & getFuzzyMatcher() const noexcept { return *_fuzzyMatcher; }
+ const FuzzyMatcher& getFuzzyMatcher() const noexcept { return *_fuzzyMatcher; }
+
+ template <typename DictionaryConstIteratorType>
+ bool is_fuzzy_match(const char* word, DictionaryConstIteratorType& itr, const DfaStringComparator::DataStoreType& data_store) const;
+
private:
using ucs4_t = uint32_t;
vespalib::Regex _regex;
std::unique_ptr<FuzzyMatcher> _fuzzyMatcher;
+ std::unique_ptr<DfaFuzzyMatcher> _dfa_fuzzy_matcher;
std::unique_ptr<ucs4_t[]> _ucs4;
const char * _term;
uint32_t _termLen; // measured in bytes
diff --git a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.h b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.h
index 134c4215fc2..17b4c2d09ba 100644
--- a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.h
+++ b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.h
@@ -34,17 +34,17 @@ public:
uint64_t _subqueries;
};
private:
- bool isRawScore() const { return _flags & RAW_SCORE_FLAG; }
- bool isMultiPos() const { return _flags & MULTIPOS_FLAG; }
- bool empty() const { return _sz == 0; }
- void clear() { _sz = 0; }
- bool allocated() const { return isMultiPos(); }
- const TermFieldMatchDataPosition * getFixed() const { return reinterpret_cast<const TermFieldMatchDataPosition *>(_data._position); }
- TermFieldMatchDataPosition * getFixed() { return reinterpret_cast<TermFieldMatchDataPosition *>(_data._position); }
- const TermFieldMatchDataPosition * getMultiple() const { return _data._positions._positions; }
- TermFieldMatchDataPosition * getMultiple() { return _data._positions._positions; }
- int32_t getElementWeight() const { return empty() ? 1 : allocated() ? getMultiple()->getElementWeight() : getFixed()->getElementWeight(); }
- uint32_t getMaxElementLength() const { return empty() ? 0 : allocated() ? _data._positions._maxElementLength : getFixed()->getElementLen(); }
+ bool isRawScore() const noexcept { return _flags & RAW_SCORE_FLAG; }
+ bool isMultiPos() const noexcept { return _flags & MULTIPOS_FLAG; }
+ bool empty() const noexcept { return _sz == 0; }
+ void clear() noexcept { _sz = 0; }
+ bool allocated() const noexcept { return isMultiPos(); }
+ const TermFieldMatchDataPosition * getFixed() const noexcept { return reinterpret_cast<const TermFieldMatchDataPosition *>(_data._position); }
+ TermFieldMatchDataPosition * getFixed() noexcept { return reinterpret_cast<TermFieldMatchDataPosition *>(_data._position); }
+ const TermFieldMatchDataPosition * getMultiple() const noexcept { return _data._positions._positions; }
+ TermFieldMatchDataPosition * getMultiple() noexcept { return _data._positions._positions; }
+ int32_t getElementWeight() const noexcept { return empty() ? 1 : allocated() ? getMultiple()->getElementWeight() : getFixed()->getElementWeight(); }
+ uint32_t getMaxElementLength() const noexcept { return empty() ? 0 : allocated() ? _data._positions._maxElementLength : getFixed()->getElementLen(); }
void appendPositionToAllocatedVector(const TermFieldMatchDataPosition &pos);
void allocateVector();
void resizePositionVector(size_t sz) __attribute__((noinline));
@@ -70,8 +70,8 @@ private:
public:
PositionsIterator begin() const { return allocated() ? getMultiple() : getFixed(); }
PositionsIterator end() const { return allocated() ? getMultiple() + _sz : empty() ? getFixed() : getFixed()+1; }
- size_t size() const { return _sz; }
- size_t capacity() const { return allocated() ? _data._positions._allocated : 1; }
+ size_t size() const noexcept { return _sz; }
+ size_t capacity() const noexcept { return allocated() ? _data._positions._allocated : 1; }
void reservePositions(size_t sz) {
if (sz > capacity()) {
if (!allocated()) {
@@ -114,7 +114,7 @@ public:
*
* @return field id
**/
- uint32_t getFieldId() const {
+ uint32_t getFieldId() const noexcept {
return __builtin_expect(_fieldId != ILLEGAL_FIELD_ID, true) ? _fieldId : IllegalFieldId;
}
@@ -125,7 +125,7 @@ public:
* @return this object (for chaining)
* @param docId id of the document we are generating match information for
**/
- TermFieldMatchData &reset(uint32_t docId) {
+ TermFieldMatchData &reset(uint32_t docId) noexcept {
_docId = docId;
_sz = 0;
_numOccs = 0;
@@ -145,7 +145,7 @@ public:
* @return this object (for chaining)
* @param docId id of the document we are generating match information for
**/
- TermFieldMatchData &resetOnlyDocId(uint32_t docId) {
+ TermFieldMatchData &resetOnlyDocId(uint32_t docId) noexcept {
_docId = docId;
return *this;
}
@@ -160,13 +160,13 @@ public:
* @param docId id of the document we have matched
* @param score a raw score for the matched document
**/
- TermFieldMatchData &setRawScore(uint32_t docId, feature_t score) {
+ TermFieldMatchData &setRawScore(uint32_t docId, feature_t score) noexcept {
resetOnlyDocId(docId);
enableRawScore();
_data._rawScore = score;
return *this;
}
- TermFieldMatchData & enableRawScore() {
+ TermFieldMatchData & enableRawScore() noexcept {
_flags |= RAW_SCORE_FLAG;
return *this;
}
@@ -176,16 +176,16 @@ public:
*
* @return raw score
**/
- feature_t getRawScore() const {
+ feature_t getRawScore() const noexcept {
return __builtin_expect(isRawScore(), true) ? _data._rawScore : 0.0;
}
- void setSubqueries(uint32_t docId, uint64_t subqueries) {
+ void setSubqueries(uint32_t docId, uint64_t subqueries) noexcept {
resetOnlyDocId(docId);
_data._subqueries = subqueries;
}
- uint64_t getSubqueries() const {
+ uint64_t getSubqueries() const noexcept {
if (!empty() || isRawScore()) {
return 0;
}
@@ -197,7 +197,7 @@ public:
*
* @return document id
**/
- uint32_t getDocId() const {
+ uint32_t getDocId() const noexcept {
return _docId;
}
@@ -208,7 +208,7 @@ public:
*
* @return weight
**/
- int32_t getWeight() const {
+ int32_t getWeight() const noexcept {
if (__builtin_expect(_sz == 0, false)) {
return 1;
}
@@ -246,8 +246,8 @@ public:
return FieldPositionsIterator(len != 0 ? len : FieldPositionsIterator::UNKNOWN_LENGTH, begin(), end());
}
- uint16_t getNumOccs() const { return _numOccs; }
- uint16_t getFieldLength() const { return _fieldLength; }
+ uint16_t getNumOccs() const noexcept { return _numOccs; }
+ uint16_t getFieldLength() const noexcept { return _fieldLength; }
void setNumOccs(uint16_t value) { _numOccs = value; }
void setFieldLength(uint16_t value) { _fieldLength = value; }
@@ -256,23 +256,25 @@ public:
* This indicates if this instance is actually used for ranking or not.
* @return true if it is not needed.
*/
- bool isNotNeeded() const { return ((_flags & (UNPACK_NORMAL_FEATURES_FLAG | UNPACK_INTERLEAVED_FEATURES_FLAG)) == 0u); }
+ bool isNotNeeded() const noexcept {
+ return ((_flags & (UNPACK_NORMAL_FEATURES_FLAG | UNPACK_INTERLEAVED_FEATURES_FLAG)) == 0u);
+ }
- bool needs_normal_features() const { return ((_flags & UNPACK_NORMAL_FEATURES_FLAG) != 0u); }
+ bool needs_normal_features() const noexcept { return ((_flags & UNPACK_NORMAL_FEATURES_FLAG) != 0u); }
- bool needs_interleaved_features() const { return ((_flags & UNPACK_INTERLEAVED_FEATURES_FLAG) != 0u); }
+ bool needs_interleaved_features() const noexcept{ return ((_flags & UNPACK_INTERLEAVED_FEATURES_FLAG) != 0u); }
/**
* Tag that this instance is not really used for ranking.
*/
- void tagAsNotNeeded() {
+ void tagAsNotNeeded() noexcept {
_flags &= ~(UNPACK_NORMAL_FEATURES_FLAG | UNPACK_INTERLEAVED_FEATURES_FLAG);
}
/**
* Tag that this instance is used for ranking (normal features)
*/
- void setNeedNormalFeatures(bool needed) {
+ void setNeedNormalFeatures(bool needed) noexcept {
if (needed) {
_flags |= UNPACK_NORMAL_FEATURES_FLAG;
} else {
@@ -283,7 +285,7 @@ public:
/**
* Tag that this instance is used for ranking (interleaved features)
*/
- void setNeedInterleavedFeatures(bool needed) {
+ void setNeedInterleavedFeatures(bool needed) noexcept {
if (needed) {
_flags |= UNPACK_INTERLEAVED_FEATURES_FLAG;
} else {
@@ -297,7 +299,7 @@ public:
*
* @return constant
**/
- static uint32_t invalidId() { return 0xdeadbeefU; }
+ static uint32_t invalidId() noexcept { return 0xdeadbeefU; }
};
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
index 3f6085ef7ff..94d1a4917fd 100644
--- a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
@@ -621,7 +621,7 @@ IntermediateBlueprint::fetchPostings(const ExecuteInfo &execInfo)
double nextHitRate = execInfo.hitRate();
for (size_t i = 0; i < _children.size(); ++i) {
Blueprint & child = *_children[i];
- child.fetchPostings(ExecuteInfo::create(execInfo.isStrict() && inheritStrict(i), nextHitRate));
+ child.fetchPostings(ExecuteInfo::create(execInfo.isStrict() && inheritStrict(i), nextHitRate, execInfo.getDoom()));
nextHitRate = computeNextHitRate(child, nextHitRate);
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp
index 795f5f1424a..4322cafb5c8 100644
--- a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp
@@ -66,7 +66,7 @@ DotProductBlueprint::createFilterSearch(bool strict, FilterConstraint constraint
void
DotProductBlueprint::fetchPostings(const ExecuteInfo &execInfo)
{
- ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo.hitRate());
+ ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo);
for (size_t i = 0; i < _terms.size(); ++i) {
_terms[i]->fetchPostings(childInfo);
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/executeinfo.cpp b/searchlib/src/vespa/searchlib/queryeval/executeinfo.cpp
index 604e20d2262..e5d20f047f5 100644
--- a/searchlib/src/vespa/searchlib/queryeval/executeinfo.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/executeinfo.cpp
@@ -4,17 +4,7 @@
namespace search::queryeval {
-const ExecuteInfo ExecuteInfo::TRUE(true, 1.0);
-const ExecuteInfo ExecuteInfo::FALSE(false, 1.0);
-
-ExecuteInfo
-ExecuteInfo::create(bool strict) {
- return create(strict, 1.0);
-}
-
-ExecuteInfo
-ExecuteInfo::create(bool strict, double hitRate) {
- return ExecuteInfo(strict, hitRate);
-}
+const ExecuteInfo ExecuteInfo::TRUE(true, 1.0, nullptr);
+const ExecuteInfo ExecuteInfo::FALSE(false, 1.0, nullptr);
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/executeinfo.h b/searchlib/src/vespa/searchlib/queryeval/executeinfo.h
index e161b2bdab7..2dd34284bef 100644
--- a/searchlib/src/vespa/searchlib/queryeval/executeinfo.h
+++ b/searchlib/src/vespa/searchlib/queryeval/executeinfo.h
@@ -1,8 +1,9 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-// Copyright 2019 Oath inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
+#include <vespa/vespalib/util/doom.h>
+
namespace search::queryeval {
/**
@@ -11,20 +12,37 @@ namespace search::queryeval {
*/
class ExecuteInfo {
public:
- ExecuteInfo() : ExecuteInfo(false, 1.0) { }
- bool isStrict() const { return _strict; }
- double hitRate() const { return _hitRate; }
+ ExecuteInfo() noexcept : ExecuteInfo(false, 1.0F, nullptr) { }
+ bool isStrict() const noexcept { return _strict; }
+ float hitRate() const noexcept { return _hitRate; }
+ bool soft_doom() const noexcept { return _doom && _doom->soft_doom(); }
+ const vespalib::Doom * getDoom() const { return _doom; }
static const ExecuteInfo TRUE;
static const ExecuteInfo FALSE;
- static ExecuteInfo create(bool strict);
- static ExecuteInfo create(bool strict, double HitRate);
+ static ExecuteInfo create(bool strict, const ExecuteInfo & org) noexcept {
+ return {strict, org._hitRate, org.getDoom()};
+ }
+ static ExecuteInfo create(bool strict, const vespalib::Doom * doom) noexcept {
+ return create(strict, 1.0F, doom);
+ }
+ static ExecuteInfo create(bool strict, float hitRate, const vespalib::Doom * doom) noexcept {
+ return {strict, hitRate, doom};
+ }
+ static ExecuteInfo create(bool strict) noexcept {
+ return create(strict, 1.0F);
+ }
+ static ExecuteInfo create(bool strict, float hitRate) noexcept {
+ return create(strict, hitRate, nullptr);
+ }
private:
- ExecuteInfo(bool strict, double hitRate_in)
- : _hitRate(hitRate_in),
+ ExecuteInfo(bool strict, float hitRate_in, const vespalib::Doom * doom) noexcept
+ : _doom(doom),
+ _hitRate(hitRate_in),
_strict(strict)
{ }
- double _hitRate;
- bool _strict;
+ const vespalib::Doom * _doom;
+ float _hitRate;
+ bool _strict;
};
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
index e9f1b526a87..5280100b5bc 100644
--- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
@@ -28,6 +28,7 @@ SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &child
_children.emplace_back(child);
}
assert((_children.size() == _childMatch.size()) || _childMatch.empty());
+ assert(_children.size() < 0x10000);
}
SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md)
@@ -36,7 +37,6 @@ SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &child
std::unique_ptr<BitVector>
SearchIteratorPack::get_hits(uint32_t begin_id, uint32_t end_id) const {
-
BitVector::UP result = TermwiseHelper::orChildren(_children.begin(), _children.end(), begin_id);
if (! result ) {
result = BitVector::create(begin_id, end_id);
diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
index 907371008d9..ca1e6461533 100644
--- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
+++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
@@ -31,27 +31,25 @@ public:
// TODO: use MultiSearch::Children to pass ownership
SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md);
- uint32_t get_docid(uint32_t ref) const {
+ uint32_t get_docid(uint16_t ref) const {
return _children[ref]->getDocId();
}
- uint32_t seek(uint32_t ref, uint32_t docid) {
+ uint32_t seek(uint16_t ref, uint32_t docid) {
_children[ref]->seek(docid);
return _children[ref]->getDocId();
}
- int32_t get_weight(uint32_t ref, uint32_t docid) {
+ int32_t get_weight(uint16_t ref, uint32_t docid) {
_children[ref]->doUnpack(docid);
return _childMatch[ref]->getWeight();
}
- void unpack(uint32_t ref, uint32_t docid) {
+ void unpack(uint16_t ref, uint32_t docid) {
_children[ref]->doUnpack(docid);
}
- size_t size() const {
- return _children.size();
- }
+ uint16_t size() const { return _children.size(); }
void initRange(uint32_t begin, uint32_t end) {
for (auto & child: _children) {
child->initRange(begin, end);
diff --git a/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.cpp b/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.cpp
index 8b8db0f293a..4eb57eda911 100644
--- a/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.cpp
@@ -11,51 +11,13 @@ namespace search::queryeval {
using vespalib::Trinary;
using vespalib::hwaccelrated::IAccelrated;
+using Meta = MultiBitVectorBase::Meta;
namespace {
-template<typename Update>
-class MultiBitVectorIterator : public MultiBitVectorIteratorBase
-{
-public:
- explicit MultiBitVectorIterator(Children children)
- : MultiBitVectorIteratorBase(std::move(children)),
- _update(),
- _accel(IAccelrated::getAccelerator()),
- _lastWords()
- {
- static_assert(sizeof(_lastWords) == 64, "Lastwords should have 64 byte size");
- static_assert(NumWordsInBatch == 8, "Batch size should be 8 words.");
- memset(_lastWords, 0, sizeof(_lastWords));
- }
-protected:
- void updateLastValue(uint32_t docId) noexcept;
- void strictSeek(uint32_t docId) noexcept;
-private:
- void doSeek(uint32_t docId) override;
- Trinary is_strict() const override { return Trinary::False; }
- bool acceptExtraFilter() const noexcept final { return Update::isAnd(); }
- Update _update;
- const IAccelrated & _accel;
- alignas(64) Word _lastWords[8];
- static constexpr size_t NumWordsInBatch = sizeof(_lastWords) / sizeof(Word);
-};
-
-template<typename Update>
-class MultiBitVectorIteratorStrict final : public MultiBitVectorIterator<Update>
-{
-public:
- explicit MultiBitVectorIteratorStrict(MultiSearch::Children children)
- : MultiBitVectorIterator<Update>(std::move(children))
- { }
-private:
- void doSeek(uint32_t docId) override { this->strictSeek(docId); }
- Trinary is_strict() const override { return Trinary::True; }
-};
-
struct And {
using Word = BitWord::Word;
- void operator () (const IAccelrated & accel, size_t offset, const std::vector<std::pair<const void *, bool>> & src, void *dest) noexcept {
+ void operator () (const IAccelrated & accel, size_t offset, const std::vector<Meta> & src, void *dest) noexcept {
accel.and64(offset, src, dest);
}
static bool isAnd() noexcept { return true; }
@@ -63,19 +25,49 @@ struct And {
struct Or {
using Word = BitWord::Word;
- void operator () (const IAccelrated & accel, size_t offset, const std::vector<std::pair<const void *, bool>> & src, void *dest) noexcept {
+ void operator () (const IAccelrated & accel, size_t offset, const std::vector<Meta> & src, void *dest) noexcept {
accel.or64(offset, src, dest);
}
static bool isAnd() noexcept { return false; }
};
+}
+
+MultiBitVectorBase::MultiBitVectorBase(size_t reserved)
+ : _numDocs(std::numeric_limits<uint32_t>::max()),
+ _lastMaxDocIdLimit(0),
+ _lastMaxDocIdLimitRequireFetch(0),
+ _lastValue(0),
+ _bvs()
+{
+ _bvs.reserve(reserved);
+}
+
+void
+MultiBitVectorBase::addBitVector(Meta bv, uint32_t docIdLimit) {
+ _numDocs = std::min(_numDocs, docIdLimit);
+ _bvs.push_back(bv);
+}
+
+template <typename Update>
+MultiBitVector<Update>::MultiBitVector(size_t reserved)
+ : MultiBitVectorBase(reserved),
+ _update(),
+ _accel(IAccelrated::getAccelerator()),
+ _lastWords()
+{
+ static_assert(sizeof(_lastWords) == 64, "Lastwords should have 64 byte size");
+ static_assert(NumWordsInBatch == 8, "Batch size should be 8 words.");
+ memset(_lastWords, 0, sizeof(_lastWords));
+}
+
template<typename Update>
-void MultiBitVectorIterator<Update>::updateLastValue(uint32_t docId) noexcept
+bool
+MultiBitVector<Update>::updateLastValue(uint32_t docId) noexcept
{
if (docId >= _lastMaxDocIdLimit) {
if (__builtin_expect(docId >= _numDocs, false)) {
- setAtEnd();
- return;
+ return true;
}
const uint32_t index(BitWord::wordNum(docId));
if (docId >= _lastMaxDocIdLimitRequireFetch) {
@@ -86,37 +78,104 @@ void MultiBitVectorIterator<Update>::updateLastValue(uint32_t docId) noexcept
_lastValue = _lastWords[index % NumWordsInBatch];
_lastMaxDocIdLimit = (index + 1) * BitWord::WordLen;
}
+ return false;
}
template<typename Update>
-void
-MultiBitVectorIterator<Update>::doSeek(uint32_t docId)
+uint32_t
+MultiBitVector<Update>::strictSeek(uint32_t docId) noexcept
+{
+ bool atEnd;
+ for (atEnd = updateLastValue(docId), _lastValue = _lastValue & BitWord::checkTab(docId);
+ (_lastValue == 0) && __builtin_expect(! atEnd, true);
+ atEnd = updateLastValue(_lastMaxDocIdLimit));
+ if (__builtin_expect(!atEnd, true)) {
+ return _lastMaxDocIdLimit - BitWord::WordLen + vespalib::Optimized::lsbIdx(_lastValue);
+ }
+ return _numDocs;
+}
+
+template<typename Update>
+bool
+MultiBitVector<Update>::seek(uint32_t docId) noexcept
{
- updateLastValue(docId);
- if (__builtin_expect( ! isAtEnd(), true)) {
+ bool atEnd = updateLastValue(docId);
+ if (__builtin_expect( ! atEnd, true)) {
if (_lastValue & BitWord::mask(docId)) {
- setDocId(docId);
+ return true;
}
}
+ return false;
}
+namespace {
+
template<typename Update>
-void
-MultiBitVectorIterator<Update>::strictSeek(uint32_t docId) noexcept
+class MultiBitVectorIterator : public MultiBitVectorIteratorBase
{
- for (updateLastValue(docId), _lastValue = _lastValue & BitWord::checkTab(docId);
- (_lastValue == 0) && __builtin_expect(! isAtEnd(), true);
- updateLastValue(_lastMaxDocIdLimit));
- if (__builtin_expect(!isAtEnd(), true)) {
- docId = _lastMaxDocIdLimit - BitWord::WordLen + vespalib::Optimized::lsbIdx(_lastValue);
- if (__builtin_expect(docId >= _numDocs, false)) {
- setAtEnd();
+public:
+ explicit MultiBitVectorIterator(Children children)
+ : MultiBitVectorIteratorBase(std::move(children)),
+ _mbv(getChildren().size() + 1)
+ {
+ for (const auto & child : getChildren()) {
+ const auto * bv = static_cast<const BitVectorIterator *>(child.get());
+ _mbv.addBitVector(Meta(bv->getBitValues(), bv->isInverted()), bv->getDocIdLimit());
+ }
+ }
+ void initRange(uint32_t beginId, uint32_t endId) override {
+ MultiBitVectorIteratorBase::initRange(beginId, endId);
+ _mbv.reset();
+ }
+ UP andWith(UP filter, uint32_t estimate) override;
+protected:
+ void doSeek(uint32_t docId) override;
+ Trinary is_strict() const override { return Trinary::False; }
+ bool acceptExtraFilter() const noexcept final { return _mbv.acceptExtraFilter(); }
+ MultiBitVector<Update> _mbv;
+};
+
+template<typename Update>
+class MultiBitVectorIteratorStrict final : public MultiBitVectorIterator<Update>
+{
+public:
+ explicit MultiBitVectorIteratorStrict(MultiSearch::Children children)
+ : MultiBitVectorIterator<Update>(std::move(children))
+ { }
+private:
+ void doSeek(uint32_t docId) override {
+ docId = this->_mbv.strictSeek(docId);
+ if (__builtin_expect(docId >= this->getEndId(), false)) {
+ this->setAtEnd();
} else {
- setDocId(docId);
+ this->setDocId(docId);
}
}
+ Trinary is_strict() const override { return Trinary::True; }
+};
+
+template<typename Update>
+void
+MultiBitVectorIterator<Update>::doSeek(uint32_t docId)
+{
+ if (_mbv.seek(docId)) {
+ setDocId(docId);
+ }
}
+template <typename Update>
+SearchIterator::UP
+MultiBitVectorIterator<Update>::andWith(UP filter, uint32_t estimate)
+{
+ (void) estimate;
+ if (filter->isBitVector() && acceptExtraFilter()) {
+ const auto & bv = static_cast<const BitVectorIterator &>(*filter);
+ _mbv.addBitVector(Meta(bv.getBitValues(), bv.isInverted()), bv.getDocIdLimit());
+ insert(getChildren().size(), std::move(filter));
+ _mbv.reset();
+ }
+ return filter;
+}
using AndBVIterator = MultiBitVectorIterator<And>;
using AndBVIteratorStrict = MultiBitVectorIteratorStrict<And>;
@@ -148,20 +207,8 @@ bool canOptimize(const MultiSearch & s) {
}
MultiBitVectorIteratorBase::MultiBitVectorIteratorBase(Children children) :
- MultiSearch(std::move(children)),
- _numDocs(std::numeric_limits<unsigned int>::max()),
- _lastMaxDocIdLimit(0),
- _lastMaxDocIdLimitRequireFetch(0),
- _lastValue(0),
- _bvs()
-{
- _bvs.reserve(getChildren().size());
- for (const auto & child : getChildren()) {
- const auto * bv = static_cast<const BitVectorIterator *>(child.get());
- _bvs.emplace_back(bv->getBitValues(), bv->isInverted());
- _numDocs = std::min(_numDocs, bv->getDocIdLimit());
- }
-}
+ MultiSearch(std::move(children))
+{ }
MultiBitVectorIteratorBase::~MultiBitVectorIteratorBase() = default;
@@ -169,22 +216,6 @@ void
MultiBitVectorIteratorBase::initRange(uint32_t beginId, uint32_t endId)
{
MultiSearch::initRange(beginId, endId);
- _lastMaxDocIdLimit = 0;
- _lastMaxDocIdLimitRequireFetch = 0;
-}
-
-SearchIterator::UP
-MultiBitVectorIteratorBase::andWith(UP filter, uint32_t estimate)
-{
- (void) estimate;
- if (filter->isBitVector() && acceptExtraFilter()) {
- const auto & bv = static_cast<const BitVectorIterator &>(*filter);
- _bvs.emplace_back(bv.getBitValues(), bv.isInverted());
- insert(getChildren().size(), std::move(filter));
- _lastMaxDocIdLimit = 0; // force reload
- _lastMaxDocIdLimitRequireFetch = 0;
- }
- return filter;
}
void
diff --git a/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.h b/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.h
index d99e439af0b..5c3d17c6786 100644
--- a/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.h
+++ b/searchlib/src/vespa/searchlib/queryeval/multibitvectoriterator.h
@@ -6,8 +6,45 @@
#include "unpackinfo.h"
#include <vespa/searchlib/common/bitword.h>
+namespace vespalib::hwaccelrated { class IAccelrated; }
+
namespace search::queryeval {
+class MultiBitVectorBase {
+public:
+ using Meta = std::pair<const void *, bool>;
+ MultiBitVectorBase(size_t reserved);
+ using Word = BitWord::Word;
+ void reset() {
+ _lastMaxDocIdLimit = 0;
+ _lastMaxDocIdLimitRequireFetch = 0;
+ }
+ void addBitVector(Meta bv, uint32_t docIdLimit);
+protected:
+ uint32_t _numDocs;
+ uint32_t _lastMaxDocIdLimit; // next documentid requiring recomputation.
+ uint32_t _lastMaxDocIdLimitRequireFetch;
+ Word _lastValue; // Last value computed
+ std::vector<Meta> _bvs;
+};
+
+template <typename Update>
+class MultiBitVector : public MultiBitVectorBase {
+public:
+ explicit MultiBitVector(size_t reserved);
+ uint32_t strictSeek(uint32_t docId) noexcept;
+ bool seek(uint32_t docId) noexcept;
+ bool acceptExtraFilter() const noexcept { return Update::isAnd(); }
+private:
+ bool updateLastValue(uint32_t docId) noexcept;
+ using IAccelrated = vespalib::hwaccelrated::IAccelrated;
+
+ Update _update;
+ const IAccelrated & _accel;
+ alignas(64) Word _lastWords[8];
+ static constexpr size_t NumWordsInBatch = sizeof(_lastWords) / sizeof(Word);
+};
+
class MultiBitVectorIteratorBase : public MultiSearch
{
public:
@@ -20,18 +57,9 @@ public:
*/
static SearchIterator::UP optimize(SearchIterator::UP parent);
protected:
- using Word = BitWord::Word;
- MultiBitVectorIteratorBase(Children children);
- using MetaWord = std::pair<const void *, bool>;
-
- uint32_t _numDocs;
- uint32_t _lastMaxDocIdLimit; // next documentid requiring recomputation.
- uint32_t _lastMaxDocIdLimitRequireFetch;
- Word _lastValue; // Last value computed
- std::vector<MetaWord> _bvs;
+ explicit MultiBitVectorIteratorBase(Children children);
private:
virtual bool acceptExtraFilter() const noexcept = 0;
- UP andWith(UP filter, uint32_t estimate) override;
void doUnpack(uint32_t docid) override;
static SearchIterator::UP optimizeMultiSearch(SearchIterator::UP parent);
diff --git a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp
index 9c3910b20f9..16461487525 100644
--- a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp
@@ -57,7 +57,7 @@ void
SameElementBlueprint::fetchPostings(const ExecuteInfo &execInfo)
{
for (size_t i = 0; i < _terms.size(); ++i) {
- _terms[i]->fetchPostings(ExecuteInfo::create(execInfo.isStrict() && (i == 0), execInfo.hitRate()));
+ _terms[i]->fetchPostings(ExecuteInfo::create(execInfo.isStrict() && (i == 0), execInfo));
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp
index 48a09f099a6..eb6241a99d5 100644
--- a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp
@@ -4,7 +4,6 @@
#include "wand_parts.h"
#include "parallel_weak_and_search.h"
#include <vespa/searchlib/queryeval/field_spec.hpp>
-#include <vespa/searchlib/queryeval/emptysearch.h>
#include <vespa/searchlib/queryeval/searchiterator.h>
#include <vespa/searchlib/fef/termfieldmatchdata.h>
#include <vespa/vespalib/objects/visit.hpp>
@@ -77,10 +76,10 @@ ParallelWeakAndBlueprint::createLeafSearch(const search::fef::TermFieldMatchData
const State &childState = _terms[i]->getState();
assert(childState.numFields() == 1);
// TODO: pass ownership with unique_ptr
- terms.push_back(wand::Term(_terms[i]->createSearch(*childrenMatchData, true).release(),
- _weights[i],
- childState.estimate().estHits,
- childState.field(0).resolve(*childrenMatchData)));
+ terms.emplace_back(_terms[i]->createSearch(*childrenMatchData, true).release(),
+ _weights[i],
+ childState.estimate().estHits,
+ childState.field(0).resolve(*childrenMatchData));
}
return SearchIterator::UP
(ParallelWeakAndSearch::create(terms,
@@ -101,9 +100,9 @@ ParallelWeakAndBlueprint::createFilterSearch(bool strict, FilterConstraint const
void
ParallelWeakAndBlueprint::fetchPostings(const ExecuteInfo & execInfo)
{
- ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo.hitRate());
- for (size_t i = 0; i < _terms.size(); ++i) {
- _terms[i]->fetchPostings(childInfo);
+ ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo);
+ for (const auto & _term : _terms) {
+ _term->fetchPostings(childInfo);
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
index 4e06f170253..55009e714b9 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
@@ -33,10 +33,8 @@ WeightedSetTermMatchingElementsSearch::WeightedSetTermMatchingElementsSearch(con
_search()
{
_tfmda.add(&_tfmd);
- auto generic_search = bp.createLeafSearch(_tfmda, false);
- auto weighted_set_term_search = dynamic_cast<WeightedSetTermSearch *>(generic_search.get());
- generic_search.release();
- _search.reset(weighted_set_term_search);
+ _search.reset(static_cast<WeightedSetTermSearch *>(bp.createLeafSearch(_tfmda, false).release()));
+
}
WeightedSetTermMatchingElementsSearch::~WeightedSetTermMatchingElementsSearch() = default;
@@ -94,7 +92,6 @@ WeightedSetTermBlueprint::addTerm(Blueprint::UP term, int32_t weight, HitEstimat
_terms.push_back(std::move(term));
}
-
SearchIterator::UP
WeightedSetTermBlueprint::createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const
{
@@ -120,16 +117,16 @@ WeightedSetTermBlueprint::create_matching_elements_search(const MatchingElements
if (fields.has_field(_children_field.getName())) {
return std::make_unique<WeightedSetTermMatchingElementsSearch>(*this, _children_field.getName(), _terms);
} else {
- return std::unique_ptr<MatchingElementsSearch>();
+ return {};
}
}
void
WeightedSetTermBlueprint::fetchPostings(const ExecuteInfo &execInfo)
{
- ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo.hitRate());
- for (size_t i = 0; i < _terms.size(); ++i) {
- _terms[i]->fetchPostings(childInfo);
+ ExecuteInfo childInfo = ExecuteInfo::create(true, execInfo);
+ for (const auto & _term : _terms) {
+ _term->fetchPostings(childInfo);
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.h
index 0e3c82444d7..9c8d6d88329 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.h
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.h
@@ -18,7 +18,7 @@ class WeightedSetTermBlueprint : public ComplexLeafBlueprint
std::vector<Blueprint::UP> _terms;
public:
- WeightedSetTermBlueprint(const FieldSpec &field);
+ explicit WeightedSetTermBlueprint(const FieldSpec &field);
WeightedSetTermBlueprint(const WeightedSetTermBlueprint &) = delete;
WeightedSetTermBlueprint &operator=(const WeightedSetTermBlueprint &) = delete;
~WeightedSetTermBlueprint() override;
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
index ee3978705cf..9568c02cf32 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
@@ -2,6 +2,7 @@
#include "weighted_set_term_search.h"
#include <vespa/searchlib/common/bitvector.h>
+#include <vespa/searchlib/attribute/document_weight_or_filter_search.h>
#include <vespa/vespalib/objects/visit.h>
#include <vespa/searchcommon/attribute/i_search_context.h>
@@ -21,7 +22,7 @@ private:
struct CmpDocId {
const uint32_t *termPos;
- CmpDocId(const uint32_t *tp) : termPos(tp) {}
+ explicit CmpDocId(const uint32_t *tp) : termPos(tp) {}
bool operator()(const ref_t &a, const ref_t &b) const {
return (termPos[a] < termPos[b]);
}
@@ -29,7 +30,7 @@ private:
struct CmpWeight {
const int32_t *weight;
- CmpWeight(const int32_t *w) : weight(w) {}
+ explicit CmpWeight(const int32_t *w) : weight(w) {}
bool operator()(const ref_t &a, const ref_t &b) const {
return (weight[a] > weight[b]);
}
@@ -45,7 +46,7 @@ private:
ref_t *_data_stash;
ref_t *_data_end;
IteratorPack _children;
- bool _field_is_filter;
+ bool _need_match_data;
void seek_child(ref_t child, uint32_t docId) {
_termPos[child] = _children.seek(child, docId);
@@ -61,7 +62,7 @@ private:
}
public:
- WeightedSetTermSearchImpl(search::fef::TermFieldMatchData &tmd,
+ WeightedSetTermSearchImpl(fef::TermFieldMatchData &tmd,
bool field_is_filter,
const std::vector<int32_t> &weights,
IteratorPack &&iteratorPack)
@@ -75,7 +76,7 @@ public:
_data_stash(nullptr),
_data_end(nullptr),
_children(std::move(iteratorPack)),
- _field_is_filter(field_is_filter)
+ _need_match_data(!field_is_filter && !_tmd.isNotNeeded())
{
HEAP::require_left_heap();
assert(_children.size() > 0);
@@ -86,7 +87,7 @@ public:
}
_data_begin = &_data_space[0];
_data_end = _data_begin + _data_space.size();
- if (!_field_is_filter && !_tmd.isNotNeeded()) {
+ if (_need_match_data) {
_tmd.reservePositions(_children.size());
}
}
@@ -112,7 +113,7 @@ public:
}
void doUnpack(uint32_t docId) override {
- if (!_field_is_filter && !_tmd.isNotNeeded()) {
+ if (_need_match_data) {
_tmd.reset(docId);
pop_matching_children(docId);
std::sort(_data_stash, _data_end, _cmpWeight);
@@ -171,6 +172,10 @@ WeightedSetTermSearch::create(const std::vector<SearchIterator *> &children,
using ArrayHeapImpl = WeightedSetTermSearchImpl<vespalib::LeftArrayHeap, SearchIteratorPack>;
using HeapImpl = WeightedSetTermSearchImpl<vespalib::LeftHeap, SearchIteratorPack>;
+ if (tmd.isNotNeeded()) {
+ return attribute::DocumentWeightOrFilterSearch::create(children, std::move(match_data));
+ }
+
if (children.size() < 128) {
return SearchIterator::UP(new ArrayHeapImpl(tmd, field_is_filter, weights, SearchIteratorPack(children, std::move(match_data))));
}
@@ -180,7 +185,7 @@ WeightedSetTermSearch::create(const std::vector<SearchIterator *> &children,
//-----------------------------------------------------------------------------
SearchIterator::UP
-WeightedSetTermSearch::create(search::fef::TermFieldMatchData &tmd,
+WeightedSetTermSearch::create(fef::TermFieldMatchData &tmd,
bool field_is_filter,
const std::vector<int32_t> &weights,
std::vector<DocumentWeightIterator> &&iterators)
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.h b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.h
index e3e12c27f28..b30d3bc3301 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.h
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.h
@@ -10,12 +10,9 @@
#include <memory>
#include <vector>
-namespace search {
-namespace fef {
-class TermFieldMatchData;
-} // namespace fef
+namespace search::fef { class TermFieldMatchData; }
-namespace queryeval {
+namespace search::queryeval {
class Blueprint;
@@ -26,7 +23,7 @@ class Blueprint;
class WeightedSetTermSearch : public SearchIterator
{
protected:
- WeightedSetTermSearch() {}
+ WeightedSetTermSearch() = default;
public:
// TODO: pass ownership with unique_ptr
@@ -47,6 +44,4 @@ public:
virtual void find_matching_elements(uint32_t docid, const std::vector<std::unique_ptr<Blueprint>> &child_blueprints, std::vector<uint32_t> &dst) = 0;
};
-} // namespace search::queryeval
-} // namespace search
-
+}
diff --git a/storage/src/vespa/storage/storageserver/CMakeLists.txt b/storage/src/vespa/storage/storageserver/CMakeLists.txt
index 009f8170669..1ef670f96ac 100644
--- a/storage/src/vespa/storage/storageserver/CMakeLists.txt
+++ b/storage/src/vespa/storage/storageserver/CMakeLists.txt
@@ -14,7 +14,6 @@ vespa_add_library(storage_storageserver OBJECT
documentapiconverter.cpp
fnet_metrics_wrapper.cpp
mergethrottler.cpp
- opslogger.cpp
priorityconverter.cpp
rpcrequestwrapper.cpp
service_layer_error_listener.cpp
diff --git a/storage/src/vespa/storage/storageserver/distributornode.cpp b/storage/src/vespa/storage/storageserver/distributornode.cpp
index ab80381f5d4..431dd89b613 100644
--- a/storage/src/vespa/storage/storageserver/distributornode.cpp
+++ b/storage/src/vespa/storage/storageserver/distributornode.cpp
@@ -3,7 +3,6 @@
#include "distributornode.h"
#include "bouncer.h"
#include "communicationmanager.h"
-#include "opslogger.h"
#include "statemanager.h"
#include <vespa/storage/common/hostreporter/hostinfo.h>
#include <vespa/storage/common/i_storage_chain_builder.h>
@@ -96,7 +95,6 @@ DistributorNode::createChain(IStorageChainBuilder &builder)
std::unique_ptr<StateManager> stateManager(releaseStateManager());
builder.add(std::make_unique<Bouncer>(dcr, _configUri));
- builder.add(std::make_unique<OpsLogger>(dcr, _configUri));
// Distributor instance registers a host info reporter with the state
// manager, which is safe since the lifetime of said state manager
// extends to the end of the process.
diff --git a/storage/src/vespa/storage/storageserver/opslogger.cpp b/storage/src/vespa/storage/storageserver/opslogger.cpp
deleted file mode 100644
index dcf7ddf4a92..00000000000
--- a/storage/src/vespa/storage/storageserver/opslogger.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "opslogger.h"
-#include <vespa/storageframework/generic/clock/clock.h>
-#include <vespa/storageapi/message/persistence.h>
-#include <vespa/config/helper/configfetcher.hpp>
-#include <vespa/config/subscription/configuri.h>
-#include <sstream>
-
-#include <vespa/log/log.h>
-LOG_SETUP(".operationslogger");
-
-namespace storage {
-
-OpsLogger::OpsLogger(StorageComponentRegister& compReg,
- const config::ConfigUri & configUri)
- : StorageLink("Operations logger"),
- _lock(),
- _fileName(),
- _targetFile(nullptr),
- _component(compReg, "opslogger"),
- _configFetcher(std::make_unique<config::ConfigFetcher>(configUri.getContext()))
-{
- _configFetcher->subscribe<vespa::config::content::core::StorOpsloggerConfig>(configUri.getConfigId(), this);
- _configFetcher->start();
-}
-
-OpsLogger::~OpsLogger()
-{
- closeNextLink();
- LOG(debug, "Deleting link %s.", toString().c_str());
-
- if (_targetFile) {
- fclose(_targetFile);
- }
-}
-
-void
-OpsLogger::onClose()
-{
- // Avoid getting config during shutdown
- _configFetcher->close();
-}
-
-void
-OpsLogger::configure(std::unique_ptr<vespa::config::content::core::StorOpsloggerConfig> config)
-{
- std::lock_guard lock(_lock);
- // If no change in state, ignore
- if (config->targetfile == _fileName) return;
- // If a change we need to close old handle if open
- if (_targetFile != nullptr) {
- fclose(_targetFile);
- _targetFile = nullptr;
- }
- // Set up the new operations log file
- _fileName = config->targetfile;
- if (_fileName.length() > 0) {
- _targetFile = fopen(_fileName.c_str(), "a+b");
-
- if (!_targetFile) {
- LOG(warning, "Could not open file %s for operations logging",
- _fileName.c_str());
- }
- }
-}
-
-void
-OpsLogger::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- (void) verbose; (void) indent;
- out << "OpsLogger()";
-}
-
-bool
-OpsLogger::onPutReply(const std::shared_ptr<api::PutReply>& msg)
-{
- if (_targetFile == nullptr) return false;
- std::ostringstream ost;
- ost << vespalib::to_string(_component.getClock().getSystemTime())
- << "\tPUT\t" << msg->getDocumentId() << "\t"
- << msg->getResult() << "\n";
- {
- std::lock_guard lock(_lock);
- if (_targetFile == nullptr) return false;
- fwrite(ost.str().c_str(), ost.str().length(), 1, _targetFile);
- fflush(_targetFile);
- }
- return false;
-}
-
-bool
-OpsLogger::onUpdateReply(const std::shared_ptr<api::UpdateReply>& msg)
-{
- if (_targetFile == nullptr) return false;
- std::ostringstream ost;
- ost << vespalib::to_string(_component.getClock().getSystemTime())
- << "\tUPDATE\t" << msg->getDocumentId() << "\t"
- << msg->getResult() << "\n";
- {
- std::lock_guard lock(_lock);
- if (_targetFile == nullptr) return false;
- fwrite(ost.str().c_str(), ost.str().length(), 1, _targetFile);
- fflush(_targetFile);
- }
- return false;
-}
-
-bool
-OpsLogger::onRemoveReply(const std::shared_ptr<api::RemoveReply>& msg)
-{
- if (_targetFile == nullptr) return false;
- std::ostringstream ost;
- ost << vespalib::to_string(_component.getClock().getSystemTime())
- << "\tREMOVE\t" << msg->getDocumentId() << "\t"
- << msg->getResult() << "\n";
- {
- std::lock_guard lock(_lock);
- if (_targetFile == nullptr) return false;
- fwrite(ost.str().c_str(), ost.str().length(), 1, _targetFile);
- fflush(_targetFile);
- }
- return false;
-}
-
-bool
-OpsLogger::onGetReply(const std::shared_ptr<api::GetReply>& msg)
-{
- if (_targetFile == nullptr) return false;
- std::ostringstream ost;
- ost << vespalib::to_string(_component.getClock().getSystemTime())
- << "\tGET\t" << msg->getDocumentId() << "\t"
- << msg->getResult() << "\n";
- {
- std::lock_guard lock(_lock);
- if (_targetFile == nullptr) return false;
- fwrite(ost.str().c_str(), ost.str().length(), 1, _targetFile);
- fflush(_targetFile);
- }
- return false;
-}
-
-} // storage
diff --git a/storage/src/vespa/storage/storageserver/opslogger.h b/storage/src/vespa/storage/storageserver/opslogger.h
deleted file mode 100644
index 039cb72969e..00000000000
--- a/storage/src/vespa/storage/storageserver/opslogger.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-/**
- * \class storage::OpsLogger
- *
- * \brief Storage link that can be configured to log all storage operations to
- * a file.
-*/
-#pragma once
-
-#include <vespa/storage/common/storagelink.h>
-#include <vespa/storage/common/storagecomponent.h>
-#include <vespa/storageapi/messageapi/storagemessage.h>
-#include <vespa/storageapi/message/state.h>
-#include <vespa/storage/config/config-stor-opslogger.h>
-#include <vespa/config/helper/ifetchercallback.h>
-
-namespace config {
- class ConfigUri;
- class ConfigFetcher;
-}
-
-namespace storage {
-
-class OpsLogger : public StorageLink,
- public config::IFetcherCallback<vespa::config::content::core::StorOpsloggerConfig> {
-public:
- explicit OpsLogger(StorageComponentRegister&,
- const config::ConfigUri & configUri);
- ~OpsLogger() override;
-
- void onClose() override;
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- bool onPutReply(const std::shared_ptr<api::PutReply>& msg) override;
- bool onUpdateReply(const std::shared_ptr<api::UpdateReply>& msg) override;
- bool onRemoveReply(const std::shared_ptr<api::RemoveReply>& msg) override;
- bool onGetReply(const std::shared_ptr<api::GetReply>& msg) override;
-
- /** Ignore all replies on the way down the storage chain. */
- bool onDown(const std::shared_ptr<api::StorageMessage>&) override { return false; };
- void configure(std::unique_ptr<vespa::config::content::core::StorOpsloggerConfig> config) override;
-private:
- std::mutex _lock;
- std::string _fileName;
- FILE * _targetFile;
- framework::Component _component;
-
- std::unique_ptr<config::ConfigFetcher> _configFetcher;
-};
-
-}
diff --git a/storage/src/vespa/storage/storageserver/servicelayernode.cpp b/storage/src/vespa/storage/storageserver/servicelayernode.cpp
index 65615bea2dd..846d6ed09bf 100644
--- a/storage/src/vespa/storage/storageserver/servicelayernode.cpp
+++ b/storage/src/vespa/storage/storageserver/servicelayernode.cpp
@@ -5,7 +5,6 @@
#include "communicationmanager.h"
#include "changedbucketownershiphandler.h"
#include "mergethrottler.h"
-#include "opslogger.h"
#include "statemanager.h"
#include "priorityconverter.h"
#include "service_layer_error_listener.h"
@@ -167,7 +166,6 @@ ServiceLayerNode::createChain(IStorageChainBuilder &builder)
_communicationManager = communication_manager.get();
builder.add(std::move(communication_manager));
builder.add(std::make_unique<Bouncer>(compReg, _configUri));
- builder.add(std::make_unique<OpsLogger>(compReg, _configUri));
auto merge_throttler_up = std::make_unique<MergeThrottler>(_configUri, compReg);
auto merge_throttler = merge_throttler_up.get();
builder.add(std::move(merge_throttler_up));
diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
index 2972ea7745e..3ae0923aef0 100644
--- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
+++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
@@ -1,211 +1,206 @@
# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#[non-test]
-# Contains dependencies that are not used exclusively in 'test' scope
-ai.djl:api:0.23.0
ai.djl.huggingface:tokenizers:0.23.0
-aopalliance:aopalliance:1.0
+ai.djl:api:0.23.0
+aopalliance:aopalliance:${aopalliance.vespa.version}
backport-util-concurrent:backport-util-concurrent:3.1
ch.qos.logback:logback-classic:1.2.10
ch.qos.logback:logback-core:1.2.10
classworlds:classworlds:1.1-alpha-2
-com.amazonaws:aws-java-sdk-core:1.12.540
-com.amazonaws:aws-java-sdk-kms:1.12.540
-com.amazonaws:aws-java-sdk-s3:1.12.540
-com.amazonaws:aws-java-sdk-ssm:1.12.540
-com.amazonaws:aws-java-sdk-sts:1.12.540
-com.amazonaws:jmespath-java:1.12.540
-com.auth0:java-jwt:4.4.0
-com.fasterxml.jackson.core:jackson-annotations:2.15.2
-com.fasterxml.jackson.core:jackson-core:2.15.2
-com.fasterxml.jackson.core:jackson-databind:2.15.2
-com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.15.2
-com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.2
-com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2
-com.github.luben:zstd-jni:1.5.5-5
+com.amazonaws:aws-java-sdk-core:${aws-sdk.vespa.version}
+com.amazonaws:aws-java-sdk-kms:${aws-sdk.vespa.version}
+com.amazonaws:aws-java-sdk-s3:${aws-sdk.vespa.version}
+com.amazonaws:aws-java-sdk-ssm:${aws-sdk.vespa.version}
+com.amazonaws:aws-java-sdk-sts:${aws-sdk.vespa.version}
+com.amazonaws:jmespath-java:${aws-sdk.vespa.version}
+com.auth0:java-jwt:${java-jwt.vespa.version}
+com.fasterxml.jackson.core:jackson-annotations:${jackson2.vespa.version}
+com.fasterxml.jackson.core:jackson-core:${jackson2.vespa.version}
+com.fasterxml.jackson.core:jackson-databind:${jackson-databind.vespa.version}
+com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${jackson2.vespa.version}
+com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jackson2.vespa.version}
+com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jackson2.vespa.version}
+com.github.luben:zstd-jni:${luben.zstd.vespa.version}
com.github.spotbugs:spotbugs-annotations:3.1.9
-com.google.code.findbugs:jsr305:3.0.2
-com.google.errorprone:error_prone_annotations:2.21.1
+com.google.code.findbugs:jsr305:${findbugs.vespa.version}
+com.google.errorprone:error_prone_annotations:${error-prone-annotations.vespa.version}
com.google.guava:failureaccess:1.0.1
-com.google.guava:guava:32.1.2-jre
-com.google.inject:guice:6.0.0
+com.google.guava:guava:${guava.vespa.version}
+com.google.inject:guice:${guice.vespa.version}
com.google.j2objc:j2objc-annotations:2.8
-com.google.protobuf:protobuf-java:3.24.3
-com.ibm.icu:icu4j:73.2
-com.microsoft.onnxruntime:onnxruntime:1.15.1
-com.sun.activation:javax.activation:1.2.0
+com.google.jimfs:jimfs:${jimfs.vespa.version}
+com.google.protobuf:protobuf-java:${protobuf.vespa.version}
+com.ibm.icu:icu4j:${icu4j.vespa.version}
+com.microsoft.onnxruntime:onnxruntime:${onnxruntime.vespa.version}
+com.sun.activation:javax.activation:${properties-maven-plugin.vespa.version}
com.sun.istack:istack-commons-runtime:4.1.2
-com.sun.xml.bind:jaxb-core:2.3.0.1
-com.sun.xml.bind:jaxb-impl:2.3.0
+com.sun.xml.bind:jaxb-core:${jaxb-core.vespa.version}
+com.sun.xml.bind:jaxb-impl:${jaxb-impl.vespa.version}
com.thaiopensource:jing:20091111
-com.yahoo.athenz:athenz-auth-core:1.11.42
-com.yahoo.athenz:athenz-client-common:1.11.42
-com.yahoo.athenz:athenz-zms-core:1.11.42
-com.yahoo.athenz:athenz-zpe-java-client:1.11.42
-com.yahoo.athenz:athenz-zts-core:1.11.42
+com.yahoo.athenz:athenz-auth-core:${athenz.vespa.version}
+com.yahoo.athenz:athenz-client-common:${athenz.vespa.version}
+com.yahoo.athenz:athenz-zms-core:${athenz.vespa.version}
+com.yahoo.athenz:athenz-zpe-java-client:${athenz.vespa.version}
+com.yahoo.athenz:athenz-zts-core:${athenz.vespa.version}
com.yahoo.rdl:rdl-java:1.5.4
commons-cli:commons-cli:1.5.0
-commons-codec:commons-codec:1.16.0
+commons-codec:commons-codec:${commons-codec.vespa.version}
commons-fileupload:commons-fileupload:1.5
-commons-io:commons-io:2.13.0
-commons-logging:commons-logging:1.2
-io.airlift:airline:0.9
-io.dropwizard.metrics:metrics-core:4.2.19
-io.jsonwebtoken:jjwt-api:0.11.5
-io.jsonwebtoken:jjwt-impl:0.11.5
-io.jsonwebtoken:jjwt-jackson:0.11.5
-io.netty:netty-buffer:4.1.98.Final
-io.netty:netty-codec:4.1.98.Final
-io.netty:netty-common:4.1.98.Final
-io.netty:netty-handler:4.1.98.Final
-io.netty:netty-resolver:4.1.98.Final
-io.netty:netty-tcnative:2.0.61.Final
-io.netty:netty-tcnative-classes:2.0.61.Final
-io.netty:netty-transport:4.1.98.Final
-io.netty:netty-transport-classes-epoll:4.1.98.Final
-io.netty:netty-transport-native-epoll:4.1.98.Final
-io.netty:netty-transport-native-unix-common:4.1.98.Final
-io.prometheus:simpleclient:0.16.0
-io.prometheus:simpleclient_common:0.16.0
-io.prometheus:simpleclient_tracer_common:0.16.0
-io.prometheus:simpleclient_tracer_otel:0.16.0
-io.prometheus:simpleclient_tracer_otel_agent:0.16.0
-jakarta.inject:jakarta.inject-api:2.0.1
-javax.activation:javax.activation-api:1.2.0
-javax.annotation:javax.annotation-api:1.2
-javax.inject:javax.inject:1
-javax.servlet:javax.servlet-api:3.1.0
-javax.ws.rs:javax.ws.rs-api:2.1.1
-javax.xml.bind:jaxb-api:2.3.1
-joda-time:joda-time:2.12.5
-junit:junit:4.13.2
-net.java.dev.jna:jna:5.13.0
-net.openhft:zero-allocation-hashing:0.16
-org.antlr:antlr-runtime:3.5.3
-org.antlr:antlr4-runtime:4.13.1
-org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.6
-org.apache.commons:commons-compress:1.24.0
-org.apache.commons:commons-csv:1.10.0
-org.apache.commons:commons-exec:1.3
-org.apache.commons:commons-lang3:3.13.0
-org.apache.commons:commons-math3:3.6.1
-org.apache.curator:curator-client:5.5.0
-org.apache.curator:curator-framework:5.5.0
-org.apache.curator:curator-recipes:5.5.0
-org.apache.felix:org.apache.felix.framework:7.0.5
-org.apache.felix:org.apache.felix.log:1.3.0
-org.apache.httpcomponents:httpclient:4.5.14
-org.apache.httpcomponents:httpcore:4.4.16
-org.apache.httpcomponents:httpmime:4.5.14
-org.apache.httpcomponents.client5:httpclient5:5.2.1
-org.apache.httpcomponents.core5:httpcore5:5.2.3
-org.apache.httpcomponents.core5:httpcore5-h2:5.2.3
-org.apache.lucene:lucene-analysis-common:9.7.0
-org.apache.lucene:lucene-core:9.7.0
-org.apache.maven:maven-archiver:3.6.1
-org.apache.maven:maven-artifact:3.9.4
+commons-io:commons-io:${commons-io.vespa.version}
+commons-logging:commons-logging:${commons-logging.vespa.version}
+io.airlift:airline:${airline.vespa.version}
+io.dropwizard.metrics:metrics-core:${dropwizard.metrics.vespa.version}
+io.jsonwebtoken:jjwt-api:${java-jjwt.vespa.version}
+io.jsonwebtoken:jjwt-impl:${java-jjwt.vespa.version}
+io.jsonwebtoken:jjwt-jackson:${java-jjwt.vespa.version}
+io.netty:netty-buffer:${netty.vespa.version}
+io.netty:netty-codec:${netty.vespa.version}
+io.netty:netty-common:${netty.vespa.version}
+io.netty:netty-handler:${netty.vespa.version}
+io.netty:netty-resolver:${netty.vespa.version}
+io.netty:netty-tcnative-classes:${netty-tcnative.vespa.version}
+io.netty:netty-tcnative:${netty-tcnative.vespa.version}
+io.netty:netty-transport-classes-epoll:${netty.vespa.version}
+io.netty:netty-transport-native-epoll:${netty.vespa.version}
+io.netty:netty-transport-native-unix-common:${netty.vespa.version}
+io.netty:netty-transport:${netty.vespa.version}
+io.prometheus:simpleclient:${prometheus.client.vespa.version}
+io.prometheus:simpleclient_common:${prometheus.client.vespa.version}
+io.prometheus:simpleclient_tracer_common:${prometheus.client.vespa.version}
+io.prometheus:simpleclient_tracer_otel:${prometheus.client.vespa.version}
+io.prometheus:simpleclient_tracer_otel_agent:${prometheus.client.vespa.version}
+jakarta.inject:jakarta.inject-api:${jakarta.inject.vespa.version}
+javax.activation:javax.activation-api:${properties-maven-plugin.vespa.version}
+javax.annotation:javax.annotation-api:${commons-logging.vespa.version}
+javax.inject:javax.inject:${javax.inject.vespa.version}
+javax.servlet:javax.servlet-api:${javax.servlet-api.vespa.version}
+javax.ws.rs:javax.ws.rs-api:${javax.ws.rs-api.vespa.version}
+javax.xml.bind:jaxb-api:${jaxb-api.vespa.version}
+joda-time:joda-time:${joda-time.vespa.version}
+junit:junit:${junit4.vespa.version}
+net.bytebuddy:byte-buddy-agent:${byte-buddy.vespa.version}
+net.bytebuddy:byte-buddy:${byte-buddy.vespa.version}
+net.java.dev.jna:jna:${jna.vespa.version}
+net.openhft:zero-allocation-hashing:${zero-allocation-hashing.vespa.version}
+org.antlr:antlr-runtime:${antlr.vespa.version}
+org.antlr:antlr4-runtime:${antlr4.vespa.version}
+org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:${spifly.vespa.version}
+org.apache.commons:commons-compress:${commons-compress.vespa.version}
+org.apache.commons:commons-csv:${commons-csv.vespa.version}
+org.apache.commons:commons-exec:${commons-exec.vespa.version}
+org.apache.commons:commons-lang3:${commons-lang3.vespa.version}
+org.apache.commons:commons-math3:${commons.math3.vespa.version}
+org.apache.curator:curator-client:${curator.vespa.version}
+org.apache.curator:curator-framework:${curator.vespa.version}
+org.apache.curator:curator-recipes:${curator.vespa.version}
+org.apache.curator:curator-test:${curator.vespa.version}
+org.apache.felix:org.apache.felix.framework:${felix.vespa.version}
+org.apache.felix:org.apache.felix.log:${felix.log.vespa.version}
+org.apache.httpcomponents.client5:httpclient5:${apache.httpclient5.vespa.version}
+org.apache.httpcomponents.core5:httpcore5-h2:${apache.httpcore5.vespa.version}
+org.apache.httpcomponents.core5:httpcore5:${apache.httpcore5.vespa.version}
+org.apache.httpcomponents:httpclient:${apache.httpclient.vespa.version}
+org.apache.httpcomponents:httpcore:${apache.httpcore.vespa.version}
+org.apache.httpcomponents:httpmime:${apache.httpclient.vespa.version}
+org.apache.lucene:lucene-analysis-common:${lucene.vespa.version}
+org.apache.lucene:lucene-core:${lucene.vespa.version}
+org.apache.maven.plugin-tools:maven-plugin-annotations:${maven-plugin-tools.vespa.version}
+org.apache.maven.plugins:maven-jar-plugin:${maven-jar-plugin.vespa.version}
+org.apache.maven.shared:file-management:3.1.0
+org.apache.maven.wagon:wagon-provider-api:${maven-wagon.vespa.version}
+org.apache.maven:maven-archiver:${maven-archiver.vespa.version}
org.apache.maven:maven-artifact-manager:2.2.1
-org.apache.maven:maven-model:3.9.4
-org.apache.maven:maven-plugin-api:3.9.4
+org.apache.maven:maven-artifact:${maven-core.vespa.version}
+org.apache.maven:maven-model:${maven-core.vespa.version}
+org.apache.maven:maven-plugin-api:${maven-core.vespa.version}
org.apache.maven:maven-plugin-registry:2.2.1
org.apache.maven:maven-profile:2.2.1
org.apache.maven:maven-project:2.2.1
-org.apache.maven:maven-repository-metadata:3.9.4
-org.apache.maven:maven-settings:3.9.4
-org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0
-org.apache.maven.plugins:maven-jar-plugin:3.3.0
-org.apache.maven.shared:file-management:3.1.0
-org.apache.maven.wagon:wagon-provider-api:3.5.3
-org.apache.opennlp:opennlp-tools:2.3.0
+org.apache.maven:maven-repository-metadata:${maven-core.vespa.version}
+org.apache.maven:maven-settings:${maven-core.vespa.version}
+org.apache.opennlp:opennlp-tools:${opennlp.vespa.version}
org.apache.velocity:velocity-engine-core:2.3
org.apache.yetus:audience-annotations:0.12.0
-org.apache.zookeeper:zookeeper:3.8.0
-org.apache.zookeeper:zookeeper:3.8.1
-org.apache.zookeeper:zookeeper-jute:3.8.0
+org.apache.zookeeper:zookeeper-jute:${zookeeper.client.vespa.version}
org.apache.zookeeper:zookeeper-jute:3.8.1
-org.apiguardian:apiguardian-api:1.1.2
-org.bouncycastle:bcpkix-jdk18on:1.76
-org.bouncycastle:bcprov-jdk18on:1.76
-org.bouncycastle:bcutil-jdk18on:1.76
+org.apache.zookeeper:zookeeper:${zookeeper.client.vespa.version}
+org.apache.zookeeper:zookeeper:3.8.1
+org.apiguardian:apiguardian-api:${apiguardian.vespa.version}
+org.assertj:assertj-core:${assertj.vespa.version}
+org.bouncycastle:bcpkix-jdk18on:${bouncycastle.vespa.version}
+org.bouncycastle:bcprov-jdk18on:${bouncycastle.vespa.version}
+org.bouncycastle:bcutil-jdk18on:${bouncycastle.vespa.version}
org.codehaus.plexus:plexus-archiver:4.8.0
org.codehaus.plexus:plexus-classworlds:2.7.0
org.codehaus.plexus:plexus-component-annotations:1.5.5
org.codehaus.plexus:plexus-container-default:1.0-alpha-9-stable-1
org.codehaus.plexus:plexus-interpolation:1.26
-org.codehaus.plexus:plexus-io:3.4.1
-org.codehaus.plexus:plexus-utils:3.5.1
-org.eclipse.angus:angus-activation:2.0.1
-org.eclipse.collections:eclipse-collections:11.1.0
-org.eclipse.collections:eclipse-collections-api:11.1.0
-org.eclipse.jetty:jetty-alpn-client:11.0.16
-org.eclipse.jetty:jetty-alpn-java-client:11.0.16
-org.eclipse.jetty:jetty-alpn-java-server:11.0.16
-org.eclipse.jetty:jetty-alpn-server:11.0.16
-org.eclipse.jetty:jetty-client:11.0.16
-org.eclipse.jetty:jetty-http:11.0.16
-org.eclipse.jetty:jetty-io:11.0.16
-org.eclipse.jetty:jetty-jmx:11.0.16
-org.eclipse.jetty:jetty-security:11.0.16
-org.eclipse.jetty:jetty-server:11.0.16
-org.eclipse.jetty:jetty-servlet:11.0.16
-org.eclipse.jetty:jetty-util:11.0.16
-org.eclipse.jetty.http2:http2-client:11.0.16
-org.eclipse.jetty.http2:http2-common:11.0.16
-org.eclipse.jetty.http2:http2-hpack:11.0.16
-org.eclipse.jetty.http2:http2-http-client-transport:11.0.16
-org.eclipse.jetty.http2:http2-server:11.0.16
-org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2
+org.codehaus.plexus:plexus-io:${maven-enforcer-plugin.vespa.version}
+org.codehaus.plexus:plexus-utils:${maven-shade-plugin.vespa.version}
+org.eclipse.angus:angus-activation:${jakarta.inject.vespa.version}
+org.eclipse.collections:eclipse-collections-api:${eclipse-collections.vespa.version}
+org.eclipse.collections:eclipse-collections:${eclipse-collections.vespa.version}
+org.eclipse.jetty.http2:http2-client:${jetty.vespa.version}
+org.eclipse.jetty.http2:http2-common:${jetty.vespa.version}
+org.eclipse.jetty.http2:http2-hpack:${jetty.vespa.version}
+org.eclipse.jetty.http2:http2-http-client-transport:${jetty.vespa.version}
+org.eclipse.jetty.http2:http2-server:${jetty.vespa.version}
+org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:${jetty-servlet-api.vespa.version}
+org.eclipse.jetty:jetty-alpn-client:${jetty.vespa.version}
+org.eclipse.jetty:jetty-alpn-java-client:${jetty.vespa.version}
+org.eclipse.jetty:jetty-alpn-java-server:${jetty.vespa.version}
+org.eclipse.jetty:jetty-alpn-server:${jetty.vespa.version}
+org.eclipse.jetty:jetty-client:${jetty.vespa.version}
+org.eclipse.jetty:jetty-http:${jetty.vespa.version}
+org.eclipse.jetty:jetty-io:${jetty.vespa.version}
+org.eclipse.jetty:jetty-jmx:${jetty.vespa.version}
+org.eclipse.jetty:jetty-security:${jetty.vespa.version}
+org.eclipse.jetty:jetty-server:${jetty.vespa.version}
+org.eclipse.jetty:jetty-servlet:${jetty.vespa.version}
+org.eclipse.jetty:jetty-util:${jetty.vespa.version}
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5
org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5
org.fusesource.jansi:jansi:1.18
-org.glassfish.jaxb:jaxb-core:4.0.3
-org.glassfish.jaxb:jaxb-runtime:4.0.3
-org.glassfish.jaxb:txw2:4.0.3
-org.hamcrest:hamcrest:2.2
-org.hamcrest:hamcrest-core:2.2
-org.hdrhistogram:HdrHistogram:2.1.12
+org.glassfish.jaxb:jaxb-core:${jaxb.runtime.vespa.version}
+org.glassfish.jaxb:jaxb-runtime:${jaxb.runtime.vespa.version}
+org.glassfish.jaxb:txw2:${jaxb.runtime.vespa.version}
+org.hamcrest:hamcrest-core:${hamcrest.vespa.version}
+org.hamcrest:hamcrest:${hamcrest.vespa.version}
+org.hdrhistogram:HdrHistogram:${hdrhistogram.vespa.version}
org.iq80.snappy:snappy:0.4
-org.json:json:20230618
-org.junit.jupiter:junit-jupiter-api:5.10.0
-org.junit.jupiter:junit-jupiter-api:5.8.1
-org.junit.jupiter:junit-jupiter-engine:5.8.1
-org.junit.platform:junit-platform-commons:1.8.1
-org.junit.platform:junit-platform-engine:1.8.1
-org.junit.platform:junit-platform-launcher:1.8.1
+org.json:json:${org.json.vespa.version}
+org.junit.jupiter:junit-jupiter-api:${junit.vespa.tenant.version}
+org.junit.jupiter:junit-jupiter-api:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter-engine:${junit.vespa.tenant.version}
+org.junit.jupiter:junit-jupiter-engine:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter-params:${junit.vespa.version}
+org.junit.jupiter:junit-jupiter:${junit.vespa.version}
+org.junit.platform:junit-platform-commons:${junit.platform.vespa.tenant.version}
+org.junit.platform:junit-platform-commons:${junit.platform.vespa.version}
+org.junit.platform:junit-platform-engine:${junit.platform.vespa.tenant.version}
+org.junit.platform:junit-platform-engine:${junit.platform.vespa.version}
+org.junit.platform:junit-platform-launcher:${junit.platform.vespa.tenant.version}
+org.junit.vintage:junit-vintage-engine:${junit.vespa.tenant.version}
+org.junit.vintage:junit-vintage-engine:${junit.vespa.version}
org.kohsuke:libpam4j:1.11
-org.lz4:lz4-java:1.8.0
-org.opentest4j:opentest4j:1.3.0
-org.ow2.asm:asm:9.5
-org.ow2.asm:asm-analysis:9.5
-org.ow2.asm:asm-commons:9.5
-org.ow2.asm:asm-tree:9.5
-org.ow2.asm:asm-util:9.5
-org.questdb:questdb:7.3.2
-org.slf4j:jcl-over-slf4j:1.7.36
-org.slf4j:log4j-over-slf4j:1.7.36
-org.slf4j:slf4j-api:1.7.36
-org.slf4j:slf4j-jdk14:1.7.36
-org.slf4j:slf4j-simple:1.7.36
+org.lz4:lz4-java:${org.lz4.vespa.version}
+org.mockito:mockito-core:${mockito.vespa.version}
+org.mockito:mockito-junit-jupiter:${mockito.vespa.version}
+org.objenesis:objenesis:3.3
+org.opentest4j:opentest4j:${opentest4j.vespa.version}
+org.ow2.asm:asm-analysis:${asm.vespa.version}
+org.ow2.asm:asm-commons:${asm.vespa.version}
+org.ow2.asm:asm-tree:${asm.vespa.version}
+org.ow2.asm:asm-util:${asm.vespa.version}
+org.ow2.asm:asm:${asm.vespa.version}
+org.questdb:questdb:${questdb.vespa.version}
+org.slf4j:jcl-over-slf4j:${slf4j.vespa.version}
+org.slf4j:log4j-over-slf4j:${slf4j.vespa.version}
+org.slf4j:slf4j-api:${slf4j.vespa.version}
+org.slf4j:slf4j-jdk14:${slf4j.vespa.version}
+org.slf4j:slf4j-simple:${slf4j.vespa.version}
org.tukaani:xz:1.9
-org.xerial.snappy:snappy-java:1.1.10.3
+org.wiremock:wiremock-standalone:${wiremock.vespa.version}
+org.xerial.snappy:snappy-java:${snappy.vespa.version}
software.amazon.ion:ion-java:1.0.2
-xerces:xercesImpl:2.12.2
-
-#[test-only]
-# Contains dependencies that are used exclusively in 'test' scope
-com.google.jimfs:jimfs:1.3.0
-net.bytebuddy:byte-buddy:1.14.8
-net.bytebuddy:byte-buddy-agent:1.14.8
-org.apache.curator:curator-test:5.5.0
-org.assertj:assertj-core:3.24.2
-org.junit.jupiter:junit-jupiter:5.10.0
-org.junit.jupiter:junit-jupiter-engine:5.10.0
-org.junit.jupiter:junit-jupiter-params:5.10.0
-org.junit.platform:junit-platform-commons:1.10.0
-org.junit.platform:junit-platform-engine:1.10.0
-org.junit.vintage:junit-vintage-engine:5.10.0
-org.junit.vintage:junit-vintage-engine:5.8.1
-org.mockito:mockito-core:5.5.0
-org.mockito:mockito-junit-jupiter:5.5.0
-org.objenesis:objenesis:3.3
-org.wiremock:wiremock-standalone:3.1.0
+xerces:xercesImpl:${xerces.vespa.version}
diff --git a/vespa-dependencies-enforcer/pom.xml b/vespa-dependencies-enforcer/pom.xml
index 768e5708ee5..91820fd292b 100644
--- a/vespa-dependencies-enforcer/pom.xml
+++ b/vespa-dependencies-enforcer/pom.xml
@@ -38,7 +38,7 @@
</goals>
<configuration>
<rules>
- <enforceDependencies implementation="com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects">
+ <enforceDependencies implementation="com.yahoo.vespa.maven.plugin.enforcer.AllowedDependencies">
<rootProjectId>com.yahoo.vespa:vespa</rootProjectId>
<specFile>allowed-maven-dependencies.txt</specFile>
<ignored>
@@ -47,14 +47,6 @@
<i>com.yahoo.vespa.bundle-plugin:*:*</i>
<i>com.yahoo.vespa.jdisc_core:*:*</i>
</ignored>
-
- <!-- Classifly all dependencies of below modules as 'test' -->
- <testUtilProjects>
- <!-- Misc -->
- <i>com.yahoo.vespa:testutil</i>
- <!-- Bundle plugin integration test -->
- <i>com.yahoo.vespa.bundle-plugin:*</i>
- </testUtilProjects>
</enforceDependencies>
</rules>
<fail>true</fail>
diff --git a/vespa-enforcer-extensions/pom.xml b/vespa-enforcer-extensions/pom.xml
index 9d8e99156f3..d27dcc08a43 100644
--- a/vespa-enforcer-extensions/pom.xml
+++ b/vespa-enforcer-extensions/pom.xml
@@ -45,6 +45,11 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
@@ -54,21 +59,18 @@
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-shade-plugin</artifactId>
+ <!-- generate index of project components -->
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>sisu-maven-plugin</artifactId>
+ <version>0.9.0.M2</version>
<executions>
<execution>
- <phase>package</phase>
<goals>
- <goal>shade</goal>
+ <goal>main-index</goal>
</goals>
- <configuration>
- <createDependencyReducedPom>false</createDependencyReducedPom>
- </configuration>
</execution>
</executions>
</plugin>
-
</plugins>
</build>
diff --git a/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/AllowedDependencies.java b/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/AllowedDependencies.java
new file mode 100644
index 00000000000..656e2f52558
--- /dev/null
+++ b/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/AllowedDependencies.java
@@ -0,0 +1,305 @@
+package com.yahoo.vespa.maven.plugin.enforcer;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
+import org.apache.maven.enforcer.rule.api.EnforcerRule;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author bjorncs
+ */
+@Named("allowedDependencies")
+@SuppressWarnings("deprecation")
+public class AllowedDependencies extends AbstractEnforcerRule implements EnforcerRule {
+
+ private static final String WRITE_SPEC_PROP = "dependencyEnforcer.writeSpec";
+ private static final String GUESS_VERSION = "dependencyEnforcer.guessProperty";
+
+ @Inject private MavenProject project;
+ @Inject private MavenSession session;
+ @Inject private DependencyGraphBuilder graphBuilder;
+
+ // Injected parameters
+ public List<String> ignored;
+ public String rootProjectId;
+ public String specFile;
+
+ @Override
+ public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
+ try {
+ project = (MavenProject) helper.evaluate("${project}");
+ session = (MavenSession) helper.evaluate("${session}");
+ graphBuilder = helper.getComponent(DependencyGraphBuilder.class);
+ } catch (ExpressionEvaluationException | ComponentLookupException e) {
+ throw new RuntimeException(e);
+ }
+ execute();
+ }
+
+ public void execute() throws EnforcerRuleException {
+ var dependencies = getDependenciesOfAllProjects();
+ getLog().info("Found %d unique dependencies ".formatted(dependencies.size()));
+ var specFile = Paths.get(project.getBasedir() + File.separator + this.specFile).normalize();
+ var spec = loadDependencySpec(specFile);
+ var resolved = resolve(spec, dependencies);
+ if (System.getProperties().containsKey(WRITE_SPEC_PROP)) {
+ writeDependencySpec(specFile, resolved, System.getProperties().containsKey(GUESS_VERSION));
+ getLog().info("Updated spec file '%s'".formatted(specFile.toString()));
+ } else {
+ warnOnDuplicateVersions(resolved);
+ validateDependencies(resolved, session.getRequest().getPom().toPath(), project.getArtifactId());
+ }
+ getLog().info("The dependency enforcer completed successfully");
+ }
+
+ private static void validateDependencies(Resolved resolved, Path aggregatorPomRoot, String moduleName)
+ throws EnforcerRuleException {
+ if (!resolved.unmatchedRules().isEmpty() || !resolved.unmatchedDeps().isEmpty()) {
+ var errorMsg = new StringBuilder("The dependency enforcer failed:\n");
+ if (!resolved.unmatchedRules().isEmpty()) {
+ errorMsg.append("Rules not matching any dependency:\n");
+ resolved.unmatchedRules().forEach(r -> errorMsg.append(" - ").append(r.asString()).append('\n'));
+ }
+ if (!resolved.unmatchedDeps().isEmpty()) {
+ errorMsg.append("Dependencies not matching any rule:\n");
+ resolved.unmatchedDeps().forEach(d -> errorMsg.append(" - ").append(d.asString(null)).append('\n'));
+ }
+ throw new EnforcerRuleException(
+ errorMsg.append("Maven dependency validation failed. ")
+ .append("If this change was intentional, update the dependency spec by running:\n")
+ .append("$ mvn validate -D").append(WRITE_SPEC_PROP).append(" -pl :").append(moduleName)
+ .append(" -f ").append(aggregatorPomRoot).append("\n").toString());
+ }
+ }
+
+ private Set<Dependency> getDependenciesOfAllProjects() throws EnforcerRuleException {
+ try {
+ Pattern depIgnorePattern = Pattern.compile(
+ ignored.stream()
+ .map(s -> s.replace(".", "\\.").replace("*", ".*").replace(":", "\\:").replace('?', '.'))
+ .collect(Collectors.joining(")|(", "^(", ")$")));
+ List<MavenProject> projects = getAllProjects(session, rootProjectId);
+ Set<Dependency> dependencies = new HashSet<>();
+ for (MavenProject project : projects) {
+ var req = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
+ req.setProject(project);
+ var root = graphBuilder.buildDependencyGraph(req, null);
+ addDependenciesRecursive(root, dependencies, depIgnorePattern);
+ }
+ return Set.copyOf(dependencies);
+ } catch (DependencyGraphBuilderException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ private static void addDependenciesRecursive(DependencyNode node, Set<Dependency> dependencies, Pattern ignored) {
+ if (node.getChildren() != null) {
+ for (DependencyNode dep : node.getChildren()) {
+ Artifact a = dep.getArtifact();
+ Dependency dependency = Dependency.fromArtifact(a);
+ if (!ignored.matcher(dependency.asString(null)).matches()) {
+ dependencies.add(dependency);
+ }
+ addDependenciesRecursive(dep, dependencies, ignored);
+ }
+ }
+ }
+
+ /** Only return the projects we'd like to enforce dependencies for: the root project, its modules, their modules, etc. */
+ private static List<MavenProject> getAllProjects(MavenSession session, String rootProjectId) throws EnforcerRuleException {
+ if (rootProjectId == null) throw new EnforcerRuleException("Missing required <rootProjectId> in <enforceDependencies> in pom.xml");
+
+ List<MavenProject> allProjects = session.getAllProjects();
+ if (allProjects.size() == 1) {
+ throw new EnforcerRuleException(
+ "Only a single Maven module detected. Enforcer must be executed from root of aggregator pom.");
+ }
+ MavenProject rootProject = allProjects
+ .stream()
+ .filter(project -> rootProjectId.equals(projectIdOf(project)))
+ .findAny()
+ .orElseThrow(() -> new EnforcerRuleException("Root project not found: " + rootProjectId));
+
+ Map<Path, MavenProject> projectsByBaseDir = allProjects
+ .stream()
+ .collect(Collectors.toMap(project -> project.getBasedir().toPath().normalize(), project -> project));
+
+ var projects = new ArrayList<MavenProject>();
+
+ var pendingProjects = new ArrayDeque<MavenProject>();
+ pendingProjects.add(rootProject);
+
+ while (!pendingProjects.isEmpty()) {
+ MavenProject project = pendingProjects.pop();
+ projects.add(project);
+
+ for (var module : project.getModules()) {
+ // Assumption: The module is a relative path to a project base directory.
+ Path moduleBaseDir = project.getBasedir().toPath().resolve(module).normalize();
+ MavenProject moduleProject = projectsByBaseDir.get(moduleBaseDir);
+ if (moduleProject == null)
+ throw new EnforcerRuleException("Failed to find module '" + module + "' in project " + project.getBasedir());
+ pendingProjects.add(moduleProject);
+ }
+ }
+
+ projects.sort(Comparator.comparing(AllowedDependencies::projectIdOf));
+ return projects;
+ }
+
+ private List<Rule> loadDependencySpec(Path specFile) {
+ try (Stream<String> s = Files.lines(specFile)) {
+ return s.map(String::trim)
+ .filter(l -> !l.isEmpty() && !l.startsWith("#"))
+ .map(Rule::fromString)
+ .toList();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private Resolved resolve(List<Rule> spec, Set<Dependency> dependencies) {
+ var resolvedDeps = new HashSet<Dependency>();
+ var resolveRules = new HashSet<Rule>();
+ var unmatchedDeps = new HashSet<Dependency>();
+ var unmatchedRules = new HashSet<Rule>();
+ for (var rule : spec) {
+ var requiredDependency = rule.resolveToDependency(project.getProperties());
+ if (dependencies.contains(requiredDependency)) {
+ resolvedDeps.add(requiredDependency);
+ resolveRules.add(rule);
+ } else {
+ unmatchedRules.add(rule);
+ }
+ }
+ for (var dependency : dependencies) {
+ if (!resolvedDeps.contains(dependency)) {
+ unmatchedDeps.add(dependency);
+ }
+ }
+ return new Resolved(resolvedDeps, resolveRules, unmatchedDeps, unmatchedRules);
+ }
+
+ void writeDependencySpec(Path specFile, Resolved resolved, boolean guessVersion) {
+ var content = new TreeSet<String>();
+ resolved.matchedRules().forEach(r -> content.add(r.asString()));
+ resolved.unmatchedDeps().forEach(d -> content.add(d.asString(guessVersion ? project.getProperties() : null)));
+ try (var out = Files.newBufferedWriter(specFile)) {
+ out.write("# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.\n\n");
+ for (var line : content) {
+ out.write(line); out.write('\n');
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void warnOnDuplicateVersions(Resolved resolved) {
+ Map<String, Set<String>> versionsForDependency = new TreeMap<>();
+ Set<Dependency> allDeps = new HashSet<>(resolved.matchedDeps());
+ allDeps.addAll(resolved.unmatchedDeps());
+ for (Dependency d : allDeps) {
+ String id = "%s:%s".formatted(d.groupId(), d.artifactId());
+ versionsForDependency.computeIfAbsent(id, __ -> new TreeSet<>()).add(d.version());
+ }
+ versionsForDependency.forEach((dependency, versions) -> {
+ if (versions.size() > 1) {
+ getLog().warn("'%s' has multiple versions %s".formatted(dependency, versions));
+ }
+ });
+ }
+
+ private static String projectIdOf(MavenProject project) { return "%s:%s".formatted(project.getGroupId(), project.getArtifactId()); }
+
+ private record Rule(String groupId, String artifactId, String version, Optional<String> classifier){
+ static final Pattern PROPERTY_PATTERN = Pattern.compile("\\$\\{(.+?)}");
+
+ static Rule fromString(String s) {
+ String[] splits = s.split(":");
+ return splits.length == 3
+ ? new Rule(splits[0], splits[1], splits[2], Optional.empty())
+ : new Rule(splits[0], splits[1], splits[2], Optional.of(splits[3]));
+ }
+
+ Dependency resolveToDependency(Properties props) {
+ // Replace expressions on form ${property} in 'version' field with value from properties
+ var matcher = PROPERTY_PATTERN.matcher(version);
+ var resolvedVersion = version;
+ while (matcher.find()) {
+ String property = matcher.group(1);
+ String value = props.getProperty(property);
+ if (value == null) throw new IllegalArgumentException("Missing property: " + property);
+ resolvedVersion = version.replace(matcher.group(), value);
+ }
+ return new Dependency(groupId, artifactId, resolvedVersion, classifier);
+ }
+
+ String asString() {
+ var b = new StringBuilder(groupId).append(':').append(artifactId).append(':').append(version);
+ classifier.ifPresent(c -> b.append(':').append(c));
+ return b.toString();
+ }
+ }
+
+ record Dependency(String groupId, String artifactId, String version, Optional<String> classifier) {
+ static Dependency fromArtifact(Artifact a) {
+ return new Dependency(
+ a.getGroupId(), a.getArtifactId(), a.getVersion(), Optional.ofNullable(a.getClassifier()));
+ }
+
+ String asString(Properties props) {
+ String versionStr = version;
+ if (props != null) {
+ // Guess property name if properties are provided
+ var matchingProps = props.entrySet().stream()
+ .filter(e -> e.getValue().equals(version))
+ .map(v -> "${%s}".formatted(v.getKey()))
+ .collect(Collectors.joining("|"));
+ if (!matchingProps.isEmpty()) versionStr = matchingProps;
+ }
+ var b = new StringBuilder(groupId).append(':').append(artifactId).append(':').append(versionStr);
+ classifier.ifPresent(c -> b.append(':').append(c));
+ return b.toString();
+ }
+ }
+
+ record Resolved(Set<Dependency> matchedDeps, Set<Rule> matchedRules,
+ Set<Dependency> unmatchedDeps, Set<Rule> unmatchedRules) {}
+
+ // Mark rule as not cachable
+ @Override public boolean isCacheable() { return false; }
+ @Override public boolean isResultValid(EnforcerRule r) { return false; }
+ @Override public String getCacheId() { return ""; }
+}
diff --git a/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java b/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java
deleted file mode 100644
index 3db1019a2b1..00000000000
--- a/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.maven.plugin.enforcer;
-
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.enforcer.rule.api.EnforcerRule;
-import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
-import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.logging.Log;
-import org.apache.maven.project.DefaultProjectBuildingRequest;
-import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
-import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * @author bjorncs
- */
-@SuppressWarnings("deprecation")
-public class EnforceDependenciesAllProjects implements EnforcerRule {
-
- private static final String WRITE_SPEC_PROP = "dependencyEnforcer.writeSpec";
- private static final String NON_TEST_HEADER = "#[non-test]";
- private static final String TEST_ONLY_HEADER = "#[test-only]";
-
- private String rootProjectId;
- private String specFile;
- private List<String> ignored = List.of();
- private List<String> testUtilProjects = List.of();
-
- @Override
- public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
- Log log = helper.getLog();
- Dependencies deps = getDependenciesOfAllProjects(helper, ignored, testUtilProjects, rootProjectId);
- log.info("Found %d unique dependencies (%d non-test, %d test only)".formatted(
- deps.nonTest().size() + deps.testOnly().size(), deps.nonTest().size(), deps.testOnly().size()));
- Path specFile = resolveSpecFile(helper, this.specFile);
- if (System.getProperties().containsKey(WRITE_SPEC_PROP)) {
- writeDependencySpec(specFile, deps);
- log.info("Updated spec file '%s'".formatted(specFile.toString()));
- } else {
- warnOnDuplicateVersions(log, deps);
- validateDependencies(deps, specFile, aggregatorPomRoot(helper), projectName(helper));
- }
- log.info("The dependency enforcer completed successfully");
- }
-
- // Config injection for rule configuration. Method names must match config XML elements.
- @SuppressWarnings("unused") public void setRootProjectId(String l) { this.rootProjectId = l; }
- @SuppressWarnings("unused") public String getRootProjectId() { return rootProjectId; }
- @SuppressWarnings("unused") public void setSpecFile(String f) { this.specFile = f; }
- @SuppressWarnings("unused") public String getSpecFile() { return specFile; }
- @SuppressWarnings("unused") public void setIgnored(List<String> l) { this.ignored = l; }
- @SuppressWarnings("unused") public List<String> getIgnored() { return ignored; }
- @SuppressWarnings("unused") public void setTestUtilProjects(List<String> l) { this.testUtilProjects = l; }
- @SuppressWarnings("unused") public List<String> getTestUtilProjects() { return testUtilProjects; }
-
- record Dependency(String groupId, String artifactId, String version, Optional<String> classifier)
- implements Comparable<Dependency> {
- static Dependency fromArtifact(Artifact a) {
- return new Dependency(
- a.getGroupId(), a.getArtifactId(), a.getVersion(), Optional.ofNullable(a.getClassifier()));
- }
-
- static Dependency fromString(String s) {
- String[] splits = s.split(":");
- return splits.length == 3
- ? new Dependency(splits[0], splits[1], splits[2], Optional.empty())
- : new Dependency(splits[0], splits[1], splits[2], Optional.of(splits[3]));
- }
-
- String asString() {
- var b = new StringBuilder(groupId).append(':').append(artifactId).append(':').append(version);
- classifier.ifPresent(c -> b.append(':').append(c));
- return b.toString();
- }
-
- static final Comparator<Dependency> COMPARATOR = Comparator.comparing(Dependency::groupId)
- .thenComparing(Dependency::artifactId).thenComparing(Dependency::version)
- .thenComparing(d -> d.classifier().orElse(""));
- @Override public int compareTo(Dependency o) { return COMPARATOR.compare(this, o); }
- }
-
- record Dependencies(SortedSet<Dependency> nonTest, SortedSet<Dependency> testOnly) {}
-
- static void validateDependencies(Dependencies dependencies, Path specFile, Path aggregatorPomRoot,
- String moduleName)
- throws EnforcerRuleException {
- Dependencies allowedDependencies = loadDependencySpec(specFile);
- if (!allowedDependencies.equals(dependencies)) {
- StringBuilder errorMsg = new StringBuilder("The dependency enforcer failed:\n");
- generateDiff(errorMsg, "non-test", dependencies.nonTest(), allowedDependencies.nonTest());
- generateDiff(errorMsg, "test-only", dependencies.testOnly(), allowedDependencies.testOnly());
- throw new EnforcerRuleException(
- errorMsg.append("Maven dependency validation failed. ")
- .append("If this change was intentional, update the dependency spec by running:\n")
- .append("$ mvn validate -D").append(WRITE_SPEC_PROP).append(" -pl :").append(moduleName)
- .append(" -f ").append(aggregatorPomRoot).append("\n").toString());
- }
- }
-
- static void generateDiff(
- StringBuilder errorMsg, String label, SortedSet<Dependency> actual, SortedSet<Dependency> expected) {
- SortedSet<Dependency> forbidden = new TreeSet<>(actual);
- forbidden.removeAll(expected);
- SortedSet<Dependency> removed = new TreeSet<>(expected);
- removed.removeAll(actual);
- if (!forbidden.isEmpty()) {
- errorMsg.append("Forbidden ").append(label).append(" dependencies:\n");
- forbidden.forEach(d -> errorMsg.append(" - ").append(d.asString()).append('\n'));
- }
- if (!removed.isEmpty()) {
- errorMsg.append("Removed ").append(label).append(" dependencies:\n");
- removed.forEach(d -> errorMsg.append(" - ").append(d.asString()).append('\n'));
- }
- }
-
- private static Dependencies getDependenciesOfAllProjects(EnforcerRuleHelper helper, List<String> ignored,
- List<String> testUtilProjects, String rootProjectId)
- throws EnforcerRuleException {
- try {
- Pattern depIgnorePattern = Pattern.compile(
- ignored.stream()
- .map(s -> s.replace(".", "\\.").replace("*", ".*").replace(":", "\\:").replace('?', '.'))
- .collect(Collectors.joining(")|(", "^(", ")$")));
- Pattern projectIgnorePattern = Pattern.compile(
- testUtilProjects.stream()
- .map(s -> s.replace(".", "\\.").replace("*", ".*").replace(":", "\\:").replace('?', '.'))
- .collect(Collectors.joining(")|(", "^(", ")$")));
- SortedSet<Dependency> nonTestDeps = new TreeSet<>();
- SortedSet<Dependency> testDeps = new TreeSet<>();
- MavenSession session = mavenSession(helper);
- var graphBuilder = helper.getComponent(DependencyGraphBuilder.class);
- List<MavenProject> projects = getAllProjects(session, rootProjectId);
- for (MavenProject project : projects) {
- var req = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
- req.setProject(project);
- DependencyNode root = graphBuilder.buildDependencyGraph(req, null);
- String projectId = projectIdOf(project);
- boolean overrideToTest = projectIgnorePattern.matcher(projectId).matches();
- if (overrideToTest) helper.getLog().info("Treating dependencies of '%s' as 'test'".formatted(projectId));
- addDependenciesRecursive(root, nonTestDeps, testDeps, depIgnorePattern, overrideToTest);
- }
- testDeps.removeAll(nonTestDeps);
- return new Dependencies(nonTestDeps, testDeps);
- } catch (DependencyGraphBuilderException | ComponentLookupException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
- }
-
- private static String projectIdOf(MavenProject project) { return "%s:%s".formatted(project.getGroupId(), project.getArtifactId()); }
-
- /** Only return the projects we'd like to enforce dependencies for: the root project, its modules, their modules, etc. */
- private static List<MavenProject> getAllProjects(MavenSession session, String rootProjectId) throws EnforcerRuleException {
- if (rootProjectId == null) throw new EnforcerRuleException("Missing required <rootProjectId> in <enforceDependencies> in pom.xml");
-
- List<MavenProject> allProjects = session.getAllProjects();
- if (allProjects.size() == 1) {
- throw new EnforcerRuleException(
- "Only a single Maven module detected. Enforcer must be executed from root of aggregator pom.");
- }
- MavenProject rootProject = allProjects
- .stream()
- .filter(project -> rootProjectId.equals(projectIdOf(project)))
- .findAny()
- .orElseThrow(() -> new EnforcerRuleException("Root project not found: " + rootProjectId));
-
- Map<Path, MavenProject> projectsByBaseDir = allProjects
- .stream()
- .collect(Collectors.toMap(project -> project.getBasedir().toPath().normalize(), project -> project));
-
- var projects = new ArrayList<MavenProject>();
-
- var pendingProjects = new ArrayDeque<MavenProject>();
- pendingProjects.add(rootProject);
-
- while (!pendingProjects.isEmpty()) {
- MavenProject project = pendingProjects.pop();
- projects.add(project);
-
- for (var module : project.getModules()) {
- // Assumption: The module is a relative path to a project base directory.
- Path moduleBaseDir = project.getBasedir().toPath().resolve(module).normalize();
- MavenProject moduleProject = projectsByBaseDir.get(moduleBaseDir);
- if (moduleProject == null)
- throw new EnforcerRuleException("Failed to find module '" + module + "' in project " + project.getBasedir());
- pendingProjects.add(moduleProject);
- }
- }
-
- projects.sort(Comparator.comparing(EnforceDependenciesAllProjects::projectIdOf));
- return projects;
- }
-
- private static void addDependenciesRecursive(
- DependencyNode node, Set<Dependency> nonTestDeps, Set<Dependency> testDeps, Pattern ignored,
- boolean overrideToTest) {
- if (node.getChildren() != null) {
- for (DependencyNode dep : node.getChildren()) {
- Artifact a = dep.getArtifact();
- Dependency dependency = Dependency.fromArtifact(a);
- if (!ignored.matcher(dependency.asString()).matches()) {
- if (a.getScope().equals("test") || overrideToTest) {
- testDeps.add(dependency);
- } else {
- nonTestDeps.add(dependency);
- }
- }
- addDependenciesRecursive(dep, nonTestDeps, testDeps, ignored, overrideToTest);
- }
- }
- }
-
- private static void warnOnDuplicateVersions(Log log, Dependencies deps) {
- Map<String, Set<String>> versionsForDependency = new TreeMap<>();
- Set<Dependency> allDeps = new TreeSet<>(deps.nonTest());
- allDeps.addAll(deps.testOnly());
- for (Dependency d : allDeps) {
- String id = "%s:%s".formatted(d.groupId(), d.artifactId());
- versionsForDependency.computeIfAbsent(id, __ -> new TreeSet<>()).add(d.version());
- }
- versionsForDependency.forEach((dependency, versions) -> {
- if (versions.size() > 1) {
- log.warn("'%s' has multiple versions %s".formatted(dependency, versions));
- }
- });
- }
-
- private static Path resolveSpecFile(EnforcerRuleHelper helper, String specFile) {
- return Paths.get(mavenProject(helper).getBasedir() + File.separator + specFile).normalize();
- }
-
- private static String projectName(EnforcerRuleHelper helper) { return mavenProject(helper).getArtifactId(); }
-
- private static Path aggregatorPomRoot(EnforcerRuleHelper helper) {
- return mavenSession(helper).getRequest().getPom().toPath();
- }
-
- private static MavenProject mavenProject(EnforcerRuleHelper helper) {
- try {
- return (MavenProject) helper.evaluate("${project}");
- } catch (ExpressionEvaluationException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
- }
-
- private static MavenSession mavenSession(EnforcerRuleHelper helper) {
- try {
- return (MavenSession) helper.evaluate("${session}");
- } catch (ExpressionEvaluationException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
- }
-
- static void writeDependencySpec(Path specFile, Dependencies dependencies) {
- try (var out = Files.newBufferedWriter(specFile)) {
- out.write("# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.\n\n");
- out.write(NON_TEST_HEADER); out.write('\n');
- out.write("# Contains dependencies that are not used exclusively in 'test' scope\n");
- for (Dependency d : dependencies.nonTest()) {
- out.write(d.asString()); out.write('\n');
- }
- out.write("\n"); out.write(TEST_ONLY_HEADER); out.write('\n');
- out.write("# Contains dependencies that are used exclusively in 'test' scope\n");
- for (Dependency d : dependencies.testOnly()) {
- out.write(d.asString()); out.write('\n');
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private static Dependencies loadDependencySpec(Path specFile) {
- try {
- List<String> lines;
- try (Stream<String> s = Files.lines(specFile)) {
- lines = s.map(String::trim).filter(l -> !l.isEmpty()).toList();
- }
- SortedSet<Dependency> nonTest = parseDependencies(lines.stream().takeWhile(l -> !l.equals(TEST_ONLY_HEADER)));
- SortedSet<Dependency> testOnly = parseDependencies(lines.stream().dropWhile(l -> !l.equals(TEST_ONLY_HEADER)));
- return new Dependencies(nonTest, testOnly);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private static SortedSet<Dependency> parseDependencies(Stream<String> lines) {
- return lines.filter(l -> !l.startsWith("#")).map(Dependency::fromString)
- .collect(Collectors.toCollection(TreeSet::new));
- }
-
- // Mark rule as not cachable
- @Override public boolean isCacheable() { return false; }
- @Override public boolean isResultValid(EnforcerRule r) { return false; }
- @Override public String getCacheId() { return ""; }
-
-}
diff --git a/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java b/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java
deleted file mode 100644
index 59062cbd61c..00000000000
--- a/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.maven.plugin.enforcer;
-
-import com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.Dependencies;
-import com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.Dependency;
-import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import static com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.validateDependencies;
-import static com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.writeDependencySpec;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-/**
- * @author bjorncs
- */
-class EnforceDependenciesAllProjectsTest {
-
- private static final Path POM_FILE = Paths.get("/vespa-src/pom.xml");
-
- @Test
- void succeeds_dependencies_matches_spec() {
- SortedSet<Dependency> nonTest = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:foo:1.2.3"),
- Dependency.fromString("com.example:bar:2.3.4")));
- SortedSet<Dependency> testOnly = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:testfoo:1.2.3"),
- Dependency.fromString("com.example:testbar:2.3.4")));
- Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt");
- Dependencies deps = new Dependencies(nonTest, testOnly);
- assertDoesNotThrow(() -> validateDependencies(deps, specFile, POM_FILE, "my-dep-enforcer"));
- }
-
- @Test
- void fails_on_forbidden_dependency() {
- SortedSet<Dependency> nonTest = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:foo:1.2.3"),
- Dependency.fromString("com.example:bar:2.3.4"),
- Dependency.fromString("com.example:foobar:3.4.5")));
- SortedSet<Dependency> testOnly = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:testfoo:1.2.3"),
- Dependency.fromString("com.example:testbar:2.3.4")));
- Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt");
- Dependencies deps = new Dependencies(nonTest, testOnly);
- var exception = assertThrows(EnforcerRuleException.class,
- () -> validateDependencies(deps, specFile, POM_FILE, "my-dep-enforcer"));
- String expectedErrorMessage =
- """
- The dependency enforcer failed:
- Forbidden non-test dependencies:
- - com.example:foobar:3.4.5
- Maven dependency validation failed. If this change was intentional, update the dependency spec by running:
- $ mvn validate -DdependencyEnforcer.writeSpec -pl :my-dep-enforcer -f /vespa-src/pom.xml
- """;
- assertEquals(expectedErrorMessage, exception.getMessage());
- }
-
- @Test
- void fails_on_missing_dependency() {
- SortedSet<Dependency> nonTest = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:bar:2.3.4")));
- SortedSet<Dependency> testOnly = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:testfoo:1.2.3")));
- Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt");
- Dependencies deps = new Dependencies(nonTest, testOnly);
- var exception = assertThrows(EnforcerRuleException.class,
- () -> validateDependencies(deps, specFile, POM_FILE, "my-dep-enforcer"));
- String expectedErrorMessage =
- """
- The dependency enforcer failed:
- Removed non-test dependencies:
- - com.example:foo:1.2.3
- Removed test-only dependencies:
- - com.example:testbar:2.3.4
- Maven dependency validation failed. If this change was intentional, update the dependency spec by running:
- $ mvn validate -DdependencyEnforcer.writeSpec -pl :my-dep-enforcer -f /vespa-src/pom.xml
- """;
- assertEquals(expectedErrorMessage, exception.getMessage());
- }
-
- @Test
- void writes_valid_spec_file(@TempDir Path tempDir) throws IOException {
- SortedSet<Dependency> nonTest = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:foo:1.2.3"),
- Dependency.fromString("com.example:bar:2.3.4")));
- SortedSet<Dependency> testOnly = new TreeSet<>(Set.of(
- Dependency.fromString("com.example:testfoo:1.2.3"),
- Dependency.fromString("com.example:testbar:2.3.4")));
- Dependencies deps = new Dependencies(nonTest, testOnly);
- Path outputFile = tempDir.resolve("allowed-dependencies.txt");
- writeDependencySpec(outputFile, deps);
- assertEquals(
- Files.readString(Paths.get("src/test/resources/allowed-dependencies.txt")),
- Files.readString(outputFile));
-
- }
-
-} \ No newline at end of file
diff --git a/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt b/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt
deleted file mode 100644
index 2ef0f9e0c0c..00000000000
--- a/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#[non-test]
-# Contains dependencies that are not used exclusively in 'test' scope
-com.example:bar:2.3.4
-com.example:foo:1.2.3
-
-#[test-only]
-# Contains dependencies that are used exclusively in 'test' scope
-com.example:testbar:2.3.4
-com.example:testfoo:1.2.3
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index c1d7e17b457..56dcd9abdf6 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -96,6 +96,7 @@ vespa_define_module(
src/tests/fileheader
src/tests/floatingpointtype
src/tests/fuzzy
+ src/tests/fuzzy/table_dfa
src/tests/gencnt
src/tests/growablebytebuffer
src/tests/guard
diff --git a/vespalib/src/tests/fuzzy/levenshtein_dfa_test.cpp b/vespalib/src/tests/fuzzy/levenshtein_dfa_test.cpp
index c235cb99509..919ba328085 100644
--- a/vespalib/src/tests/fuzzy/levenshtein_dfa_test.cpp
+++ b/vespalib/src/tests/fuzzy/levenshtein_dfa_test.cpp
@@ -11,6 +11,7 @@
#include <string>
#include <string_view>
#include <gtest/gtest.h>
+#include <gmock/gmock.h>
using namespace ::testing;
using namespace vespalib::fuzzy;
@@ -82,7 +83,8 @@ INSTANTIATE_TEST_SUITE_P(AllCasingAndDfaTypes,
Combine(Values(LevenshteinDfa::Casing::Uncased,
LevenshteinDfa::Casing::Cased),
Values(LevenshteinDfa::DfaType::Explicit,
- LevenshteinDfa::DfaType::Implicit)),
+ LevenshteinDfa::DfaType::Implicit,
+ LevenshteinDfa::DfaType::Table)),
LevenshteinDfaTest::stringify_params);
// Same as existing non-DFA Levenshtein tests, but with some added instantiations
@@ -122,8 +124,10 @@ TEST_P(LevenshteinDfaTest, distance_is_in_utf32_code_point_space) {
EXPECT_EQ(calculate(u8"カラオケ", u8"カラoke", 2), std::nullopt);
}
-void test_dfa_successor(const LevenshteinDfa& dfa, std::string_view source, std::string_view expected_successor) {
- std::string successor;
+void test_dfa_successor(const LevenshteinDfa& dfa, std::string_view source,
+ std::string_view expected_successor, std::string_view successor_prefix)
+{
+ std::string successor(successor_prefix);
auto m = dfa.match(source, successor);
if (m.matches()) {
FAIL() << "Expected '" << source << "' to emit a successor, but it "
@@ -131,15 +135,21 @@ void test_dfa_successor(const LevenshteinDfa& dfa, std::string_view source, std:
<< " edits (of max " << static_cast<uint32_t>(m.max_edits()) << " edits)";
}
EXPECT_EQ(successor, expected_successor);
- EXPECT_TRUE(dfa.match(successor).matches());
+ // Must skip any caller-provided successor prefix before checking if it matches the target
+ auto successor_suffix = successor.substr(successor_prefix.size());
+ EXPECT_TRUE(dfa.match(successor_suffix).matches());
// Make sure the UTF-32 successor output is codepoint-wise identical to the UTF-8 successor
- std::vector<uint32_t> u32successor;
+ std::vector<uint32_t> u32successor(utf8_string_to_utf32(successor_prefix));
m = dfa.match(source, u32successor);
EXPECT_FALSE(m.matches());
expect_utf32_string_code_point_equal_to_utf8(u32successor, successor);
}
+void test_dfa_successor(const LevenshteinDfa& dfa, std::string_view source, std::string_view expected_successor) {
+ test_dfa_successor(dfa, source, expected_successor, {});
+}
+
TEST_P(LevenshteinDfaTest, can_generate_successors_to_mismatching_source_strings) {
auto dfa = LevenshteinDfa::build("food", 1, casing(), dfa_type());
@@ -201,6 +211,28 @@ TEST_P(LevenshteinDfaTest, successor_is_well_defined_for_empty_target) {
test_dfa_successor(dfa, "vespa", "w");
}
+TEST_P(LevenshteinDfaTest, caller_provided_successor_prefix_is_preserved_on_mismatch) {
+ auto dfa = LevenshteinDfa::build("food", 1, casing(), dfa_type());
+
+ // Same inputs as existing successor tests, but with a preserved prefix in the generated successor
+ test_dfa_successor(dfa, "", "yolo\x01""food", "yolo");
+ test_dfa_successor(dfa, "faa", "xyzfaod", "xyz");
+ test_dfa_successor(dfa, "fooooo", "ABCfoop", "ABC");
+ test_dfa_successor(dfa, "ooof", "ABCpfood", "ABC");
+ test_dfa_successor(dfa, "gp", "yolohfood", "yolo");
+
+ dfa = LevenshteinDfa::build("", 1, casing(), dfa_type());
+ test_dfa_successor(dfa, "aa", "foob", "foo");
+}
+
+TEST_P(LevenshteinDfaTest, caller_provided_successor_prefix_is_preserved_on_match) {
+ auto dfa = LevenshteinDfa::build("food", 1, casing(), dfa_type());
+ std::string successor = "bar";
+ auto m = dfa.match("mood", successor);
+ EXPECT_TRUE(m.matches());
+ EXPECT_THAT(successor, StartsWith("bar"));
+}
+
// We should normally be able to rely on higher-level components to ensure we
// only receive valid UTF-8, but make sure we don't choke on it if we do get it.
TEST_P(LevenshteinDfaTest, malformed_utf8_is_replaced_with_placeholder_char) {
@@ -233,7 +265,8 @@ struct LevenshteinDfaCasingTest : TestWithParam<LevenshteinDfa::DfaType> {
INSTANTIATE_TEST_SUITE_P(AllDfaTypes,
LevenshteinDfaCasingTest,
Values(LevenshteinDfa::DfaType::Explicit,
- LevenshteinDfa::DfaType::Implicit),
+ LevenshteinDfa::DfaType::Implicit,
+ LevenshteinDfa::DfaType::Table),
PrintToStringParamName());
TEST_P(LevenshteinDfaCasingTest, uncased_edge_cases_have_correct_edit_distance) {
@@ -315,7 +348,8 @@ INSTANTIATE_TEST_SUITE_P(SupportedMaxEdits,
Combine(Values(LevenshteinDfa::Casing::Uncased,
LevenshteinDfa::Casing::Cased),
Values(LevenshteinDfa::DfaType::Explicit,
- LevenshteinDfa::DfaType::Implicit),
+ LevenshteinDfa::DfaType::Implicit,
+ LevenshteinDfa::DfaType::Table),
Values(1, 2)),
LevenshteinDfaSuccessorTest::stringify_params);
@@ -342,6 +376,7 @@ TEST_P(LevenshteinDfaSuccessorTest, exhaustive_successor_test) {
std::string skip_to, successor;
for (uint32_t j = 0; j < 256; ++j) {
const auto source = bits_to_str(static_cast<uint8_t>(j));
+ successor.clear();
auto maybe_match = target_dfa.match(source, successor);
if (maybe_match.matches() && !skip_to.empty()) {
ASSERT_GE(source, skip_to);
@@ -597,6 +632,7 @@ TEST_P(LevenshteinBenchmarkTest, benchmark_skipping_dictionary_scan) {
auto end = dict.cend();
std::string successor;
while (iter != end) {
+ successor.clear();
auto maybe_match = dfa.match(*iter, successor);
if (maybe_match.matches()) {
++iter;
diff --git a/vespalib/src/tests/fuzzy/table_dfa/CMakeLists.txt b/vespalib/src/tests/fuzzy/table_dfa/CMakeLists.txt
new file mode 100644
index 00000000000..1017ac99564
--- /dev/null
+++ b/vespalib/src/tests/fuzzy/table_dfa/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_fuzzy_table_dfa_test_app TEST
+ SOURCES
+ table_dfa_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+ )
+vespa_add_test(NAME vespalib_fuzzy_table_dfa_test_app COMMAND vespalib_fuzzy_table_dfa_test_app)
diff --git a/vespalib/src/tests/fuzzy/table_dfa/table_dfa_test.cpp b/vespalib/src/tests/fuzzy/table_dfa/table_dfa_test.cpp
new file mode 100644
index 00000000000..acb68a56de7
--- /dev/null
+++ b/vespalib/src/tests/fuzzy/table_dfa/table_dfa_test.cpp
@@ -0,0 +1,395 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/fuzzy/table_dfa.hpp>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <set>
+
+using namespace ::testing;
+using namespace vespalib::fuzzy;
+
+// test/experiment with low-level concepts underlying the construction
+// of the tables used in the table-driven dfa implementation.
+
+TEST(TableDfaTest, position) {
+ Position pos1 = Position::start();
+ EXPECT_EQ(pos1.index, 0);
+ EXPECT_EQ(pos1.edits, 0);
+ Position pos2(2, 3);
+ EXPECT_EQ(pos2.index, 2);
+ EXPECT_EQ(pos2.edits, 3);
+}
+
+TEST(TableDfaTest, position_equality) {
+ Position pos1(0, 0);
+ Position pos2(0, 1);
+ Position pos3(1, 0);
+ EXPECT_TRUE(pos1 == pos1);
+ EXPECT_FALSE(pos1 == pos2);
+ EXPECT_FALSE(pos1 == pos2);
+}
+
+TEST(TableDfaTest, position_sort_order) {
+ std::vector<Position> list;
+ list.emplace_back(0,1);
+ list.emplace_back(0,0);
+ list.emplace_back(1,0);
+ list.emplace_back(1,1);
+ std::sort(list.begin(), list.end());
+ EXPECT_EQ(list[0].index, 0);
+ EXPECT_EQ(list[0].edits, 0);
+ EXPECT_EQ(list[1].index, 1);
+ EXPECT_EQ(list[1].edits, 0);
+ EXPECT_EQ(list[2].index, 0);
+ EXPECT_EQ(list[2].edits, 1);
+ EXPECT_EQ(list[3].index, 1);
+ EXPECT_EQ(list[3].edits, 1);
+}
+
+TEST(TableDfaTest, position_subsumption) {
+ Position pos1(0, 0);
+ Position pos2(0, 1);
+ Position pos3(0, 2);
+
+ Position pos4(1, 0);
+ Position pos5(1, 1);
+ Position pos6(1, 2);
+
+ Position pos7(2, 0);
+ Position pos8(2, 1);
+ Position pos9(2, 2);
+
+ EXPECT_FALSE(pos1.subsumes(pos1));
+ EXPECT_TRUE(pos1.subsumes(pos2));
+ EXPECT_TRUE(pos1.subsumes(pos3));
+ EXPECT_FALSE(pos1.subsumes(pos4));
+ EXPECT_TRUE(pos1.subsumes(pos5));
+ EXPECT_TRUE(pos1.subsumes(pos6));
+ EXPECT_FALSE(pos1.subsumes(pos7));
+ EXPECT_FALSE(pos1.subsumes(pos8));
+ EXPECT_TRUE(pos1.subsumes(pos9));
+
+ EXPECT_FALSE(pos5.subsumes(pos1));
+ EXPECT_FALSE(pos5.subsumes(pos2));
+ EXPECT_TRUE(pos5.subsumes(pos3));
+ EXPECT_FALSE(pos5.subsumes(pos4));
+ EXPECT_FALSE(pos5.subsumes(pos5));
+ EXPECT_TRUE(pos5.subsumes(pos6));
+ EXPECT_FALSE(pos5.subsumes(pos7));
+ EXPECT_FALSE(pos5.subsumes(pos8));
+ EXPECT_TRUE(pos5.subsumes(pos9));
+}
+
+TEST(TableDfaTest, position_materialization) {
+ EXPECT_EQ(Position(1,1).materialize(0).index, 0);
+ EXPECT_EQ(Position(1,1).materialize(1).index, 1);
+ EXPECT_EQ(Position(1,1).materialize(2).index, 2);
+ EXPECT_EQ(Position(1,1).materialize(0).edits, 2);
+ EXPECT_EQ(Position(1,1).materialize(1).edits, 1);
+ EXPECT_EQ(Position(1,1).materialize(2).edits, 2);
+}
+
+TEST(TableDfaTest, position_to_string) {
+ Position pos1(0, 0);
+ Position pos2(1, 2);
+ Position pos3(2, 3);
+ EXPECT_EQ(pos1.to_string(), fmt("0#0"));
+ EXPECT_EQ(pos2.to_string(), fmt("1#2"));
+ EXPECT_EQ(pos3.to_string(), fmt("2#3"));
+}
+
+TEST(TableDfaTest, state_creation_reorder) {
+ EXPECT_EQ(State::create<5>({{0,1},{2,0}}).to_string(), fmt("{2#0,0#1}"));
+ EXPECT_EQ(State::create<5>({{2,0},{0,0}}).to_string(), fmt("{0#0,2#0}"));
+}
+
+TEST(TableDfaTest, state_creation_duplicate_removal) {
+ EXPECT_EQ(State::create<5>({{0,0},{0,0},{2,1},{2,1}}).to_string(), fmt("{0#0,2#1}"));
+}
+
+TEST(TableDfaTest, state_creation_edit_cutoff) {
+ EXPECT_EQ(State::create<2>({{0,0},{5,2},{10,3}}).to_string(), fmt("{0#0,5#2}"));
+}
+
+TEST(TableDfaTest, state_creation_subsumption_collapsing) {
+ EXPECT_EQ(State::create<2>({{0,0},{1,1}}).to_string(), fmt("{0#0}"));
+ EXPECT_EQ(State::create<2>({{0,1},{1,0}}).to_string(), fmt("{1#0}"));
+ EXPECT_EQ(State::create<2>({{0,0},{2,2}}).to_string(), fmt("{0#0}"));
+ EXPECT_EQ(State::create<2>({{0,2},{2,0}}).to_string(), fmt("{2#0}"));
+}
+
+TEST(TableDfaTest, state_normalization) {
+ auto state1 = State::create<2>({{2,1},{3,1}});
+ auto state2 = State::create<2>({{5,0},{3,1}});
+ EXPECT_EQ(state1.to_string(), fmt("{2#1,3#1}"));
+ EXPECT_EQ(state2.to_string(), fmt("{5#0,3#1}"));
+ EXPECT_EQ(state1.normalize(), 2);
+ EXPECT_EQ(state2.normalize(), 3);
+ EXPECT_EQ(state1.to_string(), fmt("{0#1,1#1}"));
+ EXPECT_EQ(state2.to_string(), fmt("{2#0,0#1}"));
+}
+
+TEST(TableDfaTest, state_repo) {
+ StateRepo repo;
+ EXPECT_EQ(repo.state_to_idx(State::failed()), 0);
+ EXPECT_EQ(repo.state_to_idx(State::start()), 1);
+ EXPECT_EQ(repo.state_to_idx(State::create<2>({{0,0},{1,0}})), 2);
+ EXPECT_EQ(repo.state_to_idx(State::create<2>({{0,0},{2,1}})), 3);
+ EXPECT_EQ(repo.state_to_idx(State::create<2>({{0,0},{1,0}})), 2);
+ EXPECT_EQ(repo.state_to_idx(State::create<2>({{0,0},{2,1}})), 3);
+ EXPECT_EQ(repo.size(), 4);
+ EXPECT_EQ(repo.idx_to_state(0).to_string(), fmt("{}"));
+ EXPECT_EQ(repo.idx_to_state(1).to_string(), fmt("{0#0}"));
+ EXPECT_EQ(repo.idx_to_state(2).to_string(), fmt("{0#0,1#0}"));
+ EXPECT_EQ(repo.idx_to_state(3).to_string(), fmt("{0#0,2#1}"));
+}
+
+TEST(TableDfaTest, expand_bits) {
+ auto yes = expand_bits<2>(0x1f);
+ auto no = expand_bits<2>(0x00);
+ auto odd = expand_bits<2>(0x0a);
+ auto even = expand_bits<2>(0x15);
+ ASSERT_EQ(yes.size(), 5);
+ ASSERT_EQ(no.size(), 5);
+ ASSERT_EQ(odd.size(), 5);
+ ASSERT_EQ(even.size(), 5);
+ for (size_t i = 0; i < 5; ++i) {
+ EXPECT_TRUE(yes[i]);
+ EXPECT_FALSE(no[i]);
+ EXPECT_EQ(odd[i], bool(i % 2 == 1));
+ EXPECT_EQ(even[i], bool(i % 2 == 0));
+ }
+}
+
+TEST(TableDfaTest, format_bits) {
+ EXPECT_EQ(format_vector(expand_bits<1>(0)), fmt("[0,0,0]"));
+ EXPECT_EQ(format_vector(expand_bits<1>(7)), fmt("[1,1,1]"));
+ EXPECT_EQ(format_vector(expand_bits<1>(5)), fmt("[1,0,1]"));
+ EXPECT_EQ(format_vector(expand_bits<1>(2)), fmt("[0,1,0]"));
+ EXPECT_EQ(format_vector(expand_bits<2>(31)), fmt("[1,1,1,1,1]"));
+ EXPECT_EQ(format_vector(expand_bits<2>(21)), fmt("[1,0,1,0,1]"));
+ EXPECT_EQ(format_vector(expand_bits<2>(31), true), fmt("11111"));
+ EXPECT_EQ(format_vector(expand_bits<2>(21), true), fmt("10101"));
+}
+
+template <uint8_t N>
+void list_states() {
+ auto repo = make_state_repo<N>();
+ EXPECT_EQ(num_states<N>(), repo.size());
+ fprintf(stderr, "max_edits: %u, number of states: %zu\n", N, repo.size());
+ for (uint32_t i = 0; i < repo.size(); ++i) {
+ fprintf(stderr, " state %u: %s\n", i, repo.idx_to_state(i).to_string().c_str());
+ }
+}
+
+TEST(TableDfaTest, list_states_for_max_edits_1) { list_states<1>(); }
+TEST(TableDfaTest, list_states_for_max_edits_2) { list_states<2>(); }
+
+template <uint8_t N>
+void list_edits() {
+ auto repo = make_state_repo<N>();
+ fprintf(stderr,
+ "per state, listing the minimal number of edits needed\n"
+ "to reach offsets at and beyond its minimal boundary\n");
+ for (uint32_t i = 0; i < repo.size(); ++i) {
+ const State &state = repo.idx_to_state(i);
+ fprintf(stderr, "%-23s : %s\n", state.to_string().c_str(),
+ format_vector(state.make_edit_vector<N>()).c_str());
+ }
+}
+
+TEST(TableDfaTest, list_edits_at_input_end_for_max_edits_1) { list_edits<1>(); }
+TEST(TableDfaTest, list_edits_at_input_end_for_max_edits_2) { list_edits<2>(); }
+
+template <uint8_t N>
+void list_transitions() {
+ auto repo = make_state_repo<N>();
+ for (uint32_t idx = 0; idx < repo.size(); ++idx) {
+ const State &state = repo.idx_to_state(idx);
+ for (uint32_t i = 0; i < num_transitions<N>(); ++i) {
+ auto bits = expand_bits<N>(i);
+ State new_state = state.next<N>(bits);
+ uint32_t step = new_state.normalize();
+ uint32_t new_idx = repo.state_to_idx(new_state);
+ ASSERT_LT(new_idx, repo.size());
+ fprintf(stderr, "%u:%s,i --%s--> %u:%s,%s\n", idx, state.to_string().c_str(),
+ format_vector(bits).c_str(), new_idx, new_state.to_string().c_str(),
+ (step == 0) ? "i" : fmt("i+%u", step).c_str());
+ }
+ }
+}
+
+TEST(TableDfaTest, list_transitions_for_max_edits_1) { list_transitions<1>(); }
+
+// Simulate all possible ways we can approach the end of the word we
+// are matching. Verify that no transition taken can produce a state
+// with a minimal boundary that exceeds the boundary of the word
+// itself. Verifying this will enable us to not care about word size
+// while simulating the dfa.
+template <uint8_t N>
+void verify_word_end_boundary() {
+ auto repo = make_state_repo<N>();
+ using StateSet = std::set<uint32_t>;
+ std::vector<StateSet> active(window_size<N>() + 1);
+ for (size_t i = 1; i < repo.size(); ++i) {
+ active[0].insert(i);
+ }
+ EXPECT_EQ(active.size(), window_size<N>() + 1);
+ EXPECT_EQ(active[0].size(), repo.size() - 1);
+ fprintf(stderr, "verifying word end for max edits %u\n", N);
+ uint32_t edge_shape = 0;
+ for (uint32_t active_idx = 0; active_idx < active.size(); ++active_idx) {
+ fprintf(stderr, " edge shape: %s, max step: %zu, active_states: %zu\n",
+ format_vector(expand_bits<N>(edge_shape)).c_str(), active.size() - active_idx - 1, active[active_idx].size());
+ for (uint32_t idx: active[active_idx]) {
+ const State &state = repo.idx_to_state(idx);
+ for (uint32_t i = 0; i < num_transitions<N>(); ++i) {
+ if ((i & edge_shape) == 0) {
+ State new_state = state.next<N>(expand_bits<N>(i));
+ uint32_t step = new_state.normalize();
+ uint32_t new_idx = repo.state_to_idx(new_state);
+ ASSERT_LT(new_idx, repo.size());
+ if (new_idx != 0) {
+ ASSERT_GT(active.size(), active_idx + step);
+ active[active_idx + step].insert(new_idx);
+ }
+ }
+ }
+ }
+ edge_shape = (edge_shape << 1) + 1;
+ }
+ EXPECT_EQ(edge_shape, (1 << (window_size<N>() + 1)) - 1);
+ while (!active.back().empty()) {
+ fprintf(stderr, " residue states after word end: %zu\n", active.back().size());
+ StateSet residue;
+ for (uint32_t idx: active.back()) {
+ const State &state = repo.idx_to_state(idx);
+ State new_state = state.next<N>(expand_bits<N>(0));
+ uint32_t step = new_state.normalize();
+ uint32_t new_idx = repo.state_to_idx(new_state);
+ ASSERT_LT(new_idx, repo.size());
+ ASSERT_EQ(step, 0);
+ if (new_idx != 0) {
+ residue.insert(new_idx);
+ }
+ }
+ active.back() = std::move(residue);
+ }
+}
+
+TEST(TableDfaTest, minimal_boundary_will_never_exceed_word_end_with_max_edits_1) {
+ verify_word_end_boundary<1>();
+}
+
+TEST(TableDfaTest, minimal_boundary_will_never_exceed_word_end_with_max_edits_2) {
+ verify_word_end_boundary<2>();
+}
+
+template <uint8_t N>
+void verify_inline_tfa() {
+ auto tfa = make_tfa<N>();
+ fprintf(stderr, "verifying TFA for N = %u (byte size: %zu)\n", N, sizeof(*tfa));
+ ASSERT_EQ(tfa->table.size(), num_states<N>());
+ ASSERT_EQ(tfa->edits.size(), num_states<N>());
+ for (size_t state = 0; state < num_states<N>(); ++state) {
+ ASSERT_EQ(tfa->table[state].size(), num_transitions<N>());
+ for (size_t transition = 0; transition < num_transitions<N>(); ++transition) {
+ EXPECT_EQ(tfa->table[state][transition].step, InlineTfa<N>::table[state][transition].step);
+ EXPECT_EQ(tfa->table[state][transition].state, InlineTfa<N>::table[state][transition].state);
+ }
+ ASSERT_EQ(tfa->edits[state].size(), window_size<N>());
+ for (size_t offset = 0; offset < window_size<N>(); ++offset) {
+ EXPECT_EQ(tfa->edits[state][offset], InlineTfa<N>::edits[state][offset]);
+ }
+ }
+}
+
+TEST(TableDfaTest, verify_inline_tfa_with_max_edits_1) {
+ verify_inline_tfa<1>();
+}
+
+TEST(TableDfaTest, verify_inline_tfa_with_max_edits_2) {
+ verify_inline_tfa<2>();
+}
+
+template <uint8_t N>
+void dump_tfa_as_code() {
+ auto tfa = make_tfa<N>();
+ fprintf(stderr, "// start of auto-generated code for N = %u\n", N);
+ fprintf(stderr, "template <> struct InlineTfa<%u> {\n", N);
+ fprintf(stderr, " static constexpr Transition table[%zu][%zu] = {\n", num_states<N>(), num_transitions<N>());
+ for (size_t state = 0; state < num_states<N>(); ++state) {
+ fprintf(stderr, " {");
+ for (size_t transition = 0; transition < num_transitions<N>(); ++transition) {
+ if (transition > 0) {
+ fprintf(stderr, ",");
+ }
+ fprintf(stderr, "{%u,%u}", tfa->table[state][transition].step, tfa->table[state][transition].state);
+ }
+ fprintf(stderr, "}%s\n", ((state + 1) < num_states<N>()) ? "," : "");
+ }
+ fprintf(stderr, " };\n");
+ fprintf(stderr, " static constexpr uint8_t edits[%zu][%zu] = {\n", num_states<N>(), window_size<N>());
+ for (size_t state = 0; state < num_states<N>(); ++state) {
+ fprintf(stderr, " {");
+ for (size_t offset = 0; offset < window_size<N>(); ++offset) {
+ if (offset > 0) {
+ fprintf(stderr, ",");
+ }
+ fprintf(stderr, "%u", tfa->edits[state][offset]);
+ }
+ fprintf(stderr, "}%s\n", ((state + 1) < num_states<N>()) ? "," : "");
+ }
+ fprintf(stderr, " };\n");
+ fprintf(stderr, "};\n");
+ fprintf(stderr, "// end of auto-generated code for N = %u\n", N);
+}
+
+TEST(TableDfaTest, dump_tfa_with_max_edits_1_as_code) {
+ dump_tfa_as_code<1>();
+}
+
+TEST(TableDfaTest, dump_tfa_with_max_edits_2_as_code) {
+ dump_tfa_as_code<2>();
+}
+
+template <uint8_t N>
+void dump_tfa_graph() {
+ auto repo = make_state_repo<N>();
+ fprintf(stderr, "digraph tfa {\n");
+ for (uint32_t idx = 0; idx < repo.size(); ++idx) {
+ fprintf(stderr, " %u [label=\"%s\"];\n", idx,
+ repo.idx_to_state(idx).to_string().c_str());
+ }
+ // omit transitions from the failure state to itself
+ for (uint32_t idx = 1; idx < repo.size(); ++idx) {
+ const State &state = repo.idx_to_state(idx);
+ for (uint32_t i = 0; i < num_transitions<N>(); ++i) {
+ auto bits = expand_bits<N>(i);
+ State new_state = state.next<N>(bits);
+ uint32_t step = new_state.normalize();
+ uint32_t new_idx = repo.state_to_idx(new_state);
+ ASSERT_LT(new_idx, repo.size());
+ if (bits[0] && idx == new_idx && step == 1) {
+ // omit simple transitions to yourself
+ } else {
+ fprintf(stderr, " %u -> %u [label=\"%s,%u\"];\n", idx, new_idx,
+ format_vector(bits, true).c_str(), step);
+ }
+ }
+ }
+ fprintf(stderr, "}\n");
+}
+
+TEST(TableDfaTest, graphviz_for_tfa_with_max_edits_1) {
+ dump_tfa_graph<1>();
+}
+
+TEST(TableDfaTest, graphviz_for_food_with_max_edits_1) {
+ auto dfa = LevenshteinDfa::build("food", 1, LevenshteinDfa::Casing::Cased, LevenshteinDfa::DfaType::Table);
+ std::ostringstream out;
+ dfa.dump_as_graphviz(out);
+ fprintf(stderr, "memory usage: %zu\n", dfa.memory_usage());
+ fprintf(stderr, "%s", out.str().c_str());
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/memorydatastore/memorydatastore.cpp b/vespalib/src/tests/memorydatastore/memorydatastore.cpp
index 1d49b0af91b..7eab32601de 100644
--- a/vespalib/src/tests/memorydatastore/memorydatastore.cpp
+++ b/vespalib/src/tests/memorydatastore/memorydatastore.cpp
@@ -6,19 +6,9 @@
using namespace vespalib;
-class MemoryDataStoreTest : public vespalib::TestApp
+TEST("testMemoryDataStore")
{
-private:
- void testMemoryDataStore();
- void testVariableSizeVector();
-public:
- int Main() override;
-};
-
-void
-MemoryDataStoreTest::testMemoryDataStore()
-{
- MemoryDataStore s(alloc::Alloc::alloc(256));
+ MemoryDataStore s(alloc::Alloc::alloc(256), nullptr);
std::vector<MemoryDataStore::Reference> v;
v.push_back(s.push_back("mumbo", 5));
for (size_t i(0); i < 50; i++) {
@@ -28,45 +18,9 @@ MemoryDataStoreTest::testMemoryDataStore()
v.push_back(s.push_back("mumbo", 5));
EXPECT_EQUAL(52ul, v.size());
EXPECT_NOT_EQUAL(static_cast<const char *>(v[50].data()) + 5, v[51].data());
- for (size_t i(0); i < v.size(); i++) {
- EXPECT_EQUAL(0, memcmp("mumbo", v[i].data(), 5));
+ for (auto & i : v) {
+ EXPECT_EQUAL(0, memcmp("mumbo", i.data(), 5));
}
}
-void
-MemoryDataStoreTest::testVariableSizeVector()
-{
- VariableSizeVector v(20000, 5*20000);
- for (size_t i(0); i < 10000; i++) {
- asciistream os;
- os << i;
- v.push_back(os.str().data(), os.str().size());
- }
- for (size_t i(0); i < v.size(); i++) {
- asciistream os;
- os << i;
- EXPECT_EQUAL(os.str().size(), v[i].size());
- EXPECT_EQUAL(0, memcmp(os.str().data(), v[i].data(), os.str().size()));
- }
- size_t i(0);
- for (auto it(v.begin()), mt(v.end()); it != mt; it++, i++) {
- asciistream os;
- os << i;
- EXPECT_EQUAL(os.str().size(), it->size());
- EXPECT_EQUAL(0, memcmp(os.str().data(), (*it).data(), os.str().size()));
- }
-
-}
-
-int
-MemoryDataStoreTest::Main()
-{
- TEST_INIT("data_test");
- testMemoryDataStore();
- testVariableSizeVector();
-
- TEST_DONE();
-}
-
-TEST_APPHOOK(MemoryDataStoreTest);
-
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/stllike/hash_test.cpp b/vespalib/src/tests/stllike/hash_test.cpp
index ae27d2dc58b..dc6e48100a9 100644
--- a/vespalib/src/tests/stllike/hash_test.cpp
+++ b/vespalib/src/tests/stllike/hash_test.cpp
@@ -494,6 +494,14 @@ TEST("test hash set initializer list - empty")
EXPECT_EQUAL(0u, s.size());
}
+TEST("empty hash_set can be looked up")
+{
+ IntHashSet s;
+ EXPECT_EQUAL(0u, s.size());
+ EXPECT_EQUAL(1u, s.capacity());
+ EXPECT_TRUE(s.find(1) == s.end());
+}
+
TEST("test hash set initializer list - 1 element")
{
IntHashSet s = {1};
diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.h b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
index 2418da18c23..9480e5880e0 100644
--- a/vespalib/src/vespa/vespalib/btree/btreeiterator.h
+++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
@@ -302,22 +302,22 @@ public:
/**
* Get key at current iterator location.
*/
- const KeyType & getKey() const { return _leaf.getKey(); }
+ const KeyType & getKey() const noexcept { return _leaf.getKey(); }
/**
* Get data at current iterator location.
*/
- const DataType & getData() const { return _leaf.getData(); }
+ const DataType & getData() const noexcept { return _leaf.getData(); }
/**
* Check if iterator is at a valid element, i.e. not at end.
*/
- bool valid() const { return _leaf.valid(); }
+ bool valid() const noexcept{ return _leaf.valid(); }
/**
* Return the number of elements in the tree.
*/
- size_t size() const;
+ size_t size() const noexcept;
/**
@@ -333,7 +333,7 @@ public:
/**
* Return if the tree has data or not (e.g. keys and data or only keys).
*/
- static bool hasData() { return LeafNodeType::hasData(); }
+ static bool hasData() noexcept { return LeafNodeType::hasData(); }
/**
* Move the iterator directly to end. Used by findHelper method in BTree.
diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
index b7927feaa1a..d6dda0047ce 100644
--- a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
@@ -387,15 +387,13 @@ position(uint32_t levels) const
res += inode->validLeaves();
for (uint32_t c = elem.getIdx(); c < slots; ++c) {
BTreeNode::Ref node = inode->getChild(c);
- const InternalNodeType *jnode =
- _allocator->mapInternalRef(node);
+ const InternalNodeType *jnode = _allocator->mapInternalRef(node);
res -= jnode->validLeaves();
}
} else {
for (uint32_t c = 0; c < elem.getIdx(); ++c) {
BTreeNode::Ref node = inode->getChild(c);
- const InternalNodeType *jnode =
- _allocator->mapInternalRef(node);
+ const InternalNodeType *jnode = _allocator->mapInternalRef(node);
res += jnode->validLeaves();
}
}
@@ -484,7 +482,7 @@ template <typename KeyT, typename DataT, typename AggrT,
uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE>
size_t
BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>::
-size() const
+size() const noexcept
{
if (_pathSize > 0) {
return _path[_pathSize - 1].getNode()->validLeaves();
diff --git a/vespalib/src/vespa/vespalib/btree/btreenode.h b/vespalib/src/vespa/vespalib/btree/btreenode.h
index 0a77a0b4685..4931021d771 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenode.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenode.h
@@ -67,14 +67,14 @@ public:
using Ref = datastore::EntryRef;
using ChildRef = datastore::AtomicEntryRef;
- bool isLeaf() const { return _level == 0u; }
- bool getFrozen() const { return _isFrozen; }
- void freeze() { _isFrozen = true; }
- void unFreeze() { _isFrozen = false; }
- void setLevel(uint8_t level) { _level = level; }
- uint32_t getLevel() const { return _level; }
- uint32_t validSlots() const { return _validSlots; }
- void setValidSlots(uint16_t validSlots_) { _validSlots = validSlots_; }
+ bool isLeaf() const noexcept { return _level == 0u; }
+ bool getFrozen() const noexcept { return _isFrozen; }
+ void freeze() noexcept { _isFrozen = true; }
+ void unFreeze() noexcept { _isFrozen = false; }
+ void setLevel(uint8_t level) noexcept { _level = level; }
+ uint32_t getLevel() const noexcept { return _level; }
+ uint32_t validSlots() const noexcept { return _validSlots; }
+ void setValidSlots(uint16_t validSlots_) noexcept { _validSlots = validSlots_; }
};
@@ -358,7 +358,7 @@ public:
void insert(uint32_t idx, const KeyT & key, BTreeNode::Ref child) {
insert(idx, key, BTreeNode::ChildRef(child));
}
- uint32_t validLeaves() const { return _validLeaves; }
+ uint32_t validLeaves() const noexcept { return _validLeaves; }
void setValidLeaves(uint32_t newValidLeaves) { _validLeaves = newValidLeaves; }
void incValidLeaves(uint32_t delta) { _validLeaves += delta; }
void decValidLeaves(uint32_t delta) { _validLeaves -= delta; }
diff --git a/vespalib/src/vespa/vespalib/data/memorydatastore.cpp b/vespalib/src/vespa/vespalib/data/memorydatastore.cpp
index 354787690c2..6d483e6ff4e 100644
--- a/vespalib/src/vespa/vespalib/data/memorydatastore.cpp
+++ b/vespalib/src/vespa/vespalib/data/memorydatastore.cpp
@@ -41,21 +41,4 @@ MemoryDataStore::push_back(const void * data, const size_t sz)
return ref;
}
-VariableSizeVector::VariableSizeVector(size_t initialCount, size_t initialBufferSize)
- : _vector(),
- _store(Alloc::alloc(initialBufferSize))
-{
- _vector.reserve(initialCount);
-}
-
-VariableSizeVector::~VariableSizeVector() = default;
-
-VariableSizeVector::Reference
-VariableSizeVector::push_back(const void * data, const size_t sz)
-{
- MemoryDataStore::Reference ptr(_store.push_back(data, sz));
- _vector.push_back(Reference(ptr.data(), sz));
- return _vector.back();
-}
-
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/data/memorydatastore.h b/vespalib/src/vespa/vespalib/data/memorydatastore.h
index 7022eb88051..6691211cdd0 100644
--- a/vespalib/src/vespa/vespalib/data/memorydatastore.h
+++ b/vespalib/src/vespa/vespalib/data/memorydatastore.h
@@ -13,18 +13,19 @@ namespace vespalib {
* It has the important property that once an object has been allocated it does not move in memory.
* It will start of by allocating one backing buffer and items stored will be appended here.
* When limit is exceeded a new buffer is allocated with twice the size of the previous and so it goes.
+ * You can also provide an optional lock to make it thread safe.
**/
class MemoryDataStore {
public:
class Reference {
public:
- Reference(void * data_) noexcept : _data(data_) { }
+ explicit Reference(void * data_) noexcept : _data(data_) { }
void * data() noexcept { return _data; }
const char * c_str() const noexcept { return static_cast<const char *>(_data); }
private:
void * _data;
};
- MemoryDataStore(alloc::Alloc && initialAlloc=alloc::Alloc::alloc(256), std::mutex * lock=nullptr);
+ MemoryDataStore(alloc::Alloc && initialAlloc, std::mutex * lock);
MemoryDataStore(const MemoryDataStore &) = delete;
MemoryDataStore & operator = (const MemoryDataStore &) = delete;
~MemoryDataStore();
@@ -33,7 +34,7 @@ public:
* for the lifetime of this object.
* @return A pointer/reference to the freshly stored object.
*/
- Reference push_back(const void * data, const size_t sz);
+ Reference push_back(const void * data, size_t sz);
void swap(MemoryDataStore & rhs) { _buffers.swap(rhs._buffers); }
void clear() noexcept {
_buffers.clear();
@@ -44,83 +45,5 @@ private:
std::mutex * _lock;
};
-class VariableSizeVector
-{
-public:
- class Reference {
- public:
- Reference(void * data_, size_t sz) noexcept : _data(data_), _sz(sz) { }
- void * data() noexcept { return _data; }
- const char * c_str() const noexcept { return static_cast<const char *>(_data); }
- size_t size() const noexcept { return _sz; }
- private:
- void * _data;
- size_t _sz;
- };
- class iterator {
- public:
- iterator(vespalib::Array<Reference> & v, size_t index) noexcept : _vector(&v), _index(index) {}
- Reference & operator * () const noexcept { return (*_vector)[_index]; }
- Reference * operator -> () const noexcept { return &(*_vector)[_index]; }
- iterator & operator ++ () noexcept {
- _index++;
- return *this;
- }
- iterator operator ++ (int) noexcept {
- iterator prev = *this;
- ++(*this);
- return prev;
- }
- bool operator==(const iterator& rhs) const noexcept { return (_index == rhs._index); }
- bool operator!=(const iterator& rhs) const noexcept { return (_index != rhs._index); }
- private:
- vespalib::Array<Reference> * _vector;
- size_t _index;
- };
- class const_iterator {
- public:
- const_iterator(const vespalib::Array<Reference> & v, size_t index) noexcept : _vector(&v), _index(index) {}
- const Reference & operator * () const noexcept { return (*_vector)[_index]; }
- const Reference * operator -> () const noexcept { return &(*_vector)[_index]; }
- const_iterator & operator ++ () noexcept {
- _index++;
- return *this;
- }
- const_iterator operator ++ (int) noexcept {
- const_iterator prev = *this;
- ++(*this);
- return prev;
- }
- bool operator==(const const_iterator& rhs) const noexcept { return (_index == rhs._index); }
- bool operator!=(const const_iterator& rhs) const noexcept { return (_index != rhs._index); }
- private:
- const vespalib::Array<Reference> * _vector;
- size_t _index;
- };
- VariableSizeVector(const VariableSizeVector &) = delete;
- VariableSizeVector & operator = (const VariableSizeVector &) = delete;
- VariableSizeVector(size_t initialCount, size_t initialBufferSize);
- ~VariableSizeVector();
- iterator begin() noexcept { return iterator(_vector, 0); }
- iterator end() noexcept { return iterator(_vector, size()); }
- const_iterator begin() const noexcept { return const_iterator(_vector, 0); }
- const_iterator end() const noexcept { return const_iterator(_vector, size()); }
- Reference push_back(const void * data, const size_t sz);
- Reference operator [] (uint32_t index) const noexcept { return _vector[index]; }
- size_t size() const noexcept { return _vector.size(); }
- bool empty() const noexcept { return _vector.empty(); }
- void swap(VariableSizeVector & rhs) noexcept {
- _vector.swap(rhs._vector);
- _store.swap(rhs._store);
- }
- void clear() {
- _vector.clear();
- _store.clear();
- }
-private:
- vespalib::Array<Reference> _vector;
- MemoryDataStore _store;
-};
-
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/data/slime/named_symbol_lookup.h b/vespalib/src/vespa/vespalib/data/slime/named_symbol_lookup.h
index 44dbf05c9da..fb4bc3943ae 100644
--- a/vespalib/src/vespa/vespalib/data/slime/named_symbol_lookup.h
+++ b/vespalib/src/vespa/vespalib/data/slime/named_symbol_lookup.h
@@ -20,7 +20,7 @@ private:
const Memory &_name;
public:
- NamedSymbolLookup(const SymbolTable &table, const Memory &name)
+ NamedSymbolLookup(const SymbolTable &table, const Memory &name) noexcept
: _table(table), _name(name) {}
Symbol lookup() const override;
};
diff --git a/vespalib/src/vespa/vespalib/data/slime/slime.cpp b/vespalib/src/vespa/vespalib/data/slime/slime.cpp
index d6fac44b360..5a9ec54584d 100644
--- a/vespalib/src/vespa/vespalib/data/slime/slime.cpp
+++ b/vespalib/src/vespa/vespalib/data/slime/slime.cpp
@@ -6,16 +6,6 @@
namespace vespalib {
-Slime::Params::Params() : Params(std::make_unique<SymbolTable>()) { }
-Slime::Params::Params(std::unique_ptr<SymbolTable> symbols) noexcept : _symbols(std::move(symbols)), _chunkSize(4096) { }
-Slime::Params::Params(Params &&) noexcept = default;
-Slime::Params::~Params() = default;
-
-std::unique_ptr<slime::SymbolTable>
-Slime::Params::detachSymbols() {
- return std::move(_symbols);
-}
-
Slime::Slime(Params params)
: _names(params.detachSymbols()),
_stash(std::make_unique<Stash>(params.getChunkSize())),
@@ -33,26 +23,6 @@ Slime::reclaimSymbols(Slime &&rhs) {
return std::move(rhs._names);
}
-size_t
-Slime::symbols() const noexcept {
- return _names->symbols();
-}
-
-Memory
-Slime::inspect(Symbol symbol) const {
- return _names->inspect(symbol);
-}
-
-slime::Symbol
-Slime::insert(Memory name) {
- return _names->insert(name);
-}
-
-slime::Symbol
-Slime::lookup(Memory name) const {
- return _names->lookup(name);
-}
-
bool operator == (const Slime & a, const Slime & b) noexcept
{
return a.get() == b.get();
diff --git a/vespalib/src/vespa/vespalib/data/slime/slime.h b/vespalib/src/vespa/vespalib/data/slime/slime.h
index a426f906563..4b789838009 100644
--- a/vespalib/src/vespa/vespalib/data/slime/slime.h
+++ b/vespalib/src/vespa/vespalib/data/slime/slime.h
@@ -21,6 +21,7 @@
#include "symbol.h"
#include "symbol_inserter.h"
#include "symbol_lookup.h"
+#include "symbol_table.h"
#include "type.h"
#include "value.h"
#include "value_factory.h"
@@ -51,32 +52,30 @@ private:
using Cursor = slime::Cursor;
using Inspector = slime::Inspector;
- std::unique_ptr<SymbolTable> _names;
- std::unique_ptr<Stash> _stash;
- RootValue _root;
+ std::unique_ptr<SymbolTable> _names;
+ std::unique_ptr<Stash> _stash;
+ RootValue _root;
public:
using UP = std::unique_ptr<Slime>;
class Params {
private:
- std::unique_ptr<SymbolTable> _symbols;
- size_t _chunkSize;
+ std::unique_ptr<SymbolTable> _symbols;
+ size_t _chunkSize;
public:
- Params();
- explicit Params(std::unique_ptr<SymbolTable> symbols) noexcept;
- Params(Params &&) noexcept;
- ~Params();
- Params & setChunkSize(size_t chunkSize) {
- _chunkSize = chunkSize;
- return *this;
- }
- size_t getChunkSize() const { return _chunkSize; }
- std::unique_ptr<SymbolTable> detachSymbols();
+ Params() : Params(4096) {}
+ explicit Params(size_t chunkSize) : _symbols(std::make_unique<SymbolTable>()), _chunkSize(chunkSize) {}
+ explicit Params(std::unique_ptr<SymbolTable> symbols) noexcept : _symbols(std::move(symbols)), _chunkSize(4096) {}
+ Params(Params &&) noexcept = default;
+ ~Params() = default;
+ size_t getChunkSize() const noexcept { return _chunkSize; }
+ std::unique_ptr<SymbolTable> detachSymbols() noexcept { return std::move(_symbols); }
};
/**
* Construct an initially empty Slime object.
**/
- explicit Slime(Params params = Params());
+ explicit Slime() : Slime(Params()) {}
+ explicit Slime(Params params);
~Slime();
@@ -88,13 +87,13 @@ public:
static std::unique_ptr<SymbolTable> reclaimSymbols(Slime &&rhs);
- size_t symbols() const noexcept;
+ size_t symbols() const noexcept { return _names->symbols(); }
- Memory inspect(Symbol symbol) const;
+ Memory inspect(Symbol symbol) const { return _names->inspect(symbol); }
- Symbol insert(Memory name);
+ Symbol insert(Memory name) { return _names->insert(name); }
- Symbol lookup(Memory name) const;
+ Symbol lookup(Memory name) const { return _names->lookup(name); }
Cursor &get() noexcept { return _root.get(); }
diff --git a/vespalib/src/vespa/vespalib/data/slime/symbol.h b/vespalib/src/vespa/vespalib/data/slime/symbol.h
index 3bce727fad9..a60a49fda27 100644
--- a/vespalib/src/vespa/vespalib/data/slime/symbol.h
+++ b/vespalib/src/vespa/vespalib/data/slime/symbol.h
@@ -19,8 +19,8 @@ private:
public:
Symbol() noexcept : _value(UNDEFINED) {}
Symbol(uint32_t v) noexcept : _value(v) {}
- bool undefined() const { return (_value == UNDEFINED); }
- uint32_t getValue() const { return _value; }
+ bool undefined() const noexcept { return (_value == UNDEFINED); }
+ uint32_t getValue() const noexcept { return _value; }
bool operator<(const Symbol &rhs) const noexcept { return (_value < rhs._value); }
bool operator==(const Symbol &rhs) const noexcept { return (_value == rhs._value); }
};
diff --git a/vespalib/src/vespa/vespalib/data/slime/symbol_table.cpp b/vespalib/src/vespa/vespalib/data/slime/symbol_table.cpp
index a3313516c64..dffe35707fc 100644
--- a/vespalib/src/vespa/vespalib/data/slime/symbol_table.cpp
+++ b/vespalib/src/vespa/vespalib/data/slime/symbol_table.cpp
@@ -5,10 +5,13 @@
namespace vespalib::slime {
-SymbolTable::SymbolTable(size_t expectedNumSymbols) :
- _symbols(3*expectedNumSymbols),
- _names(expectedNumSymbols, expectedNumSymbols*16)
-{ }
+SymbolTable::SymbolTable(size_t expectedNumSymbols)
+ : _stash(),
+ _symbols(3*expectedNumSymbols),
+ _names()
+{
+ _names.reserve(expectedNumSymbols);
+}
SymbolTable::~SymbolTable() = default;
@@ -16,6 +19,7 @@ void
SymbolTable::clear() {
_names.clear();
_symbols.clear();
+ _stash.clear();
}
Symbol
@@ -23,17 +27,21 @@ SymbolTable::insert(const Memory &name) {
SymbolMap::const_iterator pos = _symbols.find(name);
if (pos == _symbols.end()) {
Symbol symbol(_names.size());
- SymbolVector::Reference r(_names.push_back(name.data, name.size));
- _symbols.insert(std::make_pair(Memory(r.c_str(), r.size()), symbol));
+ char *buf = _stash.alloc(name.size);
+ memcpy(buf, name.data, name.size);
+ Memory backed(buf, name.size);
+ _names.push_back(backed);
+ _symbols.insert(std::make_pair(backed, symbol));
return symbol;
}
return pos->second;
}
+
Symbol
SymbolTable::lookup(const Memory &name) const {
SymbolMap::const_iterator pos = _symbols.find(name);
if (pos == _symbols.end()) {
- return Symbol();
+ return {};
}
return pos->second;
}
diff --git a/vespalib/src/vespa/vespalib/data/slime/symbol_table.h b/vespalib/src/vespa/vespalib/data/slime/symbol_table.h
index c5f3cf12fd6..0eae65cead0 100644
--- a/vespalib/src/vespa/vespalib/data/slime/symbol_table.h
+++ b/vespalib/src/vespa/vespalib/data/slime/symbol_table.h
@@ -4,8 +4,8 @@
#include "symbol.h"
#include <vespa/vespalib/data/memory.h>
+#include <vespa/vespalib/util/stash.h>
#include <vespa/vespalib/stllike/hash_map.h>
-#include <vespa/vespalib/data/memorydatastore.h>
namespace vespalib::slime {
@@ -21,21 +21,24 @@ private:
}
};
using SymbolMap = hash_map<Memory, Symbol, hasher>;
- using SymbolVector = VariableSizeVector;
+ using SymbolVector = std::vector<Memory>;
+ Stash _stash;
SymbolMap _symbols;
SymbolVector _names;
public:
using UP = std::unique_ptr<SymbolTable>;
- SymbolTable(size_t expectedNumSymbols=16);
+ SymbolTable() : SymbolTable(16) {}
+ explicit SymbolTable(size_t expectedNumSymbols);
+ SymbolTable(SymbolTable &&) noexcept = default;
+ SymbolTable & operator=(SymbolTable &&) noexcept = default;
~SymbolTable();
size_t symbols() const noexcept { return _names.size(); }
Memory inspect(const Symbol &symbol) const {
if (symbol.getValue() > _names.size()) {
return Memory();
}
- SymbolVector::Reference r(_names[symbol.getValue()]);
- return Memory(r.c_str(), r.size());
+ return _names[symbol.getValue()];
}
Symbol insert(const Memory &name);
Symbol lookup(const Memory &name) const;
diff --git a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
index dd41ed244ae..d3cdc174339 100644
--- a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
+++ b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
@@ -2,8 +2,9 @@
#pragma once
-#include <iosfwd>
+#include <cstddef>
#include <cstdint>
+#include <iosfwd>
namespace vespalib {
diff --git a/vespalib/src/vespa/vespalib/fuzzy/CMakeLists.txt b/vespalib/src/vespa/vespalib/fuzzy/CMakeLists.txt
index 5e8d29980cd..8ccef84d969 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/fuzzy/CMakeLists.txt
@@ -7,6 +7,7 @@ vespa_add_library(vespalib_vespalib_fuzzy OBJECT
implicit_levenshtein_dfa.cpp
levenshtein_dfa.cpp
levenshtein_distance.cpp
+ table_dfa.cpp
unicode_utils.cpp
DEPENDS
)
diff --git a/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.cpp b/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.cpp
index 826b0beffd6..d87afef1fbe 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.cpp
+++ b/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.cpp
@@ -9,6 +9,7 @@ namespace {
const vespalib::string brute_force = "brute_force";
const vespalib::string dfa_implicit = "dfa_implicit";
const vespalib::string dfa_explicit = "dfa_explicit";
+const vespalib::string dfa_table = "dfa_table";
}
@@ -22,6 +23,8 @@ to_string(FuzzyMatchingAlgorithm algo)
return dfa_implicit;
case FuzzyMatchingAlgorithm::DfaExplicit:
return dfa_explicit;
+ case FuzzyMatchingAlgorithm::DfaTable:
+ return dfa_table;
default:
return "";
}
@@ -37,6 +40,8 @@ fuzzy_matching_algorithm_from_string(const vespalib::string& algo,
return FuzzyMatchingAlgorithm::DfaImplicit;
} else if (algo == dfa_explicit) {
return FuzzyMatchingAlgorithm::DfaExplicit;
+ } else if (algo == dfa_table) {
+ return FuzzyMatchingAlgorithm::DfaTable;
}
return default_algo;
}
diff --git a/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h b/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h
index 83cb121fe5f..9af94e84b89 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h
+++ b/vespalib/src/vespa/vespalib/fuzzy/fuzzy_matching_algorithm.h
@@ -13,7 +13,8 @@ namespace vespalib {
enum class FuzzyMatchingAlgorithm {
BruteForce,
DfaImplicit,
- DfaExplicit
+ DfaExplicit,
+ DfaTable
};
vespalib::string to_string(FuzzyMatchingAlgorithm algo);
diff --git a/vespalib/src/vespa/vespalib/fuzzy/inline_tfa.hpp b/vespalib/src/vespa/vespalib/fuzzy/inline_tfa.hpp
new file mode 100644
index 00000000000..2475704ec06
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/fuzzy/inline_tfa.hpp
@@ -0,0 +1,91 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// start of auto-generated code for N = 1
+template <> struct InlineTfa<1> {
+ static constexpr Transition table[6][8] = {
+ {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}},
+ {{0,2},{0,2},{0,3},{0,3},{1,1},{1,1},{1,1},{1,1}},
+ {{0,0},{0,0},{2,4},{2,4},{1,4},{1,4},{1,2},{1,2}},
+ {{0,0},{3,4},{2,4},{2,2},{1,4},{1,5},{1,2},{1,3}},
+ {{0,0},{0,0},{0,0},{0,0},{1,4},{1,4},{1,4},{1,4}},
+ {{0,0},{3,4},{0,0},{3,4},{1,4},{1,5},{1,4},{1,5}}
+ };
+ static constexpr uint8_t edits[6][3] = {
+ {2,2,2},
+ {0,1,2},
+ {1,1,2},
+ {1,1,1},
+ {1,2,2},
+ {1,2,1}
+ };
+};
+// end of auto-generated code for N = 1
+// start of auto-generated code for N = 2
+template <> struct InlineTfa<2> {
+ static constexpr Transition table[31][32] = {
+ {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}},
+ {{0,2},{0,2},{0,2},{0,2},{0,3},{0,3},{0,3},{0,3},{0,4},{0,4},{0,4},{0,4},{0,4},{0,4},{0,4},{0,4},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1},{1,1}},
+ {{0,5},{0,5},{0,5},{0,5},{0,6},{0,6},{0,6},{0,6},{0,7},{0,7},{0,7},{0,7},{0,7},{0,7},{0,7},{0,7},{1,8},{1,8},{1,8},{1,8},{1,9},{1,9},{1,9},{1,9},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2}},
+ {{0,5},{0,5},{0,10},{0,10},{0,6},{0,6},{0,11},{0,11},{0,7},{0,7},{0,12},{0,12},{0,7},{0,7},{0,12},{0,12},{1,8},{1,8},{1,13},{1,13},{1,9},{1,9},{1,14},{1,14},{1,2},{1,2},{1,3},{1,3},{1,2},{1,2},{1,3},{1,3}},
+ {{0,6},{0,6},{0,11},{0,11},{0,15},{0,15},{0,15},{0,15},{0,7},{0,7},{0,12},{0,12},{0,16},{0,16},{0,16},{0,16},{1,9},{1,9},{1,14},{1,14},{1,17},{1,17},{1,17},{1,17},{1,2},{1,2},{1,3},{1,3},{1,4},{1,4},{1,4},{1,4}},
+ {{0,0},{0,0},{0,0},{0,0},{3,18},{3,18},{3,18},{3,18},{2,18},{2,18},{2,18},{2,18},{2,19},{2,19},{2,19},{2,19},{1,18},{1,18},{1,18},{1,18},{1,20},{1,20},{1,20},{1,20},{1,19},{1,19},{1,19},{1,19},{1,5},{1,5},{1,5},{1,5}},
+ {{0,0},{0,0},{4,18},{4,18},{3,18},{3,18},{3,19},{3,19},{2,18},{2,18},{2,20},{2,20},{2,19},{2,19},{2,5},{2,5},{1,18},{1,18},{1,21},{1,21},{1,20},{1,20},{1,22},{1,22},{1,19},{1,19},{1,23},{1,23},{1,5},{1,5},{1,6},{1,6}},
+ {{2,19},{2,19},{2,5},{2,5},{3,8},{3,8},{3,8},{3,8},{2,19},{2,19},{2,5},{2,5},{3,8},{3,8},{3,8},{3,8},{1,5},{1,5},{1,6},{1,6},{1,7},{1,7},{1,7},{1,7},{1,5},{1,5},{1,6},{1,6},{1,7},{1,7},{1,7},{1,7}},
+ {{0,19},{0,19},{0,19},{0,19},{0,19},{0,19},{0,19},{0,19},{0,5},{0,5},{0,5},{0,5},{0,5},{0,5},{0,5},{0,5},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8},{1,8}},
+ {{0,19},{0,19},{0,19},{0,19},{0,23},{0,23},{0,23},{0,23},{0,5},{0,5},{0,5},{0,5},{0,6},{0,6},{0,6},{0,6},{1,8},{1,8},{1,8},{1,8},{1,9},{1,9},{1,9},{1,9},{1,8},{1,8},{1,8},{1,8},{1,9},{1,9},{1,9},{1,9}},
+ {{0,0},{5,18},{0,0},{5,18},{3,18},{3,20},{3,18},{3,20},{2,18},{2,21},{2,18},{2,21},{2,19},{2,23},{2,19},{2,23},{1,18},{1,24},{1,18},{1,24},{1,20},{1,25},{1,20},{1,25},{1,19},{1,26},{1,19},{1,26},{1,5},{1,10},{1,5},{1,10}},
+ {{0,0},{5,18},{4,18},{4,19},{3,18},{3,20},{3,19},{3,5},{2,18},{2,21},{2,20},{2,22},{2,19},{2,23},{2,5},{2,6},{1,18},{1,24},{1,21},{1,27},{1,20},{1,25},{1,22},{1,28},{1,19},{1,26},{1,23},{1,29},{1,5},{1,10},{1,6},{1,11}},
+ {{2,19},{2,23},{2,5},{2,6},{3,8},{3,9},{3,8},{3,9},{2,19},{2,23},{2,5},{2,6},{3,8},{3,9},{3,8},{3,9},{1,5},{1,10},{1,6},{1,11},{1,7},{1,12},{1,7},{1,12},{1,5},{1,10},{1,6},{1,11},{1,7},{1,12},{1,7},{1,12}},
+ {{0,19},{0,19},{0,26},{0,26},{0,19},{0,19},{0,26},{0,26},{0,5},{0,5},{0,10},{0,10},{0,5},{0,5},{0,10},{0,10},{1,8},{1,8},{1,13},{1,13},{1,8},{1,8},{1,13},{1,13},{1,8},{1,8},{1,13},{1,13},{1,8},{1,8},{1,13},{1,13}},
+ {{0,19},{0,19},{0,26},{0,26},{0,23},{0,23},{0,29},{0,29},{0,5},{0,5},{0,10},{0,10},{0,6},{0,6},{0,11},{0,11},{1,8},{1,8},{1,13},{1,13},{1,9},{1,9},{1,14},{1,14},{1,8},{1,8},{1,13},{1,13},{1,9},{1,9},{1,14},{1,14}},
+ {{3,19},{3,5},{4,8},{4,8},{3,19},{3,5},{4,8},{4,8},{2,5},{2,6},{2,7},{2,7},{2,5},{2,6},{2,7},{2,7},{1,22},{1,28},{1,30},{1,30},{1,22},{1,28},{1,30},{1,30},{1,6},{1,11},{1,15},{1,15},{1,6},{1,11},{1,15},{1,15}},
+ {{2,5},{2,6},{2,7},{2,7},{3,8},{3,9},{3,2},{3,2},{2,5},{2,6},{2,7},{2,7},{3,8},{3,9},{3,2},{3,2},{1,6},{1,11},{1,15},{1,15},{1,7},{1,12},{1,16},{1,16},{1,6},{1,11},{1,15},{1,15},{1,7},{1,12},{1,16},{1,16}},
+ {{0,6},{0,6},{0,11},{0,11},{0,15},{0,15},{0,15},{0,15},{0,6},{0,6},{0,11},{0,11},{0,15},{0,15},{0,15},{0,15},{1,9},{1,9},{1,14},{1,14},{1,17},{1,17},{1,17},{1,17},{1,9},{1,9},{1,14},{1,14},{1,17},{1,17},{1,17},{1,17}},
+ {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18}},
+ {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{2,18},{2,18},{2,18},{2,18},{2,18},{2,18},{2,18},{2,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,18},{1,19},{1,19},{1,19},{1,19},{1,19},{1,19},{1,19},{1,19}},
+ {{0,0},{0,0},{0,0},{0,0},{3,18},{3,18},{3,18},{3,18},{0,0},{0,0},{0,0},{0,0},{3,18},{3,18},{3,18},{3,18},{1,18},{1,18},{1,18},{1,18},{1,20},{1,20},{1,20},{1,20},{1,18},{1,18},{1,18},{1,18},{1,20},{1,20},{1,20},{1,20}},
+ {{0,0},{0,0},{4,18},{4,18},{0,0},{0,0},{4,18},{4,18},{0,0},{0,0},{4,18},{4,18},{0,0},{0,0},{4,18},{4,18},{1,18},{1,18},{1,21},{1,21},{1,18},{1,18},{1,21},{1,21},{1,18},{1,18},{1,21},{1,21},{1,18},{1,18},{1,21},{1,21}},
+ {{0,0},{0,0},{4,18},{4,18},{3,18},{3,18},{3,19},{3,19},{0,0},{0,0},{4,18},{4,18},{3,18},{3,18},{3,19},{3,19},{1,18},{1,18},{1,21},{1,21},{1,20},{1,20},{1,22},{1,22},{1,18},{1,18},{1,21},{1,21},{1,20},{1,20},{1,22},{1,22}},
+ {{0,0},{0,0},{4,18},{4,18},{0,0},{0,0},{4,18},{4,18},{2,18},{2,18},{2,20},{2,20},{2,18},{2,18},{2,20},{2,20},{1,18},{1,18},{1,21},{1,21},{1,18},{1,18},{1,21},{1,21},{1,19},{1,19},{1,23},{1,23},{1,19},{1,19},{1,23},{1,23}},
+ {{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24}},
+ {{0,0},{5,18},{0,0},{5,18},{3,18},{3,20},{3,18},{3,20},{0,0},{5,18},{0,0},{5,18},{3,18},{3,20},{3,18},{3,20},{1,18},{1,24},{1,18},{1,24},{1,20},{1,25},{1,20},{1,25},{1,18},{1,24},{1,18},{1,24},{1,20},{1,25},{1,20},{1,25}},
+ {{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{0,0},{5,18},{2,18},{2,21},{2,18},{2,21},{2,18},{2,21},{2,18},{2,21},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,18},{1,24},{1,19},{1,26},{1,19},{1,26},{1,19},{1,26},{1,19},{1,26}},
+ {{0,0},{5,18},{4,18},{4,19},{0,0},{5,18},{4,18},{4,19},{0,0},{5,18},{4,18},{4,19},{0,0},{5,18},{4,18},{4,19},{1,18},{1,24},{1,21},{1,27},{1,18},{1,24},{1,21},{1,27},{1,18},{1,24},{1,21},{1,27},{1,18},{1,24},{1,21},{1,27}},
+ {{0,0},{5,18},{4,18},{4,19},{3,18},{3,20},{3,19},{3,5},{0,0},{5,18},{4,18},{4,19},{3,18},{3,20},{3,19},{3,5},{1,18},{1,24},{1,21},{1,27},{1,20},{1,25},{1,22},{1,28},{1,18},{1,24},{1,21},{1,27},{1,20},{1,25},{1,22},{1,28}},
+ {{0,0},{5,18},{4,18},{4,19},{0,0},{5,18},{4,18},{4,19},{2,18},{2,21},{2,20},{2,22},{2,18},{2,21},{2,20},{2,22},{1,18},{1,24},{1,21},{1,27},{1,18},{1,24},{1,21},{1,27},{1,19},{1,26},{1,23},{1,29},{1,19},{1,26},{1,23},{1,29}},
+ {{3,19},{3,5},{4,8},{4,8},{3,19},{3,5},{4,8},{4,8},{3,19},{3,5},{4,8},{4,8},{3,19},{3,5},{4,8},{4,8},{1,22},{1,28},{1,30},{1,30},{1,22},{1,28},{1,30},{1,30},{1,22},{1,28},{1,30},{1,30},{1,22},{1,28},{1,30},{1,30}}
+ };
+ static constexpr uint8_t edits[31][5] = {
+ {3,3,3,3,3},
+ {0,1,2,3,3},
+ {1,1,2,3,3},
+ {1,1,2,2,3},
+ {1,1,1,2,3},
+ {2,2,2,3,3},
+ {2,2,2,2,3},
+ {2,2,1,2,3},
+ {1,2,3,3,3},
+ {1,2,2,3,3},
+ {2,2,2,3,2},
+ {2,2,2,2,2},
+ {2,2,1,2,2},
+ {1,2,3,2,3},
+ {1,2,2,2,3},
+ {2,2,2,1,2},
+ {2,2,1,1,2},
+ {1,2,1,2,3},
+ {2,3,3,3,3},
+ {2,2,3,3,3},
+ {2,3,2,3,3},
+ {2,3,3,2,3},
+ {2,3,2,2,3},
+ {2,2,3,2,3},
+ {2,3,3,3,2},
+ {2,3,2,3,2},
+ {2,2,3,3,2},
+ {2,3,3,2,2},
+ {2,3,2,2,2},
+ {2,2,3,2,2},
+ {2,3,2,1,2}
+ };
+};
+// end of auto-generated code for N = 2
diff --git a/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.cpp b/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.cpp
index 1caae408176..5f6d0ae9956 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.cpp
+++ b/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "explicit_levenshtein_dfa.h"
#include "implicit_levenshtein_dfa.h"
+#include "table_dfa.h"
#include "levenshtein_dfa.h"
#include "unicode_utils.h"
#include <vespa/vespalib/util/stringfmt.h>
@@ -53,14 +54,19 @@ LevenshteinDfa LevenshteinDfa::build(std::string_view target_string, uint8_t max
} else { // max_edits == 2
return LevenshteinDfa(std::make_unique<ImplicitLevenshteinDfa<FixedMaxEditDistanceTraits<2>>>(std::move(target_string_u32), is_cased));
}
- } else { // DfaType::Explicit
+ } else if(dfa_type == DfaType::Explicit) {
if (max_edits == 1) {
return ExplicitLevenshteinDfaBuilder<FixedMaxEditDistanceTraits<1>>(std::move(target_string_u32), is_cased).build_dfa();
} else { // max_edits == 2
return ExplicitLevenshteinDfaBuilder<FixedMaxEditDistanceTraits<2>>(std::move(target_string_u32), is_cased).build_dfa();
}
+ } else { // DfaType::Table
+ if (max_edits == 1) {
+ return LevenshteinDfa(std::make_unique<TableDfa<1>>(std::move(target_string_u32), is_cased));
+ } else { // max_edits == 2
+ return LevenshteinDfa(std::make_unique<TableDfa<2>>(std::move(target_string_u32), is_cased));
+ }
}
-
}
LevenshteinDfa LevenshteinDfa::build(std::string_view target_string, uint8_t max_edits, Casing casing) {
@@ -87,9 +93,11 @@ std::ostream& operator<<(std::ostream& os, const LevenshteinDfa::MatchResult& mo
std::ostream& operator<<(std::ostream& os, LevenshteinDfa::DfaType dt) {
if (dt == LevenshteinDfa::DfaType::Implicit) {
os << "Implicit";
- } else {
- assert(dt == LevenshteinDfa::DfaType::Explicit);
+ } else if (dt == LevenshteinDfa::DfaType::Explicit) {
os << "Explicit";
+ } else {
+ assert(dt == LevenshteinDfa::DfaType::Table);
+ os << "Table";
}
return os;
}
diff --git a/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.h b/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.h
index c6ca06d4de3..1652631e968 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.h
+++ b/vespalib/src/vespa/vespalib/fuzzy/levenshtein_dfa.h
@@ -58,8 +58,8 @@ namespace vespalib::fuzzy {
* ====== Unicode support ======
*
* Matching and successor generation is fully Unicode-aware. All input strings are expected
- * to be in UTF-8, and the generated successor is also encoded as UTF-8 (with some caveats;
- * see the documentation for match()).
+ * to be in UTF-8, and the generated successor is encoded as UTF-8 (with some caveats; see
+ * the documentation for match()) or UTF-32, depending on the chosen `match()` overload.
*
* Internally, matching is done on UTF-32 code points and the DFA itself is built around
* UTF-32. This is unlike Lucene, which converts a UTF-32 DFA to an equivalent UTF-8 DFA.
@@ -159,7 +159,7 @@ public:
/**
* Attempts to match the source string `source` with the target string this DFA was
- * built with, emitting a successor string on mismatch if `successor_out` != nullptr.
+ * built with.
*
* `source` must not contain any null UTF-8 chars.
*
@@ -179,13 +179,23 @@ public:
* Attempts to match the source string `source` with the target string this DFA was
* built with, emitting a successor string into `successor_out` on mismatch.
*
- * See `match(source)` for semantics of returned MatchResult.
+ * In the case of a _match_, the following holds:
+ *
+ * - The returned MatchResult has the same semantics as `match(source)`.
+ * - `successor_out` has a _prefix_ equal to its value that was originally passed
+ * in at the time of match() being called. The _suffix_ of the string is unspecified,
+ * i.e. it may or may not have been modified.
*
* In the case of a _mismatch_, the following holds:
*
- * - `successor_out` is modified to contain the next (in byte-wise ordering) possible
- * _matching_ string S so that there exists no other matching string S' that is
- * greater than `source` but smaller than S.
+ * - `successor_out` has a _prefix_ equal to its value that was originally passed
+ * in at the time of match() being called.
+ * - `successor_out` has a _suffix_ that contains the next (in byte-wise ordering)
+ * possible _matching_ string S so that there exists no other matching string S'
+ * that is greater than `source` but smaller than S.
+ * The caller must explicitly be aware of any prefixes it sends in, as it is
+ * entirely ignored for the purposes of ordering the successor string vis-a-vis
+ * the input source string.
* - `successor_out` contains UTF-8 bytes that are within what UTF-8 can legally
* encode in bitwise form, but the _code points_ they encode may not be valid.
* In particular, surrogate pair ranges and U+10FFFF+1 may be encoded, neither of
@@ -203,12 +213,8 @@ public:
* is what is passed to the DFA match() function.
*
* Memory allocation:
- * This function does not directly or indirectly allocate any heap memory if either:
- *
- * - the input string is within the max edit distance, or
- * - `successor_out` is nullptr, or
- * - `successor_out` has sufficient capacity to hold the generated successor
- *
+ * This function does not directly or indirectly allocate any heap memory if the
+ * `successor_out` string provided is large enough to fit any generated successor.
* By reusing the successor string across many calls, this therefore amortizes memory
* allocations down to near zero per invocation.
*/
@@ -220,7 +226,8 @@ public:
* internally, and is therefore expected to be more efficient.
*
* The code point ordering of the UTF-32 successor string is identical to that its UTF-8
- * equivalent.
+ * equivalent. This includes the special cases where the successor may contain code points
+ * outside the legal Unicode range.
*/
[[nodiscard]] MatchResult match(std::string_view source, std::vector<uint32_t>& successor_out) const;
@@ -231,7 +238,8 @@ public:
enum class DfaType {
Implicit,
- Explicit
+ Explicit,
+ Table
};
/**
diff --git a/vespalib/src/vespa/vespalib/fuzzy/match_algorithm.hpp b/vespalib/src/vespa/vespalib/fuzzy/match_algorithm.hpp
index fb5ec32abc7..9f5dc0fb0c0 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/match_algorithm.hpp
+++ b/vespalib/src/vespa/vespalib/fuzzy/match_algorithm.hpp
@@ -136,15 +136,14 @@ struct MatchAlgorithm {
* that has nothing in common with the source altogether.
* Example: "gp" -> "hfood" (+1 char value case)
*
- * Performance note:
- * Both the input and successor output strings are in UTF-8 format. To avoid doing
- * duplicate work, we keep track of the byte length of the string prefix that will be
- * part of the successor and simply copy it verbatim instead of building the string
- * from converted UTF-32 -> UTF-8 chars as we go. This optimization cannot be used
- * when one or more of the prefix characters have been lowercase-transformed.
+ * Note for cased vs. uncased matching: when uncased matching is specified, we always
+ * match "as if" both the target and source strings are lowercased. This means that
+ * successor strings are generated based on this form, _not_ on the original form.
+ * Example: uncased matching for target "food" with input "FOXX". This generates the
+ * successor "foyd" (and _not_ "FOyd"), as the latter would imply a completely different
+ * ordering when compared byte-wise against an implicitly lowercased dictionary.
*
* TODO let matcher know if source string is pre-normalized (i.e. lowercased).
- * TODO consider opportunistically appending prefix as we go instead of only when needed.
*/
template <DfaMatcher Matcher, typename SuccessorT>
static MatchResult match(const Matcher& matcher,
@@ -153,22 +152,19 @@ struct MatchAlgorithm {
{
using StateType = typename Matcher::StateType;
Utf8Reader u8_reader(source.data(), source.size());
- uint32_t n_prefix_u8_bytes = 0;
+ uint32_t n_prefix_chars = static_cast<uint32_t>(successor_out.size()); // Don't touch any existing prefix
uint32_t char_after_prefix = 0;
StateType last_state_with_higher_out = StateType{};
- bool can_use_raw_prefix = true;
StateType state = matcher.start();
while (u8_reader.hasMore()) {
- const auto u8_pos_before_char = u8_reader.getPos();
- const uint32_t raw_mch = u8_reader.getChar();
- const uint32_t mch = normalized_match_char(raw_mch, matcher.is_cased());
- if (raw_mch != mch) {
- can_use_raw_prefix = false; // FIXME this is pessimistic; considers entire string, not just prefix
- }
+ const auto pos_before_char = static_cast<uint32_t>(successor_out.size());
+ const uint32_t raw_mch = u8_reader.getChar();
+ const uint32_t mch = normalized_match_char(raw_mch, matcher.is_cased());
+ append_utf32_char(successor_out, mch);
if (matcher.has_higher_out_edge(state, mch)) {
last_state_with_higher_out = state;
- n_prefix_u8_bytes = u8_pos_before_char;
+ n_prefix_chars = pos_before_char;
char_after_prefix = mch;
}
auto maybe_next = matcher.match_input(state, mch);
@@ -176,8 +172,7 @@ struct MatchAlgorithm {
state = maybe_next;
} else {
// Can never match; find the successor
- emit_successor_prefix(successor_out, source, n_prefix_u8_bytes,
- matcher.is_cased() || can_use_raw_prefix);
+ successor_out.resize(n_prefix_chars); // Always <= successor_out.size()
assert(matcher.valid_state(last_state_with_higher_out));
backtrack_and_emit_greater_suffix(matcher, last_state_with_higher_out,
char_after_prefix, successor_out);
@@ -188,8 +183,7 @@ struct MatchAlgorithm {
if (edits <= max_edits()) {
return MatchResult::make_match(max_edits(), edits);
}
- emit_successor_prefix(successor_out, source, source.size(),
- matcher.is_cased() || can_use_raw_prefix);
+ // Successor prefix already filled, just need to emit the suffix
emit_smallest_matching_suffix(matcher, state, successor_out);
return MatchResult::make_mismatch(max_edits());
}
@@ -320,48 +314,6 @@ struct MatchAlgorithm {
}
}
- template <typename T>
- static constexpr bool has_8bit_value_type() noexcept {
- return sizeof(typename T::value_type) == 1;
- }
-
- /**
- * The successor prefix is the prefix of the source string up to (but not including) the
- * point where we emit a lexicographically higher character. Ideally we can just copy the
- * UTF-8 bytes verbatim from the source into the successor. This is possible when one of
- * the following holds:
- *
- * - DFA uses Cased (i.e. exact) matching, or
- * - DFA uses Uncased, but none of the characters in the prefix triggered a lowercase
- * transform. This means the prefix is already as-if lowercased, and we can copy it
- * verbatim.
- *
- * In the case that we can't copy verbatim, we currently have to explicitly normalize the
- * prefix by converting it to its lowercased form.
- *
- * Example: Uncased matching for target "food" with input "FOXX". This generates the
- * successor "foyd" (and _not_ "FOyd"), as the latter would imply a completely different
- * ordering when compared byte-wise against an implicitly lowercased dictionary.
- */
- template <typename SuccessorT>
- static void emit_successor_prefix(SuccessorT& successor_out, std::string_view source,
- uint32_t n_prefix_u8_bytes, bool emit_raw_prefix_u8_bytes)
- {
- // TODO redesign prefix output wiring
- if constexpr (has_8bit_value_type<SuccessorT>()) {
- if (emit_raw_prefix_u8_bytes) {
- successor_out = source.substr(0, n_prefix_u8_bytes);
- return;
- }
- }
- // TODO avoid duplicate work...! :I
- successor_out.clear();
- Utf8Reader u8_reader(source.data(), source.size());
- while (u8_reader.getPos() < n_prefix_u8_bytes) {
- append_utf32_char(successor_out, LowerCase::convert(u8_reader.getChar()));
- }
- }
-
static uint32_t normalized_match_char(uint32_t in_ch, bool is_cased) noexcept {
return (is_cased ? in_ch : LowerCase::convert(in_ch));
}
diff --git a/vespalib/src/vespa/vespalib/fuzzy/sparse_state.h b/vespalib/src/vespa/vespalib/fuzzy/sparse_state.h
index d20cfc07a9a..dfec0bac4a8 100644
--- a/vespalib/src/vespa/vespalib/fuzzy/sparse_state.h
+++ b/vespalib/src/vespa/vespalib/fuzzy/sparse_state.h
@@ -112,11 +112,11 @@ std::ostream& operator<<(std::ostream& os, const FixedSparseState<MaxEdits>& s)
if (i != 0) {
os << ", ";
}
- for (size_t j = last_idx; j < s.indices[i]; ++j) {
+ for (size_t j = last_idx; j < s.index(i); ++j) {
os << "-, ";
}
- last_idx = s.indices[i] + 1;
- os << static_cast<uint32_t>(s.costs[i]);
+ last_idx = s.index(i) + 1;
+ os << static_cast<uint32_t>(s.cost(i));
}
os << "]";
return os;
diff --git a/vespalib/src/vespa/vespalib/fuzzy/table_dfa.cpp b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.cpp
new file mode 100644
index 00000000000..943349818fb
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.cpp
@@ -0,0 +1,10 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "table_dfa.hpp"
+
+namespace vespalib::fuzzy {
+
+template class TableDfa<1>;
+template class TableDfa<2>;
+
+}
diff --git a/vespalib/src/vespa/vespalib/fuzzy/table_dfa.h b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.h
new file mode 100644
index 00000000000..45c52c84bad
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.h
@@ -0,0 +1,63 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "levenshtein_dfa.h"
+#include <vector>
+
+namespace vespalib::fuzzy {
+
+/**
+ * This implementation is based on the paper 'Fast string correction
+ * with Levenshtein automata' from 2002 by Klaus U. Schulz and Stoyan
+ * Mihov.
+ *
+ * Given the maximal distance N, a generic parameterized transition
+ * table is calculated up-front. When a specific word is given, a
+ * simple lookup structure is created to enumerate the possible
+ * characteristic vectors for each position in the given
+ * word. Together, these structures can be used to simulate the
+ * traversal of a hypothetical Levenshtein dfa that will never be
+ * created.
+ *
+ * Approaching the end of the word is handled by padding the
+ * characteristic vectors with 0 bits for everything after the word
+ * ends. In addition, a unit test verifies that there is no possible
+ * sequence of events that leads to the minimal boundary of the state
+ * exceeding the boundary of the word itself. This means that the
+ * simulated dfa can be stepped freely without checking for word size.
+ **/
+template <uint8_t N>
+class TableDfa final : public LevenshteinDfa::Impl
+{
+public:
+ // characteristic vector for a specific input value indicating how
+ // it matches the window starting at the minimal boundary.
+ struct CV {
+ uint32_t input;
+ uint32_t match;
+ CV() noexcept : input(0), match(0) {}
+ };
+ static constexpr size_t window_size() { return 2 * N + 1; }
+ struct Lookup {
+ std::array<CV, window_size()> list;
+ Lookup() noexcept : list() {}
+ };
+
+private:
+ const std::vector<Lookup> _lookup;
+ const bool _is_cased;
+
+ static std::vector<Lookup> make_lookup(const std::vector<uint32_t> &str);
+
+public:
+ using MatchResult = LevenshteinDfa::MatchResult;
+ TableDfa(std::vector<uint32_t> str, bool is_cased);
+ ~TableDfa() override;
+ [[nodiscard]] MatchResult match(std::string_view source) const override;
+ [[nodiscard]] MatchResult match(std::string_view source, std::string& successor_out) const override;
+ [[nodiscard]] MatchResult match(std::string_view source, std::vector<uint32_t>& successor_out) const override;
+ [[nodiscard]] size_t memory_usage() const noexcept override;
+ void dump_as_graphviz(std::ostream& os) const override;
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/fuzzy/table_dfa.hpp b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.hpp
new file mode 100644
index 00000000000..de850681113
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/fuzzy/table_dfa.hpp
@@ -0,0 +1,586 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "table_dfa.h"
+#include "match_algorithm.hpp"
+#include <vespa/vespalib/util/stringfmt.h>
+#include <cassert>
+#include <stdexcept>
+#include <algorithm>
+#include <map>
+#include <ostream>
+#include <set>
+#include <queue>
+
+namespace vespalib::fuzzy {
+
+namespace {
+
+using vespalib::make_string_short::fmt;
+
+// It is useful to know the number of states compile time to be able
+// to pack lookup tables better.
+template <uint8_t N> constexpr size_t num_states();
+template <> constexpr size_t num_states<1>() { return 6; }
+template <> constexpr size_t num_states<2>() { return 31; }
+template <> constexpr size_t num_states<3>() { return 197; }
+
+template <uint8_t N> constexpr size_t window_size() { return 2 * N + 1; }
+template <uint8_t N> constexpr size_t num_transitions() { return 1 << window_size<N>(); }
+
+
+auto diff(auto a, auto b) { return (a > b) ? (a - b) : (b - a); }
+
+// A Position combines an index into a word being matched with the
+// number of edits needed to get there. This maps directly onto a
+// specific state in the NFA used to match a word. Note that the sort
+// order prefers low edits over low indexs. This is to ensure that a
+// position that subsumes another position will always sort before it.
+struct Position {
+ uint32_t index;
+ uint32_t edits;
+ Position(uint32_t index_in, uint32_t edits_in) noexcept
+ : index(index_in), edits(edits_in) {}
+ static Position start() noexcept { return Position(0,0); }
+ bool subsumes(const Position &rhs) const noexcept {
+ if (edits >= rhs.edits) {
+ return false;
+ }
+ return diff(index, rhs.index) <= (rhs.edits - edits);
+ }
+ Position materialize(uint32_t target_index) const noexcept {
+ return Position(target_index, edits + diff(index, target_index));
+ }
+ bool operator==(const Position &rhs) const noexcept {
+ return (index == rhs.index) && (edits == rhs.edits);
+ }
+ bool operator<(const Position &rhs) const noexcept {
+ return std::tie(edits,index) < std::tie(rhs.edits, rhs.index);
+ }
+ template <uint8_t N>
+ void add_elementary_transitions(const std::vector<bool> &bits, std::vector<Position> &dst) const {
+ assert(bits.size() > index);
+ if (!bits[index]) {
+ dst.emplace_back(index, edits + 1);
+ dst.emplace_back(index + 1, edits + 1);
+ }
+ for (uint32_t e = 0; (edits + e) <= N; ++e) {
+ assert(bits.size() > (index + e));
+ if (bits[index + e]) {
+ dst.emplace_back(index + e + 1, edits + e);
+ }
+ }
+ }
+ vespalib::string to_string() const { return fmt("%u#%u", index, edits); }
+};
+
+// A State is a collection of different Positions that do not subsume
+// each other. If the minimal boundary of a state is larger than 0, it
+// can be lifted from the state in a normalizing operation that will
+// renumber the position indexes such that the minimal boundary of the
+// state becomes 0. This is to allow parameterized states where the
+// general progress of matching the string (minimal boundary of
+// non-normalized state) is untangled from the local competing edit
+// alternatives (normalized state).
+struct State {
+ std::vector<Position> list;
+ State() noexcept : list() {}
+ static State failed() noexcept { return State(); }
+ static State start() {
+ State result;
+ result.list.push_back(Position::start());
+ return result;
+ }
+ bool operator<(const State &rhs) const {
+ return list < rhs.list;
+ }
+ uint32_t minimal_boundary() const noexcept {
+ if (list.empty()) {
+ return 0;
+ }
+ uint32_t min = list[0].index;
+ for (size_t i = 1; i < list.size(); ++i) {
+ min = std::min(min, list[i].index);
+ }
+ return min;
+ }
+ uint32_t normalize() {
+ uint32_t min = minimal_boundary();
+ if (min > 0) {
+ for (auto &entry: list) {
+ entry.index -= min;
+ }
+ }
+ return min;
+ }
+ template <uint8_t N>
+ static State create(std::vector<Position> list_in) {
+ State result;
+ auto want = [&result](Position pos) {
+ if (pos.edits > N) {
+ return false;
+ }
+ for (const auto &old_pos: result.list) {
+ if (old_pos == pos || old_pos.subsumes(pos)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ std::sort(list_in.begin(), list_in.end());
+ for (const auto &pos: list_in) {
+ if (want(pos)) {
+ result.list.push_back(pos);
+ }
+ }
+ return result;
+ }
+ template <uint8_t N>
+ State next(const std::vector<bool> &bits) const {
+ std::vector<Position> tmp;
+ for (const auto &pos: list) {
+ pos.add_elementary_transitions<N>(bits, tmp);
+ }
+ return create<N>(std::move(tmp));
+ }
+ template <uint8_t N>
+ std::vector<uint8_t> make_edit_vector() const {
+ std::vector<uint8_t> result(window_size<N>(), N + 1);
+ for (const auto &pos: list) {
+ for (uint32_t i = 0; i < window_size<N>(); ++i) {
+ result[i] = std::min(result[i], uint8_t(pos.materialize(i).edits));
+ }
+ }
+ return result;
+ }
+ vespalib::string to_string() const {
+ vespalib::string result = "{";
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (i > 0) {
+ result.append(",");
+ }
+ result.append(list[i].to_string());
+ }
+ result.append("}");
+ return result;
+ }
+};
+
+// Keeps track of unique states, assigning an integer value to each
+// state. Only states with minimal boundary 0 is allowed to be
+// inserted into a state repo. Each repo is seeded with the empty
+// state (0) and the start state (1). An assigned integer value can be
+// mapped back into the originating state.
+struct StateRepo {
+ using Map = std::map<State,uint32_t>;
+ using Ref = Map::iterator;
+ Map seen;
+ std::vector<Ref> refs;
+ StateRepo() noexcept : seen(), refs() {
+ auto failed_idx = state_to_idx(State::failed());
+ auto start_idx = state_to_idx(State::start());
+ assert(failed_idx == 0);
+ assert(start_idx == 1);
+ }
+ ~StateRepo();
+ size_t size() const { return seen.size(); }
+ uint32_t state_to_idx(const State &state) {
+ assert(state.minimal_boundary() == 0);
+ uint32_t next = refs.size();
+ auto [pos, inserted] = seen.emplace(state, next);
+ if (inserted) {
+ refs.push_back(pos);
+ }
+ assert(seen.size() == refs.size());
+ return pos->second;
+ }
+ const State &idx_to_state(uint32_t idx) const {
+ assert(idx < refs.size());
+ return refs[idx]->first;
+ }
+};
+[[maybe_unused]] StateRepo::~StateRepo() = default;
+
+template <uint8_t N>
+std::vector<bool> expand_bits(uint32_t value) {
+ static_assert(N < 10);
+ std::vector<bool> result(window_size<N>());
+ uint32_t look_for = num_transitions<N>();
+ assert(value < look_for);
+ for (size_t i = 0; i < result.size(); ++i) {
+ look_for >>= 1;
+ result[i] = (value & look_for);
+ }
+ return result;
+}
+
+template <uint8_t N>
+StateRepo make_state_repo() {
+ StateRepo repo;
+ for (uint32_t idx = 0; idx < repo.size(); ++idx) {
+ const State &state = repo.idx_to_state(idx);
+ for (uint32_t i = 0; i < num_transitions<N>(); ++i) {
+ State new_state = state.next<N>(expand_bits<N>(i));
+ (void) new_state.normalize();
+ (void) repo.state_to_idx(new_state);
+ }
+ }
+ return repo;
+}
+
+struct Transition {
+ uint8_t step;
+ uint8_t state;
+ constexpr Transition() noexcept : step(0), state(0) {}
+ constexpr Transition(uint8_t di, uint8_t ns) noexcept : step(di), state(ns) {}
+};
+
+template <uint8_t N> struct InlineTfa;
+#include "inline_tfa.hpp"
+
+template <uint8_t N>
+struct Tfa {
+ // what happens when following a transition from a state?
+ std::array<std::array<Transition,num_transitions<N>()>,num_states<N>()> table;
+
+ // how many edits did we use to match the target word?
+ std::array<std::array<uint8_t,window_size<N>()>,num_states<N>()> edits;
+};
+
+template <uint8_t N>
+std::unique_ptr<Tfa<N>> make_tfa() {
+ auto tfa = std::make_unique<Tfa<N>>();
+ StateRepo repo;
+ uint32_t state_idx = 0;
+ for (; state_idx < repo.size(); ++state_idx) {
+ const State &state = repo.idx_to_state(state_idx);
+ for (uint32_t i = 0; i < num_transitions<N>(); ++i) {
+ State new_state = state.next<N>(expand_bits<N>(i));
+ uint32_t step = new_state.normalize();
+ uint32_t new_state_idx = repo.state_to_idx(new_state);
+ assert(step < 256);
+ assert(new_state_idx < 256);
+ tfa->table[state_idx][i].step = step;
+ tfa->table[state_idx][i].state = new_state_idx;
+ }
+ auto edits = state.make_edit_vector<N>();
+ assert(edits.size() == window_size<N>());
+ for (uint32_t i = 0; i < window_size<N>(); ++i) {
+ tfa->edits[state_idx][i] = edits[i];
+ }
+ }
+ assert(repo.size() == num_states<N>());
+ assert(state_idx == num_states<N>());
+ return tfa;
+}
+
+template <typename T>
+vespalib::string format_vector(const std::vector<T> &vector, bool compact = false) {
+ vespalib::string str = compact ? "" : "[";
+ for (size_t i = 0; i < vector.size(); ++i) {
+ if (i > 0 && !compact) {
+ str.append(",");
+ }
+ str.append(fmt("%u", uint32_t(vector[i])));
+ }
+ if (!compact) {
+ str.append("]");
+ }
+ return str;
+}
+
+// A table-based state using the InlineTfa tables to simulate stepping
+// a dfa with max edit distance N. The state itself is represented by
+// a number used as offset into these tables (state). Since the state
+// is parametric, we also store the minimal boundary of the state
+// separately (index).
+template <uint8_t N>
+struct TfaState {
+ uint32_t index;
+ uint32_t state;
+ // needed by dfa matcher concept (should use std::declval instead)
+ constexpr TfaState() noexcept : index(0), state(0) {}
+ constexpr TfaState(uint32_t i, uint32_t s) noexcept : index(i), state(s) {}
+ static constexpr TfaState start() { return TfaState(0, 1); }
+ constexpr bool valid() const noexcept { return state != 0; }
+ constexpr TfaState next(uint32_t bits) const noexcept {
+ const auto &entry = InlineTfa<N>::table[state][bits];
+ return TfaState(index + entry.step, entry.state);
+ }
+ constexpr bool is_valid_edge(uint32_t bits) const noexcept {
+ return InlineTfa<N>::table[state][bits].state != 0;
+ }
+ constexpr uint8_t edits(uint32_t end) const noexcept {
+ uint32_t leap = (end - index);
+ return (leap < window_size<N>()) ? InlineTfa<N>::edits[state][leap] : N + 1;
+ }
+ // for pretty graphviz dumping; minimal possible edits given perfect input from here on
+ constexpr uint32_t min_edits() const noexcept {
+ uint8_t res = N + 1;
+ for (uint32_t i = 0; i < window_size<N>(); ++i) {
+ res = std::min(res, InlineTfa<N>::edits[state][i]);
+ }
+ return res;
+ }
+ // for pretty graphviz dumping; actual edits needed to reach the word end from a valid state
+ constexpr uint32_t exact_edits(uint32_t end) const noexcept {
+ assert(valid());
+ uint32_t res = end;
+ for (uint32_t i = 0; i < window_size<N>(); ++i) {
+ if (uint32_t e = InlineTfa<N>::edits[state][i]; e <= N) {
+ res = std::min(res, e + diff(index + i, end));
+ }
+ }
+ return res;
+ }
+ // for pretty graphviz dumping; enable using in map and set
+ bool operator<(const TfaState &rhs) const noexcept {
+ return std::tie(index, state) < std::tie(rhs.index, rhs.state);
+ }
+ // for pretty graphviz dumping; check for redundant edges
+ bool operator==(const TfaState &rhs) const noexcept {
+ return std::tie(index, state) == std::tie(rhs.index, rhs.state);
+ }
+};
+
+template <uint8_t N>
+struct TableMatcher {
+ using S = TfaState<N>;
+ using StateType = TfaState<N>;
+ using StateParamType = TfaState<N>;
+ using EdgeType = uint32_t;
+
+ const TableDfa<N>::Lookup *lookup;
+ const uint32_t end;
+ const bool cased;
+
+ TableMatcher(const TableDfa<N>::Lookup *lookup_in, uint32_t end_in, bool cased_in)
+ noexcept : lookup(lookup_in), end(end_in), cased(cased_in) {}
+
+ bool is_cased() const noexcept { return cased; }
+ static constexpr S start() noexcept { return S::start(); }
+
+ uint8_t match_edit_distance(S s) const noexcept { return s.edits(end); }
+ bool is_match(S s) const noexcept { return s.edits(end) <= N; }
+
+ static constexpr bool can_match(S s) noexcept { return s.valid(); }
+ static constexpr bool valid_state(S) noexcept { return true; }
+
+ S match_wildcard(S s) const noexcept { return s.next(0); }
+ S match_input(S s, uint32_t c) const noexcept {
+ const auto *slice = lookup[s.index].list.data();
+ for (size_t i = 0; i < window_size<N>() && slice[i].input != 0; ++i) {
+ if (slice[i].input == c) {
+ return s.next(slice[i].match);
+ }
+ }
+ return match_wildcard(s);
+ }
+
+ bool has_higher_out_edge(S s, uint32_t c) const noexcept {
+ if (s.is_valid_edge(0)) {
+ return true;
+ }
+ const auto *slice = lookup[s.index].list.data();
+ for (size_t i = 0; i < window_size<N>() && slice[i].input > c; ++i) {
+ if (s.is_valid_edge(slice[i].match)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool has_exact_explicit_out_edge(S s, uint32_t c) const noexcept {
+ const auto *slice = lookup[s.index].list.data();
+ for (size_t i = 0; i < window_size<N>() && slice[i].input >= c; ++i) {
+ if (slice[i].input == c) {
+ return s.is_valid_edge(slice[i].match);
+ }
+ }
+ return false;
+ }
+
+ uint32_t lowest_higher_explicit_out_edge(S s, uint32_t c) const noexcept {
+ const auto *slice = lookup[s.index].list.data();
+ size_t i = window_size<N>();
+ while (i-- > 0) {
+ if (slice[i].input > c && s.is_valid_edge(slice[i].match)) {
+ return slice[i].input;
+ }
+ }
+ return 0;
+ }
+
+ uint32_t smallest_explicit_out_edge(S s) const noexcept {
+ const auto *slice = lookup[s.index].list.data();
+ size_t i = window_size<N>();
+ while (i-- > 0) {
+ if (slice[i].input != 0 && s.is_valid_edge(slice[i].match)) {
+ return slice[i].input;
+ }
+ }
+ return 0;
+ }
+
+ static constexpr bool valid_edge(uint32_t) noexcept { return true; }
+ static constexpr uint32_t edge_to_u32char(uint32_t c) noexcept { return c; }
+ S edge_to_state(S s, uint32_t c) const noexcept { return match_input(s, c); }
+
+ static constexpr bool implies_exact_match_suffix(S) noexcept { return false; }
+ static constexpr void emit_exact_match_suffix(S, std::vector<uint32_t> &) {} // not called
+ static constexpr void emit_exact_match_suffix(S, std::string &) {} // not called
+};
+
+} // unnamed
+
+template <uint8_t N>
+auto
+TableDfa<N>::make_lookup(const std::vector<uint32_t> &str)->std::vector<Lookup>
+{
+ std::vector<Lookup> result(str.size() + 1);
+ auto have_already = [&](uint32_t c, size_t i)noexcept{
+ for (size_t j = 0; j < window_size(); ++j) {
+ if (result[i].list[j].input == c) {
+ return true;
+ }
+ }
+ return false;
+ };
+ auto make_vector = [&](uint32_t c, size_t i)noexcept{
+ uint32_t bits = 0;
+ for (size_t j = 0; j < window_size(); ++j) {
+ bool found = ((i + j) < str.size()) && (str[i+j] == c);
+ bits = (bits << 1) + found;
+ }
+ return bits;
+ };
+ for (size_t i = 0; i < str.size(); ++i) {
+ for (size_t j = 0; j < window_size(); ++j) {
+ assert(result[i].list[j].input == 0);
+ assert(result[i].list[j].match == 0);
+ if ((i + j) < str.size()) {
+ uint32_t c = str[i + j];
+ if (!have_already(c, i)) {
+ result[i].list[j].input = c;
+ result[i].list[j].match = make_vector(c, i);
+ }
+ }
+ }
+ std::sort(result[i].list.begin(), result[i].list.end(),
+ [](const auto &a, const auto &b){ return a.input > b.input; });
+ }
+ return result;
+}
+
+template <uint8_t N>
+TableDfa<N>::TableDfa(std::vector<uint32_t> str, bool is_cased)
+ : _lookup(make_lookup(str)),
+ _is_cased(is_cased)
+{
+}
+
+template <uint8_t N>
+TableDfa<N>::~TableDfa() = default;
+
+template <uint8_t N>
+LevenshteinDfa::MatchResult
+TableDfa<N>::match(std::string_view u8str) const
+{
+ TableMatcher<N> matcher(_lookup.data(), _lookup.size() - 1, _is_cased);
+ return MatchAlgorithm<N>::match(matcher, u8str);
+}
+
+template <uint8_t N>
+LevenshteinDfa::MatchResult
+TableDfa<N>::match(std::string_view u8str, std::string& successor_out) const
+{
+ TableMatcher<N> matcher(_lookup.data(), _lookup.size() - 1, _is_cased);
+ return MatchAlgorithm<N>::match(matcher, u8str, successor_out);
+}
+
+template <uint8_t N>
+LevenshteinDfa::MatchResult
+TableDfa<N>::match(std::string_view u8str, std::vector<uint32_t>& successor_out) const
+{
+ TableMatcher<N> matcher(_lookup.data(), _lookup.size() - 1, _is_cased);
+ return MatchAlgorithm<N>::match(matcher, u8str, successor_out);
+}
+
+template <uint8_t N>
+size_t
+TableDfa<N>::memory_usage() const noexcept
+{
+ return _lookup.size() * sizeof(Lookup);
+}
+
+template <uint8_t N>
+void
+TableDfa<N>::dump_as_graphviz(std::ostream &os) const
+{
+ using state_t = TfaState<N>;
+ auto id_of = [state_ids = std::map<state_t,uint32_t>()](state_t state) mutable {
+ return state_ids.emplace(state, state_ids.size()).first->second;
+ };
+ auto explore = [explored = std::set<state_t>()](state_t state) mutable {
+ return explored.insert(state).second;
+ };
+ auto edits = [end = (_lookup.size() - 1)](state_t state) noexcept -> uint32_t {
+ return state.exact_edits(end);
+ };
+ struct Edge {
+ uint32_t input;
+ state_t from;
+ state_t to;
+ operator bool() const { return to.valid(); }
+ };
+ auto best_edge = [&](const Edge &a, const Edge &b) {
+ // inverted due to priority queue
+ if (a.to.min_edits() == b.to.min_edits()) {
+ return edits(a.to) > edits(b.to);
+ }
+ return a.to.min_edits() > b.to.min_edits();
+ };
+ auto todo = std::priority_queue<Edge,std::vector<Edge>,decltype(best_edge)>(best_edge);
+ auto handle_state = [&](state_t state) {
+ if (explore(state)) {
+ // number states by following the best edges first
+ auto my_id = id_of(state);
+ if (edits(state) <= N) {
+ os << " " << my_id << " [label=\"" << my_id << "(" << edits(state) << ")\", style=\"filled\"];\n";
+ }
+ auto null_edge = Edge{0, state, state.next(0)};
+ if (null_edge) {
+ todo.push(null_edge);
+ }
+ for (auto [c, bits]: _lookup[state.index].list) {
+ if (auto edge = Edge{c, state, state.next(bits)}; edge && edge.to != null_edge.to) {
+ // only process valid out edges that are not covered by the null_edge
+ todo.push(edge);
+ }
+ }
+ }
+ };
+ auto handle_edge = [&](Edge edge) {
+ handle_state(edge.to);
+ if (edge.input != 0) {
+ std::string as_utf8;
+ append_utf32_char(as_utf8, edge.input);
+ os << " " << id_of(edge.from) << " -> " << id_of(edge.to) << " [label=\"" << as_utf8 << "\"];\n";
+ } else {
+ os << " " << id_of(edge.from) << " -> " << id_of(edge.to) << " [label=\"*\"];\n";
+ }
+ };
+ os << std::dec << "digraph table_dfa {\n";
+ os << " fontname=\"Helvetica,Arial,sans-serif\"\n";
+ os << " node [shape=circle, fontname=\"Helvetica,Arial,sans-serif\", fixedsize=true];\n";
+ os << " edge [fontname=\"Helvetica,Arial,sans-serif\"];\n";
+ handle_state(state_t::start());
+ while (!todo.empty()) {
+ auto next_edge = todo.top();
+ todo.pop();
+ handle_edge(next_edge);
+ }
+ os << "}\n";
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/stllike/hashtable.h b/vespalib/src/vespa/vespalib/stllike/hashtable.h
index e290d2f626c..fa88bb038b4 100644
--- a/vespalib/src/vespa/vespalib/stllike/hashtable.h
+++ b/vespalib/src/vespa/vespalib/stllike/hashtable.h
@@ -62,7 +62,7 @@ public:
class prime_modulator
{
public:
- prime_modulator(next_t sizeOfHashTable) noexcept : _modulo(sizeOfHashTable) { }
+ explicit prime_modulator(next_t sizeOfHashTable) noexcept : _modulo(sizeOfHashTable) { }
next_t modulo(next_t hash) const noexcept { return hash % _modulo; }
next_t getTableSize() const noexcept { return _modulo; }
static next_t selectHashTableSize(size_t sz) { return hashtable_base::getModuloStl(sz); }
@@ -76,7 +76,7 @@ public:
class and_modulator
{
public:
- and_modulator(next_t sizeOfHashTable) noexcept : _mask(sizeOfHashTable-1) { }
+ explicit and_modulator(next_t sizeOfHashTable) noexcept : _mask(sizeOfHashTable-1) { }
next_t modulo(next_t hash) const noexcept { return hash & _mask; }
next_t getTableSize() const noexcept { return _mask + 1; }
static next_t selectHashTableSize(size_t sz) noexcept { return hashtable_base::getModuloSimple(sz); }
@@ -198,7 +198,7 @@ public:
using pointer = Value*;
using iterator_category = std::forward_iterator_tag;
- constexpr iterator(hashtable * hash) noexcept : _current(0), _hashTable(hash) {
+ constexpr explicit iterator(hashtable * hash) noexcept : _current(0), _hashTable(hash) {
if (! _hashTable->_nodes[_current].valid()) {
advanceToNextValidHash();
}
@@ -242,7 +242,7 @@ public:
using pointer = const Value*;
using iterator_category = std::forward_iterator_tag;
- constexpr const_iterator(const hashtable * hash) noexcept : _current(0), _hashTable(hash) {
+ constexpr explicit const_iterator(const hashtable * hash) noexcept : _current(0), _hashTable(hash) {
if (! _hashTable->_nodes[_current].valid()) {
advanceToNextValidHash();
}
@@ -282,7 +282,7 @@ public:
hashtable & operator = (hashtable &&) noexcept = default;
hashtable(const hashtable &);
hashtable & operator = (const hashtable &);
- hashtable(size_t reservedSpace);
+ explicit hashtable(size_t reservedSpace);
hashtable(size_t reservedSpace, const Hash & hasher, const Equal & equal);
virtual ~hashtable();
constexpr iterator begin() noexcept { return iterator(this); }
diff --git a/vespalib/src/vespa/vespalib/stllike/hashtable.hpp b/vespalib/src/vespa/vespalib/stllike/hashtable.hpp
index 6d2d397a887..040e421f68c 100644
--- a/vespalib/src/vespa/vespalib/stllike/hashtable.hpp
+++ b/vespalib/src/vespa/vespalib/stllike/hashtable.hpp
@@ -53,8 +53,7 @@ hashtable<Key, Value, Hash, Equal, KeyExtract, Modulator>::hashtable(size_t rese
_nodes(createStore<NodeStore>(reservedSpace, _modulator.getTableSize())),
_hasher(hasher),
_equal(equal)
-{
-}
+{ }
template< typename Key, typename Value, typename Hash, typename Equal, typename KeyExtract, typename Modulator >
hashtable<Key, Value, Hash, Equal, KeyExtract, Modulator>::hashtable(const hashtable &) = default;
@@ -130,7 +129,7 @@ hashtable<Key, Value, Hash, Equal, KeyExtract, Modulator>::insert_internal(V &&
_count++;
return insert_result(iterator(this, h), true);
}
- return insert_internal_cold(std::move(node), h);
+ return insert_internal_cold(std::forward<V>(node), h);
}
template< typename Key, typename Value, typename Hash, typename Equal, typename KeyExtract, typename Modulator >
diff --git a/vespalib/src/vespa/vespalib/text/utf8.h b/vespalib/src/vespa/vespalib/text/utf8.h
index 99e3f8cfe13..489b16b1ed4 100644
--- a/vespalib/src/vespa/vespalib/text/utf8.h
+++ b/vespalib/src/vespa/vespalib/text/utf8.h
@@ -321,6 +321,7 @@ public:
return i;
}
+ const char* get_current_ptr() const noexcept { return _p; }
};
diff --git a/vespalib/src/vespa/vespalib/util/address_space.h b/vespalib/src/vespa/vespalib/util/address_space.h
index 948217bfd2b..fd172427720 100644
--- a/vespalib/src/vespa/vespalib/util/address_space.h
+++ b/vespalib/src/vespa/vespalib/util/address_space.h
@@ -2,6 +2,7 @@
#pragma once
+#include <cstddef>
#include <iosfwd>
namespace vespalib {
diff --git a/vespalog/src/vespa/log/log.h b/vespalog/src/vespa/log/log.h
index 857bf4f2b97..edec06dae5e 100644
--- a/vespalog/src/vespa/log/log.h
+++ b/vespalog/src/vespa/log/log.h
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
+#include <cstdarg>
#include <memory>
#include <new> // for placement new
#include <sys/types.h> // for pid_t
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
index 80646dc5607..1bed85b1c02 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
@@ -166,19 +166,19 @@ public class Curator extends AbstractComponent implements AutoCloseable {
private void addLoggingListener() {
curatorFramework.getConnectionStateListenable().addListener((curatorFramework, connectionState) -> {
switch (connectionState) {
- case SUSPENDED: LOG.info("ZK connection state change: SUSPENDED"); break;
- case RECONNECTED: LOG.info("ZK connection state change: RECONNECTED"); break;
- case LOST: LOG.warning("ZK connection state change: LOST"); break;
+ case SUSPENDED -> LOG.info("ZK connection state change: SUSPENDED");
+ case RECONNECTED -> LOG.info("ZK connection state change: RECONNECTED");
+ case LOST -> LOG.warning("ZK connection state change: LOST");
}
});
}
- public CompletionWaiter getCompletionWaiter(Path waiterPath, String id, Duration waitForAll) {
- return CuratorCompletionWaiter.create(this, waiterPath, id, waitForAll);
+ public CompletionWaiter getCompletionWaiter(Path barrierPath, String id, Duration waitForAll) {
+ return CuratorCompletionWaiter.create(this, barrierPath, id, waitForAll);
}
- public CompletionWaiter createCompletionWaiter(Path waiterPath, String id, Duration waitForAll) {
- return CuratorCompletionWaiter.createAndInitialize(this, waiterPath, id, waitForAll);
+ public CompletionWaiter createCompletionWaiter(Path barrierPath, String id, Duration waitForAll) {
+ return CuratorCompletionWaiter.createAndInitialize(this, barrierPath, id, waitForAll);
}
/** Creates a listenable cache which keeps in sync with changes to all the immediate children of a path */
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorCompletionWaiter.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorCompletionWaiter.java
index 7d918baaf54..9a8b9b5bf60 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorCompletionWaiter.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorCompletionWaiter.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.curator;
import com.yahoo.path.Path;
+import com.yahoo.vespa.curator.transaction.CuratorOperations;
+import com.yahoo.vespa.curator.transaction.CuratorTransaction;
import java.time.Clock;
import java.time.Duration;
@@ -120,11 +122,13 @@ class CuratorCompletionWaiter implements CompletionWaiter {
return new CuratorCompletionWaiter(curator, barrierPath, id, Clock.systemUTC(), waitForAll);
}
- public static CompletionWaiter createAndInitialize(Curator curator, Path waiterPath, String id, Duration waitForAll) {
- curator.delete(waiterPath);
- curator.createAtomically(waiterPath);
+ public static CompletionWaiter createAndInitialize(Curator curator, Path barrierPath, String id, Duration waitForAll) {
+ // Note: Should be done atomically, but unable to that when path may not exist before delete
+ // and create should be able to create any missing parent paths
+ curator.delete(barrierPath);
+ curator.create(barrierPath);
- return new CuratorCompletionWaiter(curator, waiterPath, id, Clock.systemUTC(), waitForAll);
+ return new CuratorCompletionWaiter(curator, barrierPath, id, Clock.systemUTC(), waitForAll);
}
private int barrierMemberCount() {
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
index 592b9fc2a05..e1376fb154b 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
@@ -82,12 +82,12 @@ public class MockCurator extends Curator {
}
@Override
- public CompletionWaiter getCompletionWaiter(Path parentPath, String id, Duration waitForAll) {
+ public CompletionWaiter getCompletionWaiter(Path barrierPath, String id, Duration waitForAll) {
return mockFramework().createCompletionWaiter();
}
@Override
- public CompletionWaiter createCompletionWaiter(Path waiterPath, String id, Duration waitForAll) {
+ public CompletionWaiter createCompletionWaiter(Path barrierPath, String id, Duration waitForAll) {
return mockFramework().createCompletionWaiter();
}