aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java2
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java7
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java4
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java6
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java22
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java17
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferences.java4
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java1
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolver.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java28
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java20
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java11
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleMapper.java134
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java119
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/attributes.cfg108
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/child_a.sd8
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/child_b.sd8
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/child_c.sd7
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg110
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/imported-fields.cfg6
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/parent.sd8
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/derived/ImportedFieldsTestCase.java5
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolverTestCase.java4
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java11
-rwxr-xr-xconfig-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java2
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java14
-rw-r--r--configdefinitions/src/vespa/configserver.def2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java22
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java18
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionFactory.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionUtil.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java25
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java20
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java15
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java13
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/TenantsMaintainer.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java15
-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.java20
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java27
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java8
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java8
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionFactory.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java22
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileRegistry.java49
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java19
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java41
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java15
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java5
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java15
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java132
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleInstaller.java21
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java239
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java40
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java15
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/FileAcquirerBundleInstaller.java9
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java25
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java72
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java3
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java5
-rw-r--r--container-core/src/main/java/com/yahoo/container/handler/LogHandler.java4
-rw-r--r--container-core/src/main/java/com/yahoo/container/handler/LogReader.java12
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/MockOsgi.java6
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/Osgi.java6
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java11
-rw-r--r--container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java (renamed from container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java)36
-rw-r--r--container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java64
-rw-r--r--container-core/src/test/java/com/yahoo/container/core/config/TestBundleInstaller.java20
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java3
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java36
-rw-r--r--container-di/CMakeLists.txt2
-rw-r--r--container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java4
-rw-r--r--container-di/src/main/java/com/yahoo/container/di/Container.java54
-rw-r--r--container-di/src/main/java/com/yahoo/container/di/Osgi.java6
-rw-r--r--container-di/src/main/resources/configdefinitions/application-bundles.def5
-rw-r--r--container-di/src/main/resources/configdefinitions/bundles.def2
-rw-r--r--container-di/src/main/resources/configdefinitions/platform-bundles.def5
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTest.java3
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java5
-rw-r--r--container-disc/pom.xml3
-rw-r--r--container-search/abi-spec.json31
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/Location.java24
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java119
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/Item.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java17
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/SelectParser.java179
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java33
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java79
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java20
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java23
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/YqlParser.java144
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/searcher/test/PosSearcherTestCase.java3
-rw-r--r--container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java1
-rw-r--r--container-search/src/test/java/com/yahoo/search/yql/VespaSerializerTestCase.java8
-rw-r--r--container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java18
-rw-r--r--container-search/src/test/java/com/yahoo/select/SelectTestCase.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationStore.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/RestartFilter.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java68
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java136
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java62
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java84
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java31
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageTest.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java62
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java50
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesLegacyTest.java711
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java19
-rw-r--r--dist/vespa.spec40
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java18
-rw-r--r--fnet/src/tests/examples/examples_test.cpp54
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java7
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java5
-rw-r--r--jdisc_http_service/pom.xml1
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java8
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandlerTest.java30
-rw-r--r--metrics/src/vespa/metrics/metricmanager.cpp3
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java24
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java30
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java22
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java53
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerId.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java35
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java26
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooter.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivator.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java20
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodeAclResponse.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java12
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooterTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClientTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java3
-rw-r--r--parent/pom.xml1
-rw-r--r--searchcore/src/apps/proton/proton.cpp3
-rw-r--r--searchcore/src/apps/vespa-dump-feed/vespa-dump-feed.cpp4
-rw-r--r--searchcore/src/tests/proton/flushengine/flushengine_test.cpp2
-rw-r--r--searchcore/src/tests/proton/matching/query_test.cpp3
-rw-r--r--searchcore/src/tests/proton/matching/termdataextractor_test.cpp2
-rw-r--r--searchcore/src/tests/proton/proton_configurer/CMakeLists.txt1
-rw-r--r--searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp164
-rw-r--r--searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp12
-rw-r--r--searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp32
-rw-r--r--searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt1
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/query.cpp107
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/query.h11
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/queryenvironment.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/queryenvironment.h9
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.cpp26
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/i_document_db_config_owner.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.cpp30
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.h10
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp10
-rw-r--r--searchlib/src/tests/attribute/document_weight_iterator/document_weight_iterator_test.cpp1
-rw-r--r--searchlib/src/tests/attribute/searchable/attribute_searchable_adapter_test.cpp50
-rw-r--r--searchlib/src/tests/attribute/searchable/attributeblueprint_test.cpp33
-rw-r--r--searchlib/src/tests/common/location/CMakeLists.txt7
-rw-r--r--searchlib/src/tests/common/location/geo_location_test.cpp394
-rw-r--r--searchlib/src/tests/common/location/location_test.cpp121
-rw-r--r--searchlib/src/tests/features/prod_features.cpp29
-rw-r--r--searchlib/src/tests/query/query_visitor_test.cpp2
-rw-r--r--searchlib/src/tests/query/querybuilder_test.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp21
-rw-r--r--searchlib/src/vespa/searchlib/common/CMakeLists.txt3
-rw-r--r--searchlib/src/vespa/searchlib/common/documentlocations.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/common/documentlocations.h4
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location.cpp184
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location.h92
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location_parser.cpp209
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location_parser.h47
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location_spec.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location_spec.h21
-rw-r--r--searchlib/src/vespa/searchlib/common/location.cpp196
-rw-r--r--searchlib/src/vespa/searchlib/common/location.h48
-rw-r--r--searchlib/src/vespa/searchlib/common/locationiterators.cpp28
-rw-r--r--searchlib/src/vespa/searchlib/features/distancefeature.cpp70
-rw-r--r--searchlib/src/vespa/searchlib/features/distancefeature.h10
-rw-r--r--searchlib/src/vespa/searchlib/fef/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/fef/fef.h1
-rw-r--r--searchlib/src/vespa/searchlib/fef/iqueryenvironment.h10
-rw-r--r--searchlib/src/vespa/searchlib/fef/location.cpp18
-rw-r--r--searchlib/src/vespa/searchlib/fef/location.h111
-rw-r--r--searchlib/src/vespa/searchlib/fef/phrase_splitter_query_env.h4
-rw-r--r--searchlib/src/vespa/searchlib/fef/test/queryenvironment.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/fef/test/queryenvironment.h16
-rw-r--r--searchlib/src/vespa/searchlib/parsequery/parse.h2
-rw-r--r--searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/querynode.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/location.cpp93
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/location.h25
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/point.h12
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/rectangle.h16
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/stackdumpcreator.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h11
-rw-r--r--searchsummary/src/tests/docsummary/matched_elements_filter/matched_elements_filter_test.cpp1
-rw-r--r--searchsummary/src/tests/docsummary/positionsdfw_test.cpp1
-rw-r--r--searchsummary/src/tests/docsummary/slime_summary/slime_summary_test.cpp1
-rw-r--r--searchsummary/src/tests/extractkeywords/extractkeywordstest.cpp2
-rw-r--r--searchsummary/src/tests/extractkeywords/simplequerystackitem.cpp4
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp54
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h5
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.cpp2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.h10
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/matched_elements_filter_dfw.h5
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp96
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h31
-rw-r--r--searchsummary/src/vespa/searchsummary/test/mock_state_callback.h1
-rw-r--r--staging_vespalib/src/tests/state_server/state_server_test.cpp4
-rw-r--r--standalone-container/pom.xml1
-rw-r--r--standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java15
-rw-r--r--storage/src/tests/common/metricstest.cpp12
-rw-r--r--storage/src/tests/persistence/filestorage/operationabortingtest.cpp6
-rw-r--r--storage/src/tests/persistence/mergehandlertest.cpp2
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp4
-rw-r--r--storage/src/vespa/storage/distributor/pending_bucket_space_db_transition.h8
-rw-r--r--storage/src/vespa/storage/distributor/pendingclusterstate.cpp25
-rw-r--r--storage/src/vespa/storage/distributor/pendingclusterstate.h5
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/CMakeLists.txt1
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp6
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/filestormetrics.cpp16
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/filestormetrics.h9
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.cpp28
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.h32
-rw-r--r--storage/src/vespa/storage/persistence/mergehandler.cpp78
-rw-r--r--storage/src/vespa/storage/persistence/persistencethread.cpp4
-rw-r--r--storage/src/vespa/storage/storageserver/communicationmanager.cpp8
-rw-r--r--storage/src/vespa/storage/storageserver/communicationmanager.h2
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp64
-rw-r--r--storageframework/src/vespa/storageframework/generic/metric/metricregistrator.h2
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/matching_elements_filler.cpp3
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/queryenvironment.cpp42
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/queryenvironment.h11
-rw-r--r--vbench/src/tests/app_dumpurl/app_dumpurl_test.cpp8
-rw-r--r--vbench/src/tests/app_vbench/app_vbench_test.cpp14
-rw-r--r--vespa-osgi-testrunner/CMakeLists.txt1
-rw-r--r--vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java40
-rw-r--r--vespajlib/src/main/java/com/yahoo/concurrent/maintenance/JobMetrics.java41
-rw-r--r--vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java20
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java2
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java79
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java282
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java57
-rw-r--r--vespajlib/src/main/java/com/yahoo/text/Utf8.java4
-rw-r--r--vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java30
-rw-r--r--vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlStateMock.java35
-rw-r--r--vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlTest.java85
-rw-r--r--vespajlib/src/test/java/com/yahoo/concurrent/maintenance/MaintainerTest.java30
-rw-r--r--vespajlib/src/test/java/com/yahoo/concurrent/maintenance/TestMaintainer.java48
-rw-r--r--vespajlib/src/test/java/com/yahoo/geo/OneDegreeParserTestCase.java204
-rw-r--r--vespalib/CMakeLists.txt2
-rw-r--r--vespalib/src/tests/assert/assert_test.cpp8
-rw-r--r--vespalib/src/tests/child_process/.gitignore4
-rw-r--r--vespalib/src/tests/child_process/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/child_process/child_process_test.cpp (renamed from vespalib/src/tests/slaveproc/slaveproc_test.cpp)36
-rw-r--r--vespalib/src/tests/drop-file-from-cache/drop_file_from_cache_test.cpp10
-rw-r--r--vespalib/src/tests/exception_classes/silenceuncaught_test.cpp16
-rw-r--r--vespalib/src/tests/host_name/host_name_test.cpp2
-rw-r--r--vespalib/src/tests/make_fixture_macros/make_fixture_macros_test.cpp4
-rw-r--r--vespalib/src/tests/slaveproc/.gitignore4
-rw-r--r--vespalib/src/tests/slaveproc/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/tutorial/make_tutorial.cpp4
-rw-r--r--vespalib/src/vespa/vespalib/util/CMakeLists.txt2
-rw-r--r--vespalib/src/vespa/vespalib/util/child_process.cpp (renamed from vespalib/src/vespa/vespalib/util/slaveproc.cpp)56
-rw-r--r--vespalib/src/vespa/vespalib/util/child_process.h (renamed from vespalib/src/vespa/vespalib/util/slaveproc.h)18
-rw-r--r--vespamalloc/src/tests/doubledelete/expectsignal.cpp4
-rw-r--r--vespamalloc/src/tests/overwrite/expectsignal.cpp4
-rw-r--r--vsm/src/vespa/vsm/vsm/docsumconfig.cpp3
-rw-r--r--vsm/src/vespa/vsm/vsm/vsm-adapter.cpp5
-rw-r--r--vsm/src/vespa/vsm/vsm/vsm-adapter.h1
375 files changed, 5591 insertions, 3704 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
index c328b8b6c21..123a9bc7449 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
@@ -59,7 +59,7 @@ public class IdentityDocumentGenerator {
zone.environment().value(),
identityType);
- Set<String> ips = new HashSet<>(node.ipAddresses());
+ Set<String> ips = new HashSet<>(node.ipConfig().primary());
PrivateKey privateKey = keyProvider.getPrivateKey(athenzProviderServiceConfig.secretVersion());
AthenzService providerService = new AthenzService(athenzProviderServiceConfig.domain(), athenzProviderServiceConfig.serviceName());
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
index 05bbd790ad7..c258e1be466 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.api.ApplicationInfo;
import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.model.api.SuperModelProvider;
import com.yahoo.config.provision.ApplicationId;
+
import java.util.logging.Level;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
@@ -155,9 +156,9 @@ public class InstanceValidator {
.map(InetAddresses::forString)
.collect(Collectors.toList());
- List<InetAddress> nodeIpAddresses = node.ipAddresses().stream()
- .map(InetAddresses::forString)
- .collect(Collectors.toList());
+ List<InetAddress> nodeIpAddresses = node.ipConfig().primary().stream()
+ .map(InetAddresses::forString)
+ .collect(Collectors.toList());
// Validate that ipaddresses in request are valid for node
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
index 19ad9df2d4d..ab81cb8eda5 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
@@ -125,7 +125,7 @@ public class InstanceValidatorTest {
Node node = nodeList.get(0);
nodeList = allocateNode(nodeList, node, applicationId);
when(nodeRepository.getNodes()).thenReturn(nodeList);
- String nodeIp = node.ipAddresses().stream().findAny().orElseThrow(() -> new RuntimeException("No ipaddress for mocked node"));
+ String nodeIp = node.ipConfig().primary().stream().findAny().orElseThrow(() -> new RuntimeException("No ipaddress for mocked node"));
InstanceConfirmation instanceConfirmation = createRefreshInstanceConfirmation(applicationId, domain, service, ImmutableList.of(nodeIp));
assertTrue(instanceValidator.isValidRefresh(instanceConfirmation));
@@ -140,7 +140,7 @@ public class InstanceValidatorTest {
Node node = nodeList.get(0);
nodeList = allocateNode(nodeList, node, applicationId);
when(nodeRepository.getNodes()).thenReturn(nodeList);
- String nodeIp = node.ipAddresses().stream().findAny().orElseThrow(() -> new RuntimeException("No ipaddress for mocked node"));
+ String nodeIp = node.ipConfig().primary().stream().findAny().orElseThrow(() -> new RuntimeException("No ipaddress for mocked node"));
// Add invalid ip to list of ip addresses
InstanceConfirmation instanceConfirmation = createRefreshInstanceConfirmation(applicationId, domain, service, ImmutableList.of(nodeIp, "::ff"));
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java
index 892eb9aac05..31c8049b0dd 100644
--- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java
@@ -57,12 +57,12 @@ public class GenerateOsgiManifestMojo extends AbstractGenerateOsgiManifestMojo {
private String mainClass = null;
@Parameter(defaultValue = "false")
- private boolean buildVespaPlatformBundle;
+ private boolean buildLegacyVespaPlatformBundle;
public void execute() throws MojoExecutionException {
try {
- if (discPreInstallBundle != null && ! buildVespaPlatformBundle)
- throw new MojoExecutionException("The 'discPreInstallBundle' parameter can only be used by Vespa platform bundles.");
+ if (discPreInstallBundle != null && !buildLegacyVespaPlatformBundle)
+ throw new MojoExecutionException("The 'discPreInstallBundle' parameter can only be used by legacy Vespa platform bundles.");
Artifacts.ArtifactSet artifactSet = Artifacts.getArtifacts(project);
warnOnUnsupportedArtifacts(artifactSet.getNonJarArtifacts());
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java
index 957531b9f7f..6888626633b 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java
@@ -20,31 +20,28 @@ public class PreGeneratedFileRegistry implements FileRegistry {
private final String fileSourceHost;
private final Map<String, String> path2Hash = new LinkedHashMap<>();
- private static String entryDelimiter = "\t";
- private static Pattern entryDelimiterPattern = Pattern.compile(entryDelimiter, Pattern.LITERAL);
+ private static final String entryDelimiter = "\t";
+ private static final Pattern entryDelimiterPattern = Pattern.compile(entryDelimiter, Pattern.LITERAL);
private PreGeneratedFileRegistry(Reader readerArg) {
- BufferedReader reader = new BufferedReader(readerArg);
- try {
+ try (BufferedReader reader = new BufferedReader(readerArg)) {
fileSourceHost = reader.readLine();
if (fileSourceHost == null)
- throw new RuntimeException("Error while reading pre generated file registry");
+ throw new RuntimeException("Error while reading pre-generated file registry");
String line;
while ((line = reader.readLine()) != null) {
addFromLine(line);
}
- } catch(IOException e) {
- throw new RuntimeException("Error while reading pre generated file registry", e);
- } finally {
- try {
- reader.close();
- } catch(IOException e) {}
+ } catch (IOException e) {
+ throw new RuntimeException("Error while reading pre-generated file registry", e);
}
}
private void addFromLine(String line) {
String[] parts = entryDelimiterPattern.split(line);
+ if (parts.length < 2)
+ throw new IllegalArgumentException("Cannot split '" + line + "' into two parts");
addEntry(parts[0], parts[1]);
}
@@ -58,8 +55,7 @@ public class PreGeneratedFileRegistry implements FileRegistry {
builder.append(registry.fileSourceHost()).append('\n');
for (FileRegistry.Entry entry : entries) {
- builder.append(entry.relativePath).append(entryDelimiter).append(entry.reference.value()).
- append('\n');
+ builder.append(entry.relativePath).append(entryDelimiter).append(entry.reference.value()).append('\n');
}
return builder.toString();
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java
index 0d0da71bd0f..7f1a9933188 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferenceResolver.java
@@ -6,6 +6,7 @@ import com.yahoo.document.ReferenceDataType;
import com.yahoo.searchdefinition.document.SDDocumentType;
import com.yahoo.searchdefinition.document.SDField;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@@ -30,10 +31,24 @@ public class DocumentReferenceResolver {
}
public void resolveReferences(SDDocumentType documentType) {
- DocumentReferences references = new DocumentReferences(createFieldToDocumentReferenceMapping(documentType));
+ var references = new DocumentReferences(createFieldToDocumentReferenceMapping(documentType));
documentType.setDocumentReferences(references);
}
+ public void resolveInheritedReferences(SDDocumentType documentType) {
+ resolveInheritedReferencesRecursive(documentType, documentType.getInheritedTypes());
+ }
+
+ private void resolveInheritedReferencesRecursive(SDDocumentType documentType,
+ Collection<SDDocumentType> inheritedTypes) {
+ for (var inheritedType : inheritedTypes) {
+ documentType.getDocumentReferences().get().mergeFrom(inheritedType.getDocumentReferences().get());
+ }
+ for (var inheritedType : inheritedTypes) {
+ resolveInheritedReferencesRecursive(documentType, inheritedType.getInheritedTypes());
+ }
+ }
+
private Map<String, DocumentReference> createFieldToDocumentReferenceMapping(SDDocumentType documentType) {
return fieldStream(documentType)
.filter(field -> field.getDataType() instanceof ReferenceDataType)
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferences.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferences.java
index 37f5ab1bbde..4a3995b2d40 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferences.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentReferences.java
@@ -18,6 +18,10 @@ public class DocumentReferences implements Iterable<Map.Entry<String, DocumentRe
this.references = references;
}
+ public void mergeFrom(DocumentReferences other) {
+ references.putAll(other.references);
+ }
+
@Override
public Iterator<Map.Entry<String, DocumentReference>> iterator() {
return Collections.unmodifiableSet(references.entrySet()).iterator();
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java
index eb68e6af203..1fab30f9ea4 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java
@@ -239,6 +239,7 @@ public class SearchBuilder {
var resolver = new DocumentReferenceResolver(searchList);
sdocs.forEach(resolver::resolveReferences);
+ sdocs.forEach(resolver::resolveInheritedReferences);
var importedFieldsEnumerator = new ImportedFieldsEnumerator(searchList);
sdocs.forEach(importedFieldsEnumerator::enumerateImportedFields);
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolver.java
index d86ed265b77..77064038053 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolver.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolver.java
@@ -48,11 +48,13 @@ public class MatchedElementsOnlyResolver extends Processor {
if (isComplexFieldWithOnlyStructFieldAttributes(sourceField)) {
field.setTransform(SummaryTransform.MATCHED_ATTRIBUTE_ELEMENTS_FILTER);
}
- } else if (isSupportedAttributeField(sourceField)) {
- field.setTransform(SummaryTransform.MATCHED_ATTRIBUTE_ELEMENTS_FILTER);
+ } else if (isSupportedMultiValueField(sourceField)) {
+ if (sourceField.doesAttributing()) {
+ field.setTransform(SummaryTransform.MATCHED_ATTRIBUTE_ELEMENTS_FILTER);
+ }
} else if (validate) {
fail(summary, field, "'matched-elements-only' is not supported for this field type. " +
- "Supported field types are: array attribute, weighted set attribute, " +
+ "Supported field types are: array of primitive, weighted set of primitive, " +
"array of simple struct, map of primitive type to simple struct, " +
"and map of primitive type to primitive type");
}
@@ -60,10 +62,9 @@ public class MatchedElementsOnlyResolver extends Processor {
// else case is handled in SummaryFieldsMustHaveValidSource
}
- private boolean isSupportedAttributeField(ImmutableSDField sourceField) {
+ private boolean isSupportedMultiValueField(ImmutableSDField sourceField) {
var type = sourceField.getDataType();
- return sourceField.doesAttributing() &&
- (isArrayOfPrimitiveType(type) || isWeightedsetOfPrimitiveType(type));
+ return (isArrayOfPrimitiveType(type) || isWeightedsetOfPrimitiveType(type));
}
private boolean isArrayOfPrimitiveType(DataType type) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
index f7a04c36da0..0192e9d42c6 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
@@ -66,12 +66,12 @@ public class HostSystem extends AbstractConfigProducer<Host> {
* @return the host with the given hostname, or null if no such host
*/
public HostResource getHostByHostname(String name) {
- System.out.println("Getting name=" + name + " all hosts: " + hostname2host);
+ String localhost = "localhost";
HostResource hostResource = hostname2host.get(name);
if (hostResource == null) {
- // Create a new HostResource if this is the host this code is running on (as when running tests)
- if (HostName.getLocalhost().equals(name)) {
- String localhost = "localhost";
+ // Create a new HostResource if this is the host this code is running on (as it is when running tests)
+ // TODO: please eliminate the ugly hack using "localhost.fortestingpurposesonly"
+ if (HostName.getLocalhost().equals(name) || "localhost.fortestingpurposesonly".equals(name)) {
if (! getChildren().containsKey(localhost)) {
new Host(this, localhost);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java
index 7484e0cd9a0..1b5be1c2f97 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java
@@ -264,7 +264,8 @@ public class Admin extends AbstractConfigProducer implements Serializable {
FileDistributor fileDistributor = fileDistribution.getFileDistributor();
HostResource hostResource = hostSystem().getHostByHostname(fileDistributor.fileSourceHost());
if (hostResource == null && ! multitenant)
- throw new IllegalArgumentException("Could not find " + host + " in the application's " + hostSystem());
+ throw new IllegalArgumentException("Could not find " + fileDistributor.fileSourceHost() +
+ " in the application's " + hostSystem());
FileDistributionConfigProvider configProvider =
new FileDistributionConfigProvider(fileDistribution,
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
index 24e88d7ef7d..f9338f9cb35 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
@@ -31,7 +31,8 @@ public class LogserverContainerCluster extends ContainerCluster<LogserverContain
@Override
public void getConfig(QrStartConfig.Builder builder) {
super.getConfig(builder);
- builder.jvm.heapsize(384);
+ builder.jvm.heapsize(384)
+ .verbosegc(true);
}
protected boolean messageBusEnabled() { return false; }
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
index 547c05d2c9b..08f4e2fa12f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
@@ -5,8 +5,8 @@ import com.yahoo.cloud.config.ZookeeperServerConfig;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.model.api.container.ContainerServiceType;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.model.application.validation.RestartConfigs;
@@ -14,18 +14,17 @@ import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.component.AccessLogComponent;
import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.component.Handler;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
import java.util.Set;
import java.util.TreeSet;
-import static com.yahoo.vespa.defaults.Defaults.getDefaults;
-
/**
* Container implementation for cluster-controllers
*/
@RestartConfigs({FleetcontrollerConfig.class, ZookeeperServerConfig.class})
public class ClusterControllerContainer extends Container implements
- BundlesConfig.Producer,
+ PlatformBundlesConfig.Producer,
ZookeeperServerConfig.Producer
{
private static final ComponentSpecification CLUSTERCONTROLLER_BUNDLE = new ComponentSpecification("clustercontroller-apps");
@@ -55,11 +54,12 @@ public class ClusterControllerContainer extends Container implements
}
addComponent(new AccessLogComponent(AccessLogComponent.AccessLogType.jsonAccessLog, "controller", isHosted));
- addFileBundle("lib/jars/clustercontroller-apps-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-apputil-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-core-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-utils-jar-with-dependencies.jar");
- addFileBundle("lib/jars/zookeeper-server-jar-with-dependencies.jar");
+ // TODO: Why are bundles added here instead of in the cluster?
+ addFileBundle("clustercontroller-apps");
+ addFileBundle("clustercontroller-apputil");
+ addFileBundle("clustercontroller-core");
+ addFileBundle("clustercontroller-utils");
+ addFileBundle("zookeeper-server");
}
@Override
@@ -82,8 +82,8 @@ public class ClusterControllerContainer extends Container implements
super.addHandler(h);
}
- private void addFileBundle(String bundlePath) {
- bundles.add("file:" + getDefaults().underVespaHome(bundlePath));
+ private void addFileBundle(String bundleName) {
+ bundles.add(PlatformBundles.absoluteBundlePath(bundleName).toString());
}
private ComponentModel createComponentModel(String id, String className, ComponentSpecification bundle) {
@@ -102,10 +102,8 @@ public class ClusterControllerContainer extends Container implements
}
@Override
- public void getConfig(BundlesConfig.Builder builder) {
- for (String bundle : bundles) {
- builder.bundle(bundle);
- }
+ public void getConfig(PlatformBundlesConfig.Builder builder) {
+ bundles.forEach(builder::bundlePaths);
}
@Override
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 86b6ab8a25c..4dc9811a024 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
@@ -38,9 +38,9 @@ import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer;
import com.yahoo.vespa.model.admin.monitoring.Monitoring;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.Handler;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -58,8 +58,6 @@ import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerClus
import static com.yahoo.vespa.model.admin.monitoring.DefaultPublicConsumer.getDefaultPublicConsumer;
import static com.yahoo.vespa.model.admin.monitoring.MetricSet.emptyMetricSet;
import static com.yahoo.vespa.model.admin.monitoring.VespaMetricsConsumer.getVespaMetricsConsumer;
-import static com.yahoo.vespa.model.container.xml.BundleMapper.JarSuffix.JAR_WITH_DEPS;
-import static com.yahoo.vespa.model.container.xml.BundleMapper.absoluteBundlePath;
/**
* Container cluster for metrics proxy containers.
@@ -76,7 +74,7 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC
public static final Logger log = Logger.getLogger(MetricsProxyContainerCluster.class.getName());
private static final String METRICS_PROXY_NAME = "metrics-proxy";
- static final Path METRICS_PROXY_BUNDLE_FILE = absoluteBundlePath((Paths.get(METRICS_PROXY_NAME + JAR_WITH_DEPS.suffix)));
+ static final Path METRICS_PROXY_BUNDLE_FILE = PlatformBundles.absoluteBundlePath(METRICS_PROXY_NAME);
static final String METRICS_PROXY_BUNDLE_NAME = "com.yahoo.vespa." + METRICS_PROXY_NAME;
static final class AppDimensionNames {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
index 941870e980b..20a7a6cc856 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
@@ -642,6 +642,7 @@ public class VespaMetricSet {
metrics.add(new Metric("vds.distributor.puts.sum.failures.total.rate"));
metrics.add(new Metric("vds.distributor.puts.sum.failures.notfound.rate"));
metrics.add(new Metric("vds.distributor.puts.sum.failures.test_and_set_failed.rate"));
+ metrics.add(new Metric("vds.distributor.puts.sum.failures.concurrent_mutations.rate"));
metrics.add(new Metric("vds.distributor.removes.sum.latency.max"));
metrics.add(new Metric("vds.distributor.removes.sum.latency.sum"));
metrics.add(new Metric("vds.distributor.removes.sum.latency.count"));
@@ -650,6 +651,7 @@ public class VespaMetricSet {
metrics.add(new Metric("vds.distributor.removes.sum.failures.total.rate"));
metrics.add(new Metric("vds.distributor.removes.sum.failures.notfound.rate"));
metrics.add(new Metric("vds.distributor.removes.sum.failures.test_and_set_failed.rate"));
+ metrics.add(new Metric("vds.distributor.removes.sum.failures.concurrent_mutations.rate"));
metrics.add(new Metric("vds.distributor.updates.sum.latency.max"));
metrics.add(new Metric("vds.distributor.updates.sum.latency.sum"));
metrics.add(new Metric("vds.distributor.updates.sum.latency.count"));
@@ -658,6 +660,7 @@ public class VespaMetricSet {
metrics.add(new Metric("vds.distributor.updates.sum.failures.total.rate"));
metrics.add(new Metric("vds.distributor.updates.sum.failures.notfound.rate"));
metrics.add(new Metric("vds.distributor.updates.sum.failures.test_and_set_failed.rate"));
+ metrics.add(new Metric("vds.distributor.updates.sum.failures.concurrent_mutations.rate"));
metrics.add(new Metric("vds.distributor.updates.sum.diverging_timestamp_updates.rate"));
metrics.add(new Metric("vds.distributor.removelocations.sum.ok.rate"));
metrics.add(new Metric("vds.distributor.removelocations.sum.failures.total.rate"));
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 04fe77d9e05..b0ac02d0fe8 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,8 +8,8 @@ import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.ComponentInfo;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.container.handler.metrics.MetricsProxyApiConfig;
import com.yahoo.container.handler.metrics.MetricsV2Handler;
@@ -21,7 +21,6 @@ import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.config.search.RankProfilesConfig;
import com.yahoo.vespa.config.search.core.RankingConstantsConfig;
-import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainer;
import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.component.ConfigProducerGroup;
@@ -29,9 +28,9 @@ import com.yahoo.vespa.model.container.component.Handler;
import com.yahoo.vespa.model.container.component.Servlet;
import com.yahoo.vespa.model.container.jersey.Jersey2Servlet;
import com.yahoo.vespa.model.container.jersey.RestApi;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
import com.yahoo.vespa.model.utils.FileSender;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -47,7 +46,7 @@ import java.util.stream.Stream;
* @author gjoranv
*/
public final class ApplicationContainerCluster extends ContainerCluster<ApplicationContainer> implements
- BundlesConfig.Producer,
+ ApplicationBundlesConfig.Producer,
QrStartConfig.Producer,
RankProfilesConfig.Producer,
RankingConstantsConfig.Producer,
@@ -135,11 +134,11 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private void addTestrunnerComponentsIfTester(DeployState deployState) {
if (deployState.isHosted() && deployState.getProperties().applicationId().instance().isTester()) {
- addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/vespa-testrunner-components-jar-with-dependencies.jar")));
- addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/vespa-osgi-testrunner-jar-with-dependencies.jar")));
- addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/tenant-cd-api-jar-with-dependencies.jar")));
+ addPlatformBundle(PlatformBundles.absoluteBundlePath("vespa-testrunner-components"));
+ addPlatformBundle(PlatformBundles.absoluteBundlePath("vespa-osgi-testrunner"));
+ addPlatformBundle(PlatformBundles.absoluteBundlePath("tenant-cd-api"));
if(deployState.zone().system().isPublic()) {
- addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/cloud-tenant-cd-jar-with-dependencies.jar")));
+ addPlatformBundle(PlatformBundles.absoluteBundlePath("cloud-tenant-cd"));
}
}
}
@@ -189,10 +188,9 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
public Optional<Integer> getMemoryPercentage() { return Optional.ofNullable(memoryPercentage); }
@Override
- public void getConfig(BundlesConfig.Builder builder) {
+ public void getConfig(ApplicationBundlesConfig.Builder builder) {
applicationBundles.stream().map(FileReference::value)
- .forEach(builder::bundle);
- super.getConfig(builder);
+ .forEach(builder::bundles);
}
@Override
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 5127616ad5e..240157fb7aa 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
@@ -12,12 +12,12 @@ import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.config.provision.Zone;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.core.ApplicationMetadataConfig;
import com.yahoo.container.core.document.ContainerDocumentConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.container.jdisc.JdiscBindingsConfig;
import com.yahoo.container.jdisc.config.HealthMonitorConfig;
@@ -69,8 +69,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
/**
* Parent class for all container cluster types.
*
@@ -87,7 +85,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
ContainerDocumentConfig.Producer,
HealthMonitorConfig.Producer,
ApplicationMetadataConfig.Producer,
- BundlesConfig.Producer,
+ PlatformBundlesConfig.Producer,
IndexInfoConfig.Producer,
IlscriptsConfig.Producer,
SchemamappingConfig.Producer,
@@ -464,6 +462,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
/**
* Adds a bundle present at a known location at the target container nodes.
+ * Note that the set of platform bundles cannot change during the jdisc container's lifetime.
*
* @param bundlePath usually an absolute path, e.g. '$VESPA_HOME/lib/jars/foo.jar'
*/
@@ -472,13 +471,10 @@ public abstract class ContainerCluster<CONTAINER extends Container>
}
@Override
- public void getConfig(BundlesConfig.Builder builder) {
- platformBundles.stream() .map(ContainerCluster::toFileReferenceString)
- .forEach(builder::bundle);
- }
-
- private static String toFileReferenceString(Path path) {
- return DISK_BUNDLE_PREFIX + path.toString();
+ public void getConfig(PlatformBundlesConfig.Builder builder) {
+ platformBundles.stream()
+ .map(Path::toString)
+ .forEach(builder::bundlePaths);
}
@Override
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 58d12663c86..51f526d5efd 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,9 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.search;
-import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.container.QrSearchersConfig;
-import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.prelude.semantics.SemanticRulesConfig;
import com.yahoo.search.config.IndexInfoConfig;
import com.yahoo.search.pagetemplates.PageTemplatesConfig;
@@ -25,7 +23,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import static com.yahoo.vespa.model.container.xml.BundleMapper.searchAndDocprocBundle;
+import static com.yahoo.vespa.model.container.xml.PlatformBundles.searchAndDocprocBundle;
/**
* @author gjoranv
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java
index 284aa3b46c0..232e8fcbd1a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java
@@ -5,7 +5,7 @@ import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.config.search.DispatchConfig;
import com.yahoo.vespa.model.container.component.Component;
-import com.yahoo.vespa.model.container.xml.BundleMapper;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
import com.yahoo.vespa.model.search.IndexedSearchCluster;
/**
@@ -32,7 +32,7 @@ public class DispatcherComponent extends Component<AbstractConfigProducer<?>, Co
String dispatcherComponentId = "dispatcher." + indexedSearchCluster.getClusterName(); // used by ClusterSearcher
return new ComponentModel(dispatcherComponentId,
com.yahoo.search.dispatch.Dispatcher.class.getName(),
- BundleMapper.searchAndDocprocBundle);
+ PlatformBundles.searchAndDocprocBundle);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java
index 2689c2ce71b..248b30eafa7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.model.container.search;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.model.container.component.Component;
-import com.yahoo.vespa.model.container.xml.BundleMapper;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
public class RpcResourcePoolComponent extends Component<RpcResourcePoolComponent, ComponentModel> {
@@ -13,6 +13,6 @@ public class RpcResourcePoolComponent extends Component<RpcResourcePoolComponent
private static ComponentModel toComponentModel(String clusterName) {
String componentId = "rpcresourcepool." + clusterName;
- return new ComponentModel(componentId, com.yahoo.search.dispatch.rpc.RpcResourcePool.class.getName(), BundleMapper.searchAndDocprocBundle);
+ return new ComponentModel(componentId, com.yahoo.search.dispatch.rpc.RpcResourcePool.class.getName(), PlatformBundles.searchAndDocprocBundle);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java
index 19d1b6546a6..4e0bff1c1fc 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java
@@ -26,13 +26,14 @@ public class BundleInstantiationSpecificationBuilder {
BundleInstantiationSpecification instSpec = new BundleInstantiationSpecification(id, classId, bundle);
validate(instSpec);
- return bundle == null ? setBundleForKnownClass(instSpec) : instSpec;
+ return bundle == null ? setBundleForSearchAndDocprocComponents(instSpec) : instSpec;
}
- private static BundleInstantiationSpecification setBundleForKnownClass(BundleInstantiationSpecification spec) {
- return BundleMapper.getBundle(spec.getClassName()).
- map(spec::inBundle).
- orElse(spec);
+ private static BundleInstantiationSpecification setBundleForSearchAndDocprocComponents(BundleInstantiationSpecification spec) {
+ if (PlatformBundles.isSearchAndDocprocClass(spec.getClassName()))
+ return spec.inBundle(PlatformBundles.searchAndDocprocBundle);
+ else
+ return spec;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleMapper.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleMapper.java
deleted file mode 100644
index aa7cb5eb539..00000000000
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleMapper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.model.container.xml;
-
-import com.yahoo.vespa.defaults.Defaults;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * @author gjoranv
- * @author Ulf Lilleengen
- */
-public class BundleMapper {
-
- public enum JarSuffix {
- JAR_WITH_DEPS("-jar-with-dependencies.jar"),
- DEPLOY("-deploy.jar");
-
- public final String suffix;
-
- JarSuffix(String suffix) {
- this.suffix = suffix;
- }
- }
-
- public static final Path LIBRARY_PATH = Paths.get(Defaults.getDefaults().underVespaHome("lib/jars"));
-
- public static final String searchAndDocprocBundle = "container-search-and-docproc";
-
- private static final Map<String, String> bundleFromClass;
- private static final Map<String, Path> bundleFileFromClass;
-
- public static Optional<String> getBundle(String className) {
- return Optional.ofNullable(bundleFromClass.get(className));
- }
-
- public static Optional<Path> getBundlePath(String className) {
- return Optional.ofNullable(absoluteBundlePath(bundleFileFromClass.get(className)));
- }
-
- public static Path absoluteBundlePath(Path fileName) {
- if (fileName == null) return null;
- return LIBRARY_PATH.resolve(fileName);
- }
-
- /**
- * TODO: This is a temporary hack to ensure that users can use our internal components without
- * specifying the bundle in which the components reside. Ideally, this information
- * should be generated during vespamodel build time.
- *
- * The container_maven_plugin has much of the logic in place, but needs to be extended.
- */
- static {
- bundleFromClass = new HashMap<>();
- bundleFileFromClass = new HashMap<>();
-
- bundleFromClass.put("com.yahoo.docproc.AbstractConcreteDocumentFactory", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.docproc.DocumentProcessor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.docproc.SimpleDocumentProcessor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.docproc.util.JoinerDocumentProcessor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.docproc.util.SplitterDocumentProcessor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.example.TimingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.language.simple.SimpleLinguistics", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.cluster.ClusterSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.fastsearch.FastSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.fastsearch.VespaBackEndSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.CJKSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.CollapsePhraseSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.LiteralBoostSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.NoRankingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.NonPhrasingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.NormalizingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.PhrasingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.RecallSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.querytransform.StemmingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.BlendingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.FieldCollapsingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.FillSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.JSONDebugSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.JuniperSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.MultipleResultsSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.PosSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.QuotingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.searcher.ValidateSortingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.semantics.SemanticSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.statistics.StatisticsSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.prelude.templates.SearchRendererAdaptor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.Searcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.cluster.ClusterSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.cluster.PingableSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.FederationSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.ForwardingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.http.ConfiguredHTTPClientSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.http.ConfiguredHTTPProviderSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.http.HTTPClientSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.http.HTTPProviderSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.http.HTTPSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.news.NewsSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.federation.vespa.VespaSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.grouping.GroupingQueryParser", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.grouping.GroupingValidator", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.grouping.vespa.GroupingExecutor", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.handler.SearchWithRendererHandler", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.PageTemplate", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.PageTemplateSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.engine.Resolver", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.engine.resolvers.RandomResolver", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.pagetemplates.model.Renderer", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.query.rewrite.QueryRewriteSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.query.rewrite.SearchChainDispatcherSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.query.rewrite.rewriters.GenericExpansionRewriter", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.query.rewrite.rewriters.MisspellRewriter", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.query.rewrite.rewriters.NameRewriter", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.querytransform.AllLowercasingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.querytransform.DefaultPositionSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.querytransform.LowercasingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.querytransform.NGramSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.querytransform.VespaLowercasingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.rendering.Renderer", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.rendering.SectionedRenderer", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.searchchain.ForkingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.searchchain.example.ExampleSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.searchers.CacheControlSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.statistics.PeakQpsSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.search.statistics.TimingSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.vespa.streamingvisitors.MetricsSearcher", searchAndDocprocBundle);
- bundleFromClass.put("com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher", searchAndDocprocBundle);
- }
-
-}
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 b83632a58a0..41e092c7ea5 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
@@ -154,21 +154,12 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
ApplicationContainerCluster cluster = createContainerCluster(spec, modelContext);
addClusterContent(cluster, spec, modelContext);
- addBundlesForPlatformComponents(cluster);
cluster.setMessageBusEnabled(rpcServerEnabled);
cluster.setRpcServerEnabled(rpcServerEnabled);
cluster.setHttpServerEnabled(httpServerEnabled);
model.setCluster(cluster);
}
- private void addBundlesForPlatformComponents(ApplicationContainerCluster cluster) {
- for (Component<?, ?> component : cluster.getAllComponents()) {
- String componentClass = component.model.bundleInstantiationSpec.getClassName();
- BundleMapper.getBundlePath(componentClass).
- ifPresent(cluster::addPlatformBundle);
- }
- }
-
private ApplicationContainerCluster createContainerCluster(Element spec, ConfigModelContext modelContext) {
return new VespaDomBuilder.DomConfigProducerBuilder<ApplicationContainerCluster>() {
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java
new file mode 100644
index 00000000000..dc2437c1834
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java
@@ -0,0 +1,119 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.xml;
+
+import com.yahoo.vespa.defaults.Defaults;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+
+/**
+ * @author gjoranv
+ * @author Ulf Lilleengen
+ */
+public class PlatformBundles {
+
+ private enum JarSuffix {
+ JAR_WITH_DEPS("-jar-with-dependencies.jar"),
+ DEPLOY("-deploy.jar");
+
+ public final String suffix;
+
+ JarSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+ }
+
+ public static final Path LIBRARY_PATH = Paths.get(Defaults.getDefaults().underVespaHome("lib/jars"));
+ public static final String searchAndDocprocBundle = "container-search-and-docproc";
+
+ private static final Set<String> searchAndDocprocComponents;
+
+ public static boolean isSearchAndDocprocClass(String className) {
+ return searchAndDocprocComponents.contains(className);
+ }
+
+ public static Path absoluteBundlePath(String fileName) {
+ if (fileName == null) return null;
+ return LIBRARY_PATH.resolve(Paths.get(fileName + JarSuffix.JAR_WITH_DEPS.suffix));
+ }
+
+ // This is a hack to allow users to declare components from the search-and-docproc bundle without naming the bundle.
+ static {
+ searchAndDocprocComponents = Set.of(
+ "com.yahoo.docproc.AbstractConcreteDocumentFactory",
+ "com.yahoo.docproc.DocumentProcessor",
+ "com.yahoo.docproc.SimpleDocumentProcessor",
+ "com.yahoo.docproc.util.JoinerDocumentProcessor",
+ "com.yahoo.docproc.util.SplitterDocumentProcessor",
+ "com.yahoo.example.TimingSearcher",
+ "com.yahoo.language.simple.SimpleLinguistics",
+ "com.yahoo.prelude.cluster.ClusterSearcher",
+ "com.yahoo.prelude.fastsearch.FastSearcher",
+ "com.yahoo.prelude.fastsearch.VespaBackEndSearcher",
+ "com.yahoo.prelude.querytransform.CJKSearcher",
+ "com.yahoo.prelude.querytransform.CollapsePhraseSearcher",
+ "com.yahoo.prelude.querytransform.LiteralBoostSearcher",
+ "com.yahoo.prelude.querytransform.NoRankingSearcher",
+ "com.yahoo.prelude.querytransform.NonPhrasingSearcher",
+ "com.yahoo.prelude.querytransform.NormalizingSearcher",
+ "com.yahoo.prelude.querytransform.PhrasingSearcher",
+ "com.yahoo.prelude.querytransform.RecallSearcher",
+ "com.yahoo.prelude.querytransform.StemmingSearcher",
+ "com.yahoo.prelude.searcher.BlendingSearcher",
+ "com.yahoo.prelude.searcher.FieldCollapsingSearcher",
+ "com.yahoo.prelude.searcher.FillSearcher",
+ "com.yahoo.prelude.searcher.JSONDebugSearcher",
+ "com.yahoo.prelude.searcher.JuniperSearcher",
+ "com.yahoo.prelude.searcher.MultipleResultsSearcher",
+ "com.yahoo.prelude.searcher.PosSearcher",
+ "com.yahoo.prelude.searcher.QuotingSearcher",
+ "com.yahoo.prelude.searcher.ValidateSortingSearcher",
+ "com.yahoo.prelude.semantics.SemanticSearcher",
+ "com.yahoo.prelude.statistics.StatisticsSearcher",
+ "com.yahoo.prelude.templates.SearchRendererAdaptor",
+ "com.yahoo.search.Searcher",
+ "com.yahoo.search.cluster.ClusterSearcher",
+ "com.yahoo.search.cluster.PingableSearcher",
+ "com.yahoo.search.federation.FederationSearcher",
+ "com.yahoo.search.federation.ForwardingSearcher",
+ "com.yahoo.search.federation.http.ConfiguredHTTPClientSearcher",
+ "com.yahoo.search.federation.http.ConfiguredHTTPProviderSearcher",
+ "com.yahoo.search.federation.http.HTTPClientSearcher",
+ "com.yahoo.search.federation.http.HTTPProviderSearcher",
+ "com.yahoo.search.federation.http.HTTPSearcher",
+ "com.yahoo.search.federation.news.NewsSearcher",
+ "com.yahoo.search.federation.vespa.VespaSearcher",
+ "com.yahoo.search.grouping.GroupingQueryParser",
+ "com.yahoo.search.grouping.GroupingValidator",
+ "com.yahoo.search.grouping.vespa.GroupingExecutor",
+ "com.yahoo.search.handler.SearchWithRendererHandler",
+ "com.yahoo.search.pagetemplates.PageTemplate",
+ "com.yahoo.search.pagetemplates.PageTemplateSearcher",
+ "com.yahoo.search.pagetemplates.engine.Resolver",
+ "com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver",
+ "com.yahoo.search.pagetemplates.engine.resolvers.RandomResolver",
+ "com.yahoo.search.pagetemplates.model.Renderer",
+ "com.yahoo.search.query.rewrite.QueryRewriteSearcher",
+ "com.yahoo.search.query.rewrite.SearchChainDispatcherSearcher",
+ "com.yahoo.search.query.rewrite.rewriters.GenericExpansionRewriter",
+ "com.yahoo.search.query.rewrite.rewriters.MisspellRewriter",
+ "com.yahoo.search.query.rewrite.rewriters.NameRewriter",
+ "com.yahoo.search.querytransform.AllLowercasingSearcher",
+ "com.yahoo.search.querytransform.DefaultPositionSearcher",
+ "com.yahoo.search.querytransform.LowercasingSearcher",
+ "com.yahoo.search.querytransform.NGramSearcher",
+ "com.yahoo.search.querytransform.VespaLowercasingSearcher",
+ "com.yahoo.search.rendering.Renderer",
+ "com.yahoo.search.rendering.SectionedRenderer",
+ "com.yahoo.search.searchchain.ForkingSearcher",
+ "com.yahoo.search.searchchain.example.ExampleSearcher",
+ "com.yahoo.search.searchers.CacheControlSearcher",
+ "com.yahoo.search.statistics.PeakQpsSearcher",
+ "com.yahoo.search.statistics.TimingSearcher",
+ "com.yahoo.vespa.streamingvisitors.MetricsSearcher",
+ "com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher"
+ );
+ }
+
+}
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/attributes.cfg b/config-model/src/test/derived/imported_fields_inherited_reference/attributes.cfg
new file mode 100644
index 00000000000..1f4d5619248
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/attributes.cfg
@@ -0,0 +1,108 @@
+attribute[].name "ref_from_a"
+attribute[].datatype REFERENCE
+attribute[].collectiontype SINGLE
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported false
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
+attribute[].name "ref_from_b"
+attribute[].datatype REFERENCE
+attribute[].collectiontype SINGLE
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported false
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
+attribute[].name "from_a_int_field"
+attribute[].datatype INT32
+attribute[].collectiontype SINGLE
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported true
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
+attribute[].name "from_b_int_field"
+attribute[].datatype INT32
+attribute[].collectiontype SINGLE
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported true
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/child_a.sd b/config-model/src/test/derived/imported_fields_inherited_reference/child_a.sd
new file mode 100644
index 00000000000..75c16d1eefe
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/child_a.sd
@@ -0,0 +1,8 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+schema child_a {
+ document child_a {
+ field ref_from_a type reference<parent> {
+ indexing: attribute
+ }
+ }
+}
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/child_b.sd b/config-model/src/test/derived/imported_fields_inherited_reference/child_b.sd
new file mode 100644
index 00000000000..b4349fcc65d
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/child_b.sd
@@ -0,0 +1,8 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+schema child_b {
+ document child_b inherits child_a {
+ field ref_from_b type reference<parent> {
+ indexing: attribute
+ }
+ }
+}
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/child_c.sd b/config-model/src/test/derived/imported_fields_inherited_reference/child_c.sd
new file mode 100644
index 00000000000..f798b8c3446
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/child_c.sd
@@ -0,0 +1,7 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+schema child_c {
+ document child_c inherits child_b {
+ }
+ import field ref_from_a.int_field as from_a_int_field {}
+ import field ref_from_b.int_field as from_b_int_field {}
+}
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg b/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg
new file mode 100644
index 00000000000..ca490b053f7
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg
@@ -0,0 +1,110 @@
+enablecompression false
+documenttype[].id -94853056
+documenttype[].name "child_a"
+documenttype[].version 0
+documenttype[].headerstruct 867409663
+documenttype[].bodystruct 0
+documenttype[].inherits[].id 8
+documenttype[].datatype[].id 867409663
+documenttype[].datatype[].type STRUCT
+documenttype[].datatype[].array.element.id 0
+documenttype[].datatype[].map.key.id 0
+documenttype[].datatype[].map.value.id 0
+documenttype[].datatype[].wset.key.id 0
+documenttype[].datatype[].wset.createifnonexistent false
+documenttype[].datatype[].wset.removeifzero false
+documenttype[].datatype[].annotationref.annotation.id 0
+documenttype[].datatype[].sstruct.name "child_a.header"
+documenttype[].datatype[].sstruct.version 0
+documenttype[].datatype[].sstruct.compression.type NONE
+documenttype[].datatype[].sstruct.compression.level 0
+documenttype[].datatype[].sstruct.compression.threshold 95
+documenttype[].datatype[].sstruct.compression.minsize 200
+documenttype[].datatype[].sstruct.field[].name "ref_from_a"
+documenttype[].datatype[].sstruct.field[].id 300427062
+documenttype[].datatype[].sstruct.field[].datatype 427398467
+documenttype[].datatype[].sstruct.field[].detailedtype ""
+documenttype[].fieldsets{[]}.fields[] "ref_from_a"
+documenttype[].referencetype[].id 427398467
+documenttype[].referencetype[].target_type_id 1175161836
+documenttype[].id -94852095
+documenttype[].name "child_b"
+documenttype[].version 0
+documenttype[].headerstruct 670896158
+documenttype[].bodystruct 0
+documenttype[].inherits[].id 8
+documenttype[].inherits[].id -94853056
+documenttype[].datatype[].id 670896158
+documenttype[].datatype[].type STRUCT
+documenttype[].datatype[].array.element.id 0
+documenttype[].datatype[].map.key.id 0
+documenttype[].datatype[].map.value.id 0
+documenttype[].datatype[].wset.key.id 0
+documenttype[].datatype[].wset.createifnonexistent false
+documenttype[].datatype[].wset.removeifzero false
+documenttype[].datatype[].annotationref.annotation.id 0
+documenttype[].datatype[].sstruct.name "child_b.header"
+documenttype[].datatype[].sstruct.version 0
+documenttype[].datatype[].sstruct.compression.type NONE
+documenttype[].datatype[].sstruct.compression.level 0
+documenttype[].datatype[].sstruct.compression.threshold 95
+documenttype[].datatype[].sstruct.compression.minsize 200
+documenttype[].datatype[].sstruct.field[].name "ref_from_b"
+documenttype[].datatype[].sstruct.field[].id 185778735
+documenttype[].datatype[].sstruct.field[].datatype 427398467
+documenttype[].datatype[].sstruct.field[].detailedtype ""
+documenttype[].fieldsets{[]}.fields[] "ref_from_a"
+documenttype[].fieldsets{[]}.fields[] "ref_from_b"
+documenttype[].id -94851134
+documenttype[].name "child_c"
+documenttype[].version 0
+documenttype[].headerstruct 474382653
+documenttype[].bodystruct 0
+documenttype[].inherits[].id 8
+documenttype[].inherits[].id -94852095
+documenttype[].datatype[].id 474382653
+documenttype[].datatype[].type STRUCT
+documenttype[].datatype[].array.element.id 0
+documenttype[].datatype[].map.key.id 0
+documenttype[].datatype[].map.value.id 0
+documenttype[].datatype[].wset.key.id 0
+documenttype[].datatype[].wset.createifnonexistent false
+documenttype[].datatype[].wset.removeifzero false
+documenttype[].datatype[].annotationref.annotation.id 0
+documenttype[].datatype[].sstruct.name "child_c.header"
+documenttype[].datatype[].sstruct.version 0
+documenttype[].datatype[].sstruct.compression.type NONE
+documenttype[].datatype[].sstruct.compression.level 0
+documenttype[].datatype[].sstruct.compression.threshold 95
+documenttype[].datatype[].sstruct.compression.minsize 200
+documenttype[].fieldsets{[]}.fields[] "ref_from_a"
+documenttype[].fieldsets{[]}.fields[] "ref_from_b"
+documenttype[].importedfield[].name "from_a_int_field"
+documenttype[].importedfield[].name "from_b_int_field"
+documenttype[].id 1175161836
+documenttype[].name "parent"
+documenttype[].version 0
+documenttype[].headerstruct 836075987
+documenttype[].bodystruct 0
+documenttype[].inherits[].id 8
+documenttype[].datatype[].id 836075987
+documenttype[].datatype[].type STRUCT
+documenttype[].datatype[].array.element.id 0
+documenttype[].datatype[].map.key.id 0
+documenttype[].datatype[].map.value.id 0
+documenttype[].datatype[].wset.key.id 0
+documenttype[].datatype[].wset.createifnonexistent false
+documenttype[].datatype[].wset.removeifzero false
+documenttype[].datatype[].annotationref.annotation.id 0
+documenttype[].datatype[].sstruct.name "parent.header"
+documenttype[].datatype[].sstruct.version 0
+documenttype[].datatype[].sstruct.compression.type NONE
+documenttype[].datatype[].sstruct.compression.level 0
+documenttype[].datatype[].sstruct.compression.threshold 95
+documenttype[].datatype[].sstruct.compression.minsize 200
+documenttype[].datatype[].sstruct.field[].name "int_field"
+documenttype[].datatype[].sstruct.field[].id 2128822283
+documenttype[].datatype[].sstruct.field[].datatype 0
+documenttype[].datatype[].sstruct.field[].detailedtype ""
+documenttype[].fieldsets{[]}.fields[] "int_field"
+
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/imported-fields.cfg b/config-model/src/test/derived/imported_fields_inherited_reference/imported-fields.cfg
new file mode 100644
index 00000000000..e574bfa2393
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/imported-fields.cfg
@@ -0,0 +1,6 @@
+attribute[].name "from_a_int_field"
+attribute[].referencefield "ref_from_a"
+attribute[].targetfield "int_field"
+attribute[].name "from_b_int_field"
+attribute[].referencefield "ref_from_b"
+attribute[].targetfield "int_field"
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/parent.sd b/config-model/src/test/derived/imported_fields_inherited_reference/parent.sd
new file mode 100644
index 00000000000..0509f1a8565
--- /dev/null
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/parent.sd
@@ -0,0 +1,8 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+schema parent {
+ document parent {
+ field int_field type int {
+ indexing: attribute
+ }
+ }
+}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ImportedFieldsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ImportedFieldsTestCase.java
index b06745f9a75..a0dd89229dd 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ImportedFieldsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ImportedFieldsTestCase.java
@@ -30,4 +30,9 @@ public class ImportedFieldsTestCase extends AbstractExportingTestCase {
public void configs_for_imported_position_field_summary_are_derived() throws IOException, ParseException {
assertCorrectDeriving("imported_position_field_summary", "child");
}
+
+ @Test
+ public void derives_configs_for_imported_fields_when_reference_fields_are_inherited() throws IOException, ParseException {
+ assertCorrectDeriving("imported_fields_inherited_reference", "child_c");
+ }
}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolverTestCase.java
index 0ef696df6cf..ab98706fd44 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolverTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/MatchedElementsOnlyResolverTestCase.java
@@ -149,10 +149,10 @@ public class MatchedElementsOnlyResolverTestCase {
exceptionRule.expect(IllegalArgumentException.class);
exceptionRule.expectMessage("For search 'test', document summary 'default', summary field 'my_field': " +
"'matched-elements-only' is not supported for this field type. " +
- "Supported field types are: array attribute, weighted set attribute, " +
+ "Supported field types are: array of primitive, weighted set of primitive, " +
"array of simple struct, map of primitive type to simple struct, " +
"and map of primitive type to primitive type");
- buildSearch(joinLines("field my_field type array<string> {",
+ buildSearch(joinLines("field my_field type string {",
" indexing: summary",
" summary: matched-elements-only",
"}"));
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
index bed77bd5c77..34f06519ac9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
@@ -15,8 +15,8 @@ import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.Zone;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.core.ApplicationMetadataConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.AppDimensionNames;
@@ -44,7 +44,6 @@ import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -56,11 +55,11 @@ public class MetricsProxyContainerClusterTest {
@Test
public void metrics_proxy_bundle_is_included_in_bundles_config() {
VespaModel model = getModel(servicesWithAdminOnly(), self_hosted);
- var builder = new BundlesConfig.Builder();
+ var builder = new PlatformBundlesConfig.Builder();
model.getConfig(builder, CLUSTER_CONFIG_ID);
- BundlesConfig config = builder.build();
- assertEquals(1, config.bundle().size());
- assertThat(config.bundle(0).value(), endsWith(METRICS_PROXY_BUNDLE_FILE.toString()));
+ PlatformBundlesConfig config = builder.build();
+ assertEquals(1, config.bundlePaths().size());
+ assertThat(config.bundlePaths(0), endsWith(METRICS_PROXY_BUNDLE_FILE.toString()));
}
@Test
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 03c05af1145..97359b392a5 100755
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.model.container;
import com.yahoo.cloud.config.ClusterInfoConfig;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.cloud.config.RoutingProviderConfig;
-import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
@@ -16,7 +15,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provisioning.FlavorsConfig;
-import com.yahoo.container.BundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.Host;
@@ -292,9 +291,9 @@ public class ContainerClusterTest {
.zone(zone).build();
MockRoot root = new MockRoot("foo", state);
ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", state);
- BundlesConfig.Builder bundleBuilder = new BundlesConfig.Builder();
+ var bundleBuilder = new PlatformBundlesConfig.Builder();
cluster.getConfig(bundleBuilder);
- List<String> installedBundles = bundleBuilder.build().bundle().stream().map(FileReference::value).collect(Collectors.toList());
+ List<String> installedBundles = bundleBuilder.build().bundlePaths();
assertEquals(expectedBundleNames.size(), installedBundles.size());
assertThat(installedBundles, containsInAnyOrder(
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java
index d2a840d1fbc..70ae6a27324 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java
@@ -33,7 +33,7 @@ public class BundleInstantiationSpecificationBuilderTest {
@Test
public void bundle_is_replaced_for_internal_class() throws IOException, SAXException {
String internalClass = GroupingValidator.class.getName();
- verifyExpectedBundle(internalClass, null, BundleMapper.searchAndDocprocBundle);
+ verifyExpectedBundle(internalClass, null, PlatformBundles.searchAndDocprocBundle);
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
index eda90b03147..e9048cf7863 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
@@ -3,17 +3,16 @@ package com.yahoo.vespa.model.container.xml;
import com.yahoo.config.docproc.DocprocConfig;
import com.yahoo.config.docproc.SchemamappingConfig;
-import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
-import com.yahoo.container.BundlesConfig;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.core.ChainsConfig;
import com.yahoo.container.jdisc.ContainerMbusConfig;
import com.yahoo.document.config.DocumentmanagerConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.HostPorts;
-import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ApplicationContainer;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ContainerModel;
import com.yahoo.vespa.model.container.docproc.DocprocChain;
import com.yahoo.vespa.model.container.docproc.DocumentProcessor;
@@ -30,8 +29,8 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
@@ -46,7 +45,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
private ContainerMbusConfig containerMbusConfig;
private ComponentsConfig componentsConfig;
private ChainsConfig chainsConfig;
- private BundlesConfig bundlesConfig;
private SchemamappingConfig schemamappingConfig;
private DocprocConfig docprocConfig;
private QrStartConfig qrStartConfig;
@@ -64,7 +62,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
cluster.getConfigId() + "/component/com.yahoo.docproc.jdisc.DocumentProcessingHandler");
documentmanagerConfig = root.getConfig(DocumentmanagerConfig.class, cluster.getConfigId());
- bundlesConfig = root.getConfig(BundlesConfig.class, cluster.getConfigId());
schemamappingConfig = root.getConfig(SchemamappingConfig.class, cluster.getContainers().get(0).getConfigId());
qrStartConfig = root.getConfig(QrStartConfig.class, cluster.getConfigId());
docprocConfig = root.getConfig(DocprocConfig.class, cluster.getConfigId());
@@ -207,11 +204,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
}
@Test
- public void testBundlesConfig() {
- assertTrue(bundlesConfig.bundle().isEmpty());
- }
-
- @Test
public void testSchemaMappingConfig() {
assertTrue(schemamappingConfig.fieldmapping().isEmpty());
}
diff --git a/configdefinitions/src/vespa/configserver.def b/configdefinitions/src/vespa/configserver.def
index 0d216480c4e..cde539f25f4 100644
--- a/configdefinitions/src/vespa/configserver.def
+++ b/configdefinitions/src/vespa/configserver.def
@@ -53,7 +53,7 @@ ztsUrl string default=""
maintainerIntervalMinutes int default=30
# TODO: Default set to a high value (1 year) => maintainer will not run, change when maintainer verified out in prod
tenantsMaintainerIntervalMinutes int default=525600
-keepUnusedFileReferencesHours int default=12
+keepUnusedFileReferencesHours int default=4
# Bootstrapping
# How long bootstrapping can take before giving up (in seconds)
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 41ec7626049..58b7d0ec5bb 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
@@ -206,6 +206,14 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
this.metric = metric;
}
+ public Clock clock() {
+ return clock;
+ }
+
+ public Metric metric() {
+ return metric;
+ }
+
// ---------------- Deploying ----------------------------------------------------------------
public PrepareResult prepare(Tenant tenant, long sessionId, PrepareParams prepareParams, Instant now) {
@@ -677,7 +685,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getSessionRepository().getLocalSessions()));
Set<ApplicationId> applicationIds = new HashSet<>();
- sessionsPerTenant.values().forEach(sessionList -> sessionList.forEach(s -> applicationIds.add(s.getApplicationId())));
+ sessionsPerTenant.values()
+ .forEach(sessionList -> sessionList.stream()
+ .map(Session::getApplicationId)
+ .filter(Objects::nonNull)
+ .forEach(applicationIds::add));
Map<ApplicationId, Long> activeSessions = new HashMap<>();
applicationIds.forEach(applicationId -> {
@@ -862,12 +874,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
// We make no validation that the hostname is actually allocated to the given application since
// most applications under hosted-vespa are not known to the model and it's OK for a user to get
// logs for any host if they are authorized for the hosted-vespa tenant.
- if (hostname.isPresent()) {
- if (HOSTED_VESPA_TENANT.equals(applicationId.tenant()))
- return "http://" + hostname.get() + ":8080/logs";
- else
- throw new IllegalArgumentException("Using hostname parameter when getting logs is not supported for application "
- + applicationId);
+ if (hostname.isPresent() && HOSTED_VESPA_TENANT.equals(applicationId.tenant())) {
+ return "http://" + hostname.get() + ":8080/logs";
}
Application application = getApplication(applicationId);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
index def629f738c..466138c817f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
@@ -134,13 +134,13 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
try (ActionTimer timer = applicationRepository.timerFor(session.getApplicationId(), "deployment.activateMillis")) {
TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout);
+ validateSessionStatus(session);
ApplicationId applicationId = session.getApplicationId();
if ( ! timeoutBudget.hasTimeLeft()) throw new RuntimeException("Timeout exceeded when trying to activate '" + applicationId + "'");
RemoteSession previousActiveSession;
try (Lock lock = tenant.getApplicationRepo().lock(applicationId)) {
- validateSessionStatus(session);
NestedTransaction transaction = new NestedTransaction();
previousActiveSession = applicationRepository.getActiveSession(applicationId);
transaction.add(deactivateCurrentActivateNew(previousActiveSession, session, ignoreSessionStaleFailure));
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java
index 7e83d7013e0..c634d82010e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java
@@ -36,16 +36,14 @@ public class ZooKeeperClient {
private final ConfigCurator configCurator;
private final DeployLogger logger;
- private final boolean logFine;
/* This is the generation that will be used for reading and writing application data. (1 more than last deployed application) */
private final Path rootPath;
private static final ApplicationFile.PathFilter xmlFilter = path -> path.getName().endsWith(".xml");
- public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, boolean logFine, Path rootPath) {
+ public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, Path rootPath) {
this.configCurator = configCurator;
this.logger = logger;
- this.logFine = logFine;
this.rootPath = rootPath;
}
@@ -62,7 +60,6 @@ public class ZooKeeperClient {
try {
while (retries > 0) {
try {
- logFine("Setting up ZooKeeper nodes for this application");
createZooKeeperNodes();
break;
} catch (RuntimeException e) {
@@ -105,16 +102,11 @@ public class ZooKeeperClient {
* @param app the application package to feed to zookeeper
*/
void write(ApplicationPackage app) {
- logFine("Feeding application config into ZooKeeper");
try {
- logFine("Feeding user def files into ZooKeeper");
writeUserDefs(app);
- logFine("Feeding application package into ZooKeeper");
writeSomeOf(app);
writeSearchDefinitions(app);
writeUserIncludeDirs(app, app.getUserIncludeDirs());
- logFine("Feeding sd from docproc bundle into ZooKeeper");
- logFine("Write application metadata into ZooKeeper");
write(app.getMetaData());
} catch (Exception e) {
throw new IllegalStateException("Unable to write vespa model to config server(s) " + System.getProperty("configsources") + "\n" +
@@ -269,7 +261,6 @@ public class ZooKeeperClient {
}
private void write(Version vespaVersion, FileRegistry fileRegistry) {
- logFine("Feeding file registry data into ZooKeeper");
String exportedRegistry = PreGeneratedFileRegistry.exportRegistry(fileRegistry);
configCurator.putData(getZooKeeperAppPath(null).append(ZKApplicationPackage.fileRegistryNode).getAbsolute(),
@@ -288,7 +279,6 @@ public class ZooKeeperClient {
}
void cleanupZooKeeper() {
- logFine("Exception occurred. Cleaning up ZooKeeper");
try {
for (String subPath : Arrays.asList(
ConfigCurator.DEFCONFIGS_ZK_SUBPATH,
@@ -317,12 +307,6 @@ public class ZooKeeperClient {
}
}
- private void logFine(String msg) {
- if (logFine) {
- logger.log(Level.FINE, msg);
- }
- }
-
public void write(AllocatedHosts hosts) throws IOException {
configCurator.putData(rootPath.append(ZKApplicationPackage.allocatedHostsNode).getAbsolute(),
AllocatedHostsSerializer.toJson(hosts));
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionFactory.java
index 1cfe30270c3..ae18c3e6e95 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionFactory.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionFactory.java
@@ -16,7 +16,7 @@ import java.io.File;
@SuppressWarnings("WeakerAccess")
public class FileDistributionFactory {
- private final ConfigserverConfig configserverConfig;
+ protected final ConfigserverConfig configserverConfig;
private final Supervisor supervisor = new Supervisor(new Transport());
@Inject
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionUtil.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionUtil.java
index c06e4da2b7b..de3a2b47233 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionUtil.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionUtil.java
@@ -1,6 +1,7 @@
package com.yahoo.vespa.config.server.filedistribution;
import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.FileReference;
import com.yahoo.config.subscription.ConfigSourceSet;
import com.yahoo.jrt.Supervisor;
import com.yahoo.jrt.Transport;
@@ -50,6 +51,10 @@ public class FileDistributionUtil {
return configServers.size() > 0 ? new JRTConnectionPool(new ConfigSourceSet(configServers)) : emptyConnectionPool();
}
+ public static boolean fileReferenceExistsOnDisk(File downloadDirectory, FileReference applicationPackageReference) {
+ return getFileReferencesOnDisk(downloadDirectory).contains(applicationPackageReference.value());
+ }
+
static ConnectionPool emptyConnectionPool() {
return new EmptyConnectionPool();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java
deleted file mode 100644
index db70a51b2b4..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.config.server.filedistribution;
-
-import com.yahoo.config.model.api.FileDistribution;
-import com.yahoo.config.model.application.provider.MockFileRegistry;
-
-import java.io.File;
-
-/**
- * @author Ulf Lilleengen
- */
-public class MockFileDistributionProvider extends FileDistributionProvider {
- public int timesCalled = 0;
-
- public MockFileDistributionProvider(File fileReferencesDir) {
- super(new MockFileRegistry(), new MockFileDistribution(fileReferencesDir));
- }
-
- public FileDistribution getFileDistribution() {
- timesCalled++;
- return super.getFileDistribution();
- }
-
-}
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 d1861184c24..749f57b3104 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
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.http.v2;
import com.google.inject.Inject;
import com.yahoo.component.Version;
+import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
@@ -24,8 +25,11 @@ import com.yahoo.vespa.config.server.http.HttpErrorResponse;
import com.yahoo.vespa.config.server.http.HttpHandler;
import com.yahoo.vespa.config.server.http.JSONResponse;
import com.yahoo.vespa.config.server.http.NotFoundException;
+import com.yahoo.vespa.config.server.session.RemoteSession;
import com.yahoo.vespa.config.server.tenant.Tenant;
+import com.yahoo.vespa.defaults.Defaults;
+import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
@@ -34,6 +38,8 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.fileReferenceExistsOnDisk;
+
/**
* Operations on applications (delete, wait for config convergence, restart, application content etc.)
*
@@ -154,11 +160,25 @@ public class ApplicationHandler extends HttpHandler {
}
}
+ return getApplicationResponse(applicationId);
+ }
+
+ GetApplicationResponse getApplicationResponse(ApplicationId applicationId) {
Tenant tenant = applicationRepository.getTenant(applicationId);
Optional<ApplicationSet> applicationSet = applicationRepository.getCurrentActiveApplicationSet(tenant, applicationId);
+ String applicationPackage = "";
+ RemoteSession session = applicationRepository.getActiveSession(applicationId);
+ if (session != null) {
+ FileReference applicationPackageReference = session.getApplicationPackageReference();
+ File downloadDirectory = new File(Defaults.getDefaults().underVespaHome(applicationRepository.configserverConfig().fileReferencesDir()));
+ if (applicationPackageReference != null && ! fileReferenceExistsOnDisk(downloadDirectory, applicationPackageReference))
+ applicationPackage = applicationPackageReference.value();
+ }
+
return new GetApplicationResponse(Response.Status.OK,
applicationRepository.getApplicationGeneration(applicationId),
- applicationSet.get().getAllVersions(applicationId));
+ applicationSet.get().getAllVersions(applicationId),
+ applicationPackage);
}
@Override
@@ -316,9 +336,10 @@ public class ApplicationHandler extends HttpHandler {
}
private static class GetApplicationResponse extends JSONResponse {
- GetApplicationResponse(int status, long generation, List<Version> modelVersions) {
+ GetApplicationResponse(int status, long generation, List<Version> modelVersions, String applicationPackageReference) {
super(status);
object.setLong("generation", generation);
+ object.setString("applicationPackageFileReference", applicationPackageReference);
Cursor modelVersionArray = object.setArray("modelVersions");
modelVersions.forEach(version -> modelVersionArray.addString(version.toFullString()));
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
index 7c983ab48a0..e539acba916 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
@@ -16,11 +16,10 @@ import com.yahoo.vespa.flags.Flags;
import java.io.File;
import java.time.Duration;
-import java.util.Set;
import java.util.logging.Logger;
-import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.getFileReferencesOnDisk;
import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.createConnectionPool;
+import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.fileReferenceExistsOnDisk;
/**
* Verifies that all active sessions has an application package on local disk.
@@ -40,19 +39,19 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer {
ApplicationPackageMaintainer(ApplicationRepository applicationRepository,
Curator curator,
Duration interval,
- ConfigserverConfig configserverConfig,
FlagSource flagSource) {
super(applicationRepository, curator, flagSource, interval, interval);
this.applicationRepository = applicationRepository;
- this.configserverConfig = configserverConfig;
+ this.configserverConfig = applicationRepository.configserverConfig();
distributeApplicationPackage = Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE.bindTo(flagSource);
downloadDirectory = new File(Defaults.getDefaults().underVespaHome(configserverConfig.fileReferencesDir()));
}
@Override
- protected void maintain() {
- if (! distributeApplicationPackage.value()) return;
+ protected boolean maintain() {
+ boolean success = true;
+ if (! distributeApplicationPackage.value()) return success;
try (var fileDownloader = new FileDownloader(createConnectionPool(configserverConfig), downloadDirectory)) {
for (var applicationId : applicationRepository.listApplications()) {
@@ -65,10 +64,11 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer {
log.fine(() -> "Verifying application package file reference " + applicationPackage + " for session " + sessionId);
if (applicationPackage != null) {
- if (missingOnDisk(applicationPackage)) {
+ if (! fileReferenceExistsOnDisk(downloadDirectory, applicationPackage)) {
log.fine(() -> "Downloading missing application package for application " + applicationId + " - session " + sessionId);
if (fileDownloader.getFile(applicationPackage).isEmpty()) {
+ success = false;
log.warning("Failed to download application package for application " + applicationId + " - session " + sessionId);
continue;
}
@@ -77,6 +77,7 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer {
}
}
}
+ return success;
}
private void createLocalSessionIfMissing(ApplicationId applicationId, long sessionId) {
@@ -86,9 +87,4 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer {
sessionRepository.createLocalSessionUsingDistributedApplicationPackage(sessionId);
}
- private boolean missingOnDisk(FileReference applicationPackageReference) {
- Set<String> fileReferencesOnDisk = getFileReferencesOnDisk(downloadDirectory);
- return ! fileReferencesOnDisk.contains(applicationPackageReference.value());
- }
-
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
index 5369bbef366..5854b1d85da 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
@@ -3,7 +3,9 @@ package com.yahoo.vespa.config.server.maintenance;
import com.yahoo.concurrent.maintenance.JobControl;
import com.yahoo.concurrent.maintenance.JobControlState;
+import com.yahoo.concurrent.maintenance.JobMetrics;
import com.yahoo.concurrent.maintenance.Maintainer;
+import com.yahoo.jdisc.Metric;
import com.yahoo.path.Path;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.config.server.ApplicationRepository;
@@ -13,6 +15,7 @@ import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.ListFlag;
import java.time.Duration;
+import java.util.Map;
import java.util.Set;
/**
@@ -26,16 +29,24 @@ public abstract class ConfigServerMaintainer extends Maintainer {
ConfigServerMaintainer(ApplicationRepository applicationRepository, Curator curator, FlagSource flagSource,
Duration initialDelay, Duration interval) {
- super(null, interval, initialDelay, new JobControl(new JobControlFlags(curator, flagSource)));
+ super(null, interval, initialDelay, new JobControl(new JobControlFlags(curator, flagSource)),
+ jobMetrics(applicationRepository.metric()));
this.applicationRepository = applicationRepository;
}
+ private static JobMetrics jobMetrics(Metric metric) {
+ return new JobMetrics((job, consecutiveFailures) -> {
+ metric.set("maintenance.consecutiveFailures", consecutiveFailures, metric.createContext(Map.of("job", job)));
+ });
+ }
+
private static class JobControlFlags implements JobControlState {
private static final Path root = Path.fromString("/configserver/v1/");
- private static final Path lockRoot = root.append("locks");
+ private static final Path lockRoot = root.append("locks");
private final Curator curator;
+
private final ListFlag<String> inactiveJobsFlag;
public JobControlFlags(Curator curator, FlagSource flagSource) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
index a6585be391c..ecdca39dc72 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
@@ -4,9 +4,8 @@ package com.yahoo.vespa.config.server.maintenance;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.config.provision.SystemName;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.config.server.ApplicationRepository;
-import com.yahoo.vespa.config.server.filedistribution.FileDistributionFactory;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
@@ -30,19 +29,18 @@ public class ConfigServerMaintenance extends AbstractComponent {
public ConfigServerMaintenance(ConfigserverConfig configserverConfig,
ApplicationRepository applicationRepository,
Curator curator,
- FileDistributionFactory fileDistributionFactory,
- FlagSource flagSource) {
+ FlagSource flagSource,
+ Metric metric) {
DefaultTimes defaults = new DefaultTimes(configserverConfig);
- // TODO: Disabled until we have application metadata
+ // TODO: Disabled until we have application metadata per tenant
//tenantsMaintainer = new TenantsMaintainer(applicationRepository, curator, defaults.tenantsMaintainerInterval);
- fileDistributionMaintainer = new FileDistributionMaintainer(applicationRepository, curator, defaults.defaultInterval, configserverConfig, flagSource);
+ fileDistributionMaintainer = new FileDistributionMaintainer(applicationRepository, curator, defaults.defaultInterval, flagSource);
sessionsMaintainer = new SessionsMaintainer(applicationRepository, curator, Duration.ofMinutes(1), flagSource);
- applicationPackageMaintainer = new ApplicationPackageMaintainer(applicationRepository, curator, Duration.ofMinutes(1), configserverConfig, flagSource);
+ applicationPackageMaintainer = new ApplicationPackageMaintainer(applicationRepository, curator, Duration.ofMinutes(1), flagSource);
}
@Override
public void deconstruct() {
- //tenantsMaintainer.close();
fileDistributionMaintainer.close();
sessionsMaintainer.close();
applicationPackageMaintainer.close();
@@ -55,16 +53,9 @@ public class ConfigServerMaintenance extends AbstractComponent {
private static class DefaultTimes {
private final Duration defaultInterval;
- private final Duration tenantsMaintainerInterval;
DefaultTimes(ConfigserverConfig configserverConfig) {
this.defaultInterval = Duration.ofMinutes(configserverConfig.maintainerIntervalMinutes());
- boolean isCd = configserverConfig.system().equals(SystemName.cd.value());
- // TODO: Want job control or feature flag to control when to run this, for now use a very
- // long interval to avoid running the maintainer except in CD
- this.tenantsMaintainerInterval = isCd
- ? defaultInterval
- : Duration.ofMinutes(configserverConfig.tenantsMaintainerIntervalMinutes());
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
index ed323438e3f..3980ae9d980 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
@@ -21,22 +21,21 @@ public class FileDistributionMaintainer extends ConfigServerMaintainer {
private final ApplicationRepository applicationRepository;
private final File fileReferencesDir;
- private final ConfigserverConfig configserverConfig;
+ private final Duration maxUnusedFileReferenceAge;
FileDistributionMaintainer(ApplicationRepository applicationRepository,
Curator curator,
Duration interval,
- ConfigserverConfig configserverConfig,
FlagSource flagSource) {
super(applicationRepository, curator, flagSource, interval, interval);
this.applicationRepository = applicationRepository;
- this.configserverConfig = configserverConfig;
- this.fileReferencesDir = new File(Defaults.getDefaults().underVespaHome(configserverConfig.fileReferencesDir()));
+ this.maxUnusedFileReferenceAge = Duration.ofHours(applicationRepository.configserverConfig().keepUnusedFileReferencesHours());
+ this.fileReferencesDir = new File(Defaults.getDefaults().underVespaHome(applicationRepository.configserverConfig().fileReferencesDir()));
}
@Override
- protected void maintain() {
- applicationRepository.deleteUnusedFiledistributionReferences(fileReferencesDir,
- Duration.ofHours(configserverConfig.keepUnusedFileReferencesHours()));
+ protected boolean maintain() {
+ applicationRepository.deleteUnusedFiledistributionReferences(fileReferencesDir, maxUnusedFileReferenceAge);
+ return true;
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java
index 4adf287448d..72e8e08e38f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java
@@ -26,13 +26,13 @@ public class SessionsMaintainer extends ConfigServerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
applicationRepository.deleteExpiredLocalSessions();
// Expired remote sessions are sessions that belong to an application that have external deployments that
// are no longer active
if (hostedVespa) {
- Duration expiryTime = Duration.ofDays(1);
+ Duration expiryTime = Duration.ofHours(12);
int deleted = applicationRepository.deleteExpiredRemoteSessions(expiryTime);
log.log(LogLevel.FINE, "Deleted " + deleted + " expired remote sessions, expiry time " + expiryTime);
}
@@ -41,5 +41,7 @@ public class SessionsMaintainer extends ConfigServerMaintainer {
int deleted = applicationRepository.deleteExpiredLocks(lockExpiryTime);
if (deleted > 0)
log.log(LogLevel.INFO, "Deleted " + deleted + " locks older than " + lockExpiryTime);
+
+ return true;
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/TenantsMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/TenantsMaintainer.java
index 9a81d9f7547..d29eea842f5 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/TenantsMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/TenantsMaintainer.java
@@ -28,8 +28,9 @@ public class TenantsMaintainer extends ConfigServerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
applicationRepository.deleteUnusedTenants(ttlForUnusedTenant, clock.instant());
+ return true;
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java
index c520094e294..23300239d17 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java
@@ -15,6 +15,7 @@ import org.apache.zookeeper.KeeperException;
import java.time.Clock;
import java.util.Optional;
+import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -91,15 +92,22 @@ public class RemoteSession extends Session {
try {
completionWaiter.notifyCompletion();
} catch (RuntimeException e) {
- // Throw only if we get something else than NoNodeException -- NoNodeException might happen when
- // the session is no longer in use (e.g. the app using this session has been deleted) and this method
- // has not been called yet for the previous session operation
- // on a minority of the config servers (see awaitInternal() method in this class)
- if (e.getCause().getClass() != KeeperException.NoNodeException.class) {
+ // Throw only if we get something else than NoNodeException or NodeExistsException.
+ // NoNodeException might happen when the session is no longer in use (e.g. the app using this session
+ // has been deleted) and this method has not been called yet for the previous session operation on a
+ // minority of the config servers.
+ // NodeExistsException might happen if an event for this node is delivered more than once, in that case
+ // this is a no-op
+ Set<Class<? extends KeeperException>> acceptedExceptions = Set.of(KeeperException.NoNodeException.class,
+ KeeperException.NodeExistsException.class);
+ Class<? extends Throwable> exceptionClass = e.getCause().getClass();
+ if (acceptedExceptions.contains(exceptionClass))
+ log.log(Level.INFO, "Not able to notify completion for session: " + getSessionId() + ", node " +
+ (exceptionClass.equals(KeeperException.NoNodeException.class)
+ ? "has been deleted"
+ : "already exists"));
+ else
throw e;
- } else {
- log.log(Level.INFO, "Not able to notify completion for session: " + getSessionId() + ", node has been deleted");
- }
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
index 3643b237d7e..a6818d1e43f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
@@ -70,9 +70,18 @@ public abstract class Session implements Comparable<Session> {
* @return log preamble
*/
public String logPre() {
- return getApplicationId().equals(ApplicationId.defaultId())
- ? TenantRepository.logPre(getTenantName())
- : TenantRepository.logPre(getApplicationId());
+ Optional<ApplicationId> applicationId;
+ // We might not be able to read application id from zookeeper
+ // e.g. when the app has been deleted. Use tenant name in that case.
+ try {
+ applicationId = Optional.of(getApplicationId());
+ } catch (Exception e) {
+ applicationId = Optional.empty();
+ }
+ return applicationId
+ .filter(appId -> ! appId.equals(ApplicationId.defaultId()))
+ .map(TenantRepository::logPre)
+ .orElse(TenantRepository.logPre(getTenantName()));
}
public Instant getCreateTime() {
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 b6b0ac45bb5..35bbc1a8233 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
@@ -120,10 +120,10 @@ public class SessionPreparer {
* @param tenantPath Zookeeper path for the tenant for this session
* @return the config change actions that must be done to handle the activation of the models prepared.
*/
- public ConfigChangeActions prepare(HostValidator<ApplicationId> hostValidator, DeployLogger logger, PrepareParams params,
- Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath,
- Instant now, File serverDbSessionDir, ApplicationPackage applicationPackage,
- SessionZooKeeperClient sessionZooKeeperClient) {
+ public PrepareResult prepare(HostValidator<ApplicationId> hostValidator, DeployLogger logger, PrepareParams params,
+ Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath,
+ Instant now, File serverDbSessionDir, ApplicationPackage applicationPackage,
+ SessionZooKeeperClient sessionZooKeeperClient) {
Preparation preparation = new Preparation(hostValidator, logger, params, currentActiveApplicationSet,
tenantPath, serverDbSessionDir, applicationPackage, sessionZooKeeperClient);
@@ -313,8 +313,8 @@ public class SessionPreparer {
checkTimeout("distribute files");
}
- ConfigChangeActions result() {
- return prepareResult.getConfigChangeActions();
+ PrepareResult result() {
+ return prepareResult;
}
private List<ContainerEndpoint> readEndpointsIfNull(List<ContainerEndpoint> endpoints) {
@@ -352,7 +352,7 @@ public class SessionPreparer {
}
/** The result of preparation over all model versions */
- private static class PrepareResult {
+ static class PrepareResult {
private final AllocatedHosts allocatedHosts;
private final ImmutableList<PreparedModelsBuilder.PreparedModelResult> results;
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 0f6ba37cf51..98620877b1f 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
@@ -164,7 +164,8 @@ public class SessionRepository {
ConfigChangeActions actions = sessionPreparer.prepare(applicationRepo.getHostValidator(), logger, params,
currentActiveApplicationSet, tenantPath, now,
getSessionAppDir(sessionId),
- session.getApplicationPackage(), sessionZooKeeperClient);
+ session.getApplicationPackage(), sessionZooKeeperClient)
+ .getConfigChangeActions();
session.setPrepared();
waiter.awaitCompletion(params.getTimeoutBudget().timeLeft());
return actions;
@@ -571,11 +572,18 @@ public class SessionRepository {
throw new IllegalArgumentException(sourceDir.getAbsolutePath() + " is not a directory");
// Copy app atomically: Copy to a temp dir and move to destination
- java.nio.file.Path tempDestinationDir = Files.createTempDirectory(destinationDir.getParentFile().toPath(), "app-package");
- log.log(Level.FINE, "Copying dir " + sourceDir.getAbsolutePath() + " to " + tempDestinationDir.toFile().getAbsolutePath());
- IOUtils.copyDirectory(sourceDir, tempDestinationDir.toFile());
- log.log(Level.FINE, "Moving " + tempDestinationDir + " to " + destinationDir.getAbsolutePath());
- Files.move(tempDestinationDir, destinationDir.toPath(), StandardCopyOption.ATOMIC_MOVE);
+ java.nio.file.Path tempDestinationDir = null;
+ try {
+ tempDestinationDir = Files.createTempDirectory(destinationDir.getParentFile().toPath(), "app-package");
+ log.log(Level.FINE, "Copying dir " + sourceDir.getAbsolutePath() + " to " + tempDestinationDir.toFile().getAbsolutePath());
+ IOUtils.copyDirectory(sourceDir, tempDestinationDir.toFile());
+ log.log(Level.FINE, "Moving " + tempDestinationDir + " to " + destinationDir.getAbsolutePath());
+ Files.move(tempDestinationDir, destinationDir.toPath(), StandardCopyOption.ATOMIC_MOVE);
+ } finally {
+ // In case some of the operations above fail
+ if (tempDestinationDir != null)
+ IOUtils.recursiveDeleteDir(tempDestinationDir.toFile());
+ }
}
/**
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 807629a2148..1b9527f4376 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
@@ -150,8 +150,8 @@ public class SessionZooKeeperClient {
}
public ApplicationId readApplicationId() {
- if ( ! configCurator.exists(applicationIdPath())) return ApplicationId.defaultId();
- return ApplicationId.fromSerializedForm(configCurator.getData(applicationIdPath()));
+ String idString = configCurator.getData(applicationIdPath());
+ return idString == null ? null : ApplicationId.fromSerializedForm(idString);
}
void writeApplicationPackageReference(FileReference applicationPackageReference) {
@@ -214,7 +214,7 @@ public class SessionZooKeeperClient {
}
public ZooKeeperDeployer createDeployer(DeployLogger logger) {
- ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, logger, true, sessionPath);
+ ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, logger, sessionPath);
return new ZooKeeperDeployer(zkClient);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java
index 685856d5cf8..11cec9efd95 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java
@@ -93,8 +93,7 @@ public class ZKApplicationPackage implements ApplicationPackage {
try {
return PreGeneratedFileRegistry.importRegistry(zkApplication.getDataReader(fileRegistryNode));
} catch (Exception e) {
- throw new RuntimeException("Could not determine which files to distribute. " +
- "Please try redeploying the application", e);
+ throw new RuntimeException("Could not determine which files to distribute", e);
}
}
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 a4b2bfb8902..f74378b32a9 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
@@ -121,14 +121,15 @@ public class ApplicationRepositoryTest {
public void setup(FlagSource flagSource) throws IOException {
Curator curator = new MockCurator();
configCurator = ConfigCurator.create(curator);
+ ConfigserverConfig configserverConfig = new ConfigserverConfig.Builder()
+ .payloadCompressionType(ConfigserverConfig.PayloadCompressionType.Enum.UNCOMPRESSED)
+ .configServerDBDir(temporaryFolder.newFolder().getAbsolutePath())
+ .configDefinitionsDir(temporaryFolder.newFolder().getAbsolutePath())
+ .fileReferencesDir(temporaryFolder.newFolder().getAbsolutePath())
+ .build();
TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder()
.curator(curator)
- .configServerConfig(new ConfigserverConfig.Builder()
- .payloadCompressionType(ConfigserverConfig.PayloadCompressionType.Enum.UNCOMPRESSED)
- .configServerDBDir(temporaryFolder.newFolder().getAbsolutePath())
- .configDefinitionsDir(temporaryFolder.newFolder().getAbsolutePath())
- .fileReferencesDir(temporaryFolder.newFolder().getAbsolutePath())
- .build())
+ .configServerConfig(configserverConfig)
.flagSource(flagSource)
.clock(clock)
.build();
@@ -142,7 +143,11 @@ public class ApplicationRepositoryTest {
applicationRepository = new ApplicationRepository(tenantRepository,
provisioner,
orchestrator,
- clock);
+ configserverConfig,
+ new MockLogRetriever(),
+ clock,
+ new MockTesterClient(),
+ new NullMetric());
timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(60));
}
@@ -234,14 +239,6 @@ public class ApplicationRepositoryTest {
assertEquals(200, response.getStatus());
}
- @Test(expected = IllegalArgumentException.class)
- public void refuseToGetLogsFromHostnameNotInApplication() {
- applicationRepository = createApplicationRepository();
- deployApp(testAppLogServerWithContainer);
- HttpResponse response = applicationRepository.getLogs(applicationId(), Optional.of("host123.fake.yahoo.com"), "");
- assertEquals(200, response.getStatus());
- }
-
@Test
public void deleteUnusedFileReferences() throws IOException {
File fileReferencesDir = temporaryFolder.newFolder();
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
index f03550c0a80..68dd5396cf1 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
@@ -51,7 +51,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
private final TenantListener tenantListener;
private final PermanentApplicationPackage permanentApplicationPackage;
private final HostRegistries hostRegistries;
- private final FileDistributionFactory fileDistributionProvider;
+ private final FileDistributionFactory fileDistributionFactory;
private final ModelFactoryRegistry modelFactoryRegistry;
private final Optional<Provisioner> hostProvisioner;
private final Zone zone;
@@ -65,7 +65,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
private TestComponentRegistry(Curator curator, ConfigCurator configCurator, Metrics metrics,
ModelFactoryRegistry modelFactoryRegistry,
PermanentApplicationPackage permanentApplicationPackage,
- FileDistributionFactory fileDistributionProvider,
+ FileDistributionFactory fileDistributionFactory,
HostRegistries hostRegistries,
ConfigserverConfig configserverConfig,
SessionPreparer sessionPreparer,
@@ -86,7 +86,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
this.defRepo = defRepo;
this.permanentApplicationPackage = permanentApplicationPackage;
this.hostRegistries = hostRegistries;
- this.fileDistributionProvider = fileDistributionProvider;
+ this.fileDistributionFactory = fileDistributionFactory;
this.modelFactoryRegistry = modelFactoryRegistry;
this.hostProvisioner = hostProvisioner;
this.sessionPreparer = sessionPreparer;
@@ -247,6 +247,6 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
return secretStore;
}
- public FileDistributionFactory getFileDistributionProvider() { return fileDistributionProvider; }
+ public FileDistributionFactory getFileDistributionFactory() { return fileDistributionFactory; }
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def
index bf99c65ec14..e2f22a38fc3 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def
@@ -29,7 +29,7 @@ config[].role string
config[].id reference
## Wether the NC should start the corresponding role using the
-## slavewrapper utility application or not.
+## wrapper utility application or not.
config[].usewrapper bool default=false
routingtable[].hop[].name string
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
index 8394611737e..a4fce5e37ba 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
@@ -53,7 +53,7 @@ public class ZooKeeperClientTest {
@Before
public void setupZK() throws IOException {
zk = ConfigCurator.create(new MockCurator());
- ZooKeeperClient zkc = new ZooKeeperClient(zk, new BaseDeployLogger(), true, Path.fromString(appPath));
+ ZooKeeperClient zkc = new ZooKeeperClient(zk, new BaseDeployLogger(), Path.fromString(appPath));
ApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(new File("src/test/apps/zkfeed"),
new DeployData("foo",
"/bar/baz",
@@ -85,7 +85,7 @@ public class ZooKeeperClientTest {
ConfigCurator zk = ConfigCurator.create(new MockCurator());
BaseDeployLogger logger = new BaseDeployLogger();
long generation = 1L;
- ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, Path.fromString("/1"));
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, Path.fromString("/1"));
zooKeeperClient.setupZooKeeper();
String appPath = "/";
assertThat(zk.getChildren(appPath).size(), is(1));
@@ -120,7 +120,7 @@ public class ZooKeeperClientTest {
ConfigCurator zk = ConfigCurator.create(new MockCurator());
BaseDeployLogger logger = new BaseDeployLogger();
Path app = Path.fromString("/1");
- ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, app);
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, app);
zooKeeperClient.setupZooKeeper();
String currentAppPath = app.getAbsolute();
@@ -191,7 +191,7 @@ public class ZooKeeperClientTest {
ConfigCurator zk = ConfigCurator.create(new MockCurator());
BaseDeployLogger logger = new BaseDeployLogger();
Path app = Path.fromString("/1");
- ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, app);
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, app);
zooKeeperClient.setupZooKeeper();
HostSpec host1 = new HostSpec("host1.yahoo.com", Collections.emptyList(), Optional.empty());
HostSpec host2 = new HostSpec("host2.yahoo.com", Collections.emptyList(), Optional.empty());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java
index 4825ccc1328..641fbe5bf41 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java
@@ -48,7 +48,7 @@ public class ZooKeeperDeployerTest {
public void deploy(ApplicationPackage applicationPackage, ConfigCurator configCurator, Path appPath) throws IOException {
MockDeployLogger logger = new MockDeployLogger();
- ZooKeeperClient client = new ZooKeeperClient(configCurator, logger, true, appPath);
+ ZooKeeperClient client = new ZooKeeperClient(configCurator, logger, appPath);
ZooKeeperDeployer deployer = new ZooKeeperDeployer(client);
deployer.deploy(applicationPackage, Collections.singletonMap(new Version(1, 0, 0), new MockFileRegistry()), AllocatedHosts.withHosts(Collections.emptySet()));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionFactory.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionFactory.java
index 78729156b93..af55dc6a90e 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionFactory.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionFactory.java
@@ -10,16 +10,13 @@ import java.io.File;
*/
public class MockFileDistributionFactory extends FileDistributionFactory {
- public final MockFileDistributionProvider mockFileDistributionProvider;
-
public MockFileDistributionFactory(ConfigserverConfig configserverConfig) {
super(configserverConfig);
- mockFileDistributionProvider = new MockFileDistributionProvider(new File(configserverConfig.fileReferencesDir()));
}
@Override
public com.yahoo.vespa.config.server.filedistribution.FileDistributionProvider createProvider(File applicationFile) {
- return mockFileDistributionProvider;
+ return new MockFileDistributionProvider(applicationFile, new File(configserverConfig.fileReferencesDir()));
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java
new file mode 100644
index 00000000000..45e00e2ece8
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileDistributionProvider.java
@@ -0,0 +1,22 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.filedistribution;
+
+import com.yahoo.config.model.api.FileDistribution;
+
+import java.io.File;
+
+/**
+ * @author hmusum
+ */
+public class MockFileDistributionProvider extends FileDistributionProvider {
+
+ public MockFileDistributionProvider(File applicationDir, File fileReferencesDir) {
+ super(new MockFileRegistry(applicationDir, fileReferencesDir.toPath()),
+ new MockFileDistribution(fileReferencesDir));
+ }
+
+ public FileDistribution getFileDistribution() {
+ return super.getFileDistribution();
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileRegistry.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileRegistry.java
new file mode 100644
index 00000000000..343e0c50520
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/MockFileRegistry.java
@@ -0,0 +1,49 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.filedistribution;
+
+import com.yahoo.config.FileReference;
+import com.yahoo.config.application.api.FileRegistry;
+import com.yahoo.net.HostName;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A file registry for config server tests
+ *
+ * @author hmusum
+ */
+public class MockFileRegistry implements FileRegistry {
+
+ private final List<Entry> entries = new ArrayList<>();
+ private final AddFileInterface addFileInterface;
+
+ public MockFileRegistry(File applicationDir, Path rootPath) {
+ FileDirectory fileDirectory = new FileDirectory(rootPath.toFile());
+ this.addFileInterface = new ApplicationFileManager(applicationDir, fileDirectory);
+ }
+
+ public FileReference addFile(String relativePath) {
+ if (relativePath.isEmpty())
+ relativePath = "./";
+ addFileInterface.addFile(relativePath);
+
+ FileReference fileReference = new FileReference(relativePath);
+ entries.add(new Entry(relativePath, fileReference));
+ return fileReference;
+ }
+
+ @Override
+ public String fileSourceHost() { return HostName.getLocalhost(); }
+
+ public List<Entry> export() { return entries; }
+
+ @Override
+ public FileReference addUri(String uri) {
+ throw new IllegalArgumentException("FileReference addUri(String uri) is not implemented for " + getClass().getCanonicalName());
+ }
+
+}
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 eca32cd364a..1a558f89284 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
@@ -353,7 +353,9 @@ public class ApplicationHandlerTest {
HttpResponse response = createApplicationHandler().handle(HttpRequest.createTestRequest(url, GET));
assertEquals(200, response.getStatus());
String renderedString = SessionHandlerTest.getRenderedString(response);
- assertEquals("{\"generation\":" + expectedGeneration + ",\"modelVersions\":[\"" + expectedVersion.toFullString() + "\"]}", renderedString);
+ assertEquals("{\"generation\":" + expectedGeneration +
+ ",\"applicationPackageFileReference\":\"\"" +
+ ",\"modelVersions\":[\"" + expectedVersion.toFullString() + "\"]}", renderedString);
}
private void assertApplicationExists(ApplicationId applicationId, Zone zone) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java
index c1377ae439b..a2ef6aeb578 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java
@@ -62,7 +62,7 @@ public class LocalSessionTest {
.configServerDBDir(temporaryFolder.newFolder().getAbsolutePath())
.build())
.build();
- tenantRepository = new TenantRepository(componentRegistry, false);
+ tenantRepository = new TenantRepository(componentRegistry);
tenantRepository.addTenant(tenantName);
configCurator = ConfigCurator.create(curator);
}
@@ -119,19 +119,21 @@ public class LocalSessionTest {
Optional<AllocatedHosts> allocatedHosts) throws Exception {
SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenant, sessionId, allocatedHosts);
zkc.createWriteStatusTransaction(Session.Status.NEW).commit();
- ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false,
+ ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(),
TenantRepository.getSessionsPath(tenant).append(String.valueOf(sessionId)));
if (allocatedHosts.isPresent()) {
zkClient.write(allocatedHosts.get());
}
zkClient.write(Collections.singletonMap(new Version(0, 0, 0), new MockFileRegistry()));
TenantApplications applications = tenantRepository.getTenant(tenantName).getApplicationRepo();
- applications.createApplication(zkc.readApplicationId());
- return new LocalSession(tenant, sessionId, FilesApplicationPackage.fromFile(testApp), zkc, applications);
+ applications.createApplication(applicationId());
+ LocalSession session = new LocalSession(tenant, sessionId, FilesApplicationPackage.fromFile(testApp), zkc, applications);
+ session.setApplicationId(applicationId());
+ return session;
}
private void doPrepare(LocalSession session) {
- doPrepare(session, new PrepareParams.Builder().build());
+ doPrepare(session, new PrepareParams.Builder().applicationId(applicationId()).build());
}
private void doPrepare(LocalSession session, PrepareParams params) {
@@ -140,8 +142,11 @@ public class LocalSessionTest {
}
private DeployHandlerLogger getLogger() {
- return new DeployHandlerLogger(new Slime().get(), false,
- new ApplicationId.Builder().tenant(tenantName).applicationName("testapp").build());
+ return new DeployHandlerLogger(new Slime().get(), false, applicationId());
+ }
+
+ private ApplicationId applicationId() {
+ return new ApplicationId.Builder().tenant(tenantName).applicationName("testapp").build();
}
}
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 46b8754ebe0..7d1554c3e19 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
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.session;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.api.ContainerEndpoint;
@@ -33,7 +34,6 @@ import com.yahoo.vespa.config.server.TestComponentRegistry;
import com.yahoo.vespa.config.server.TimeoutBudgetTest;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger;
-import com.yahoo.vespa.config.server.filedistribution.MockFileDistributionFactory;
import com.yahoo.vespa.config.server.host.HostRegistry;
import com.yahoo.vespa.config.server.http.InvalidApplicationException;
import com.yahoo.vespa.config.server.model.TestModelFactory;
@@ -59,7 +59,6 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -68,6 +67,7 @@ import java.util.Set;
import java.util.logging.Level;
import static com.yahoo.vespa.config.server.session.SessionZooKeeperClient.APPLICATION_PACKAGE_REFERENCE_PATH;
+import static com.yahoo.vespa.config.server.session.SessionPreparer.PrepareResult;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -85,27 +85,31 @@ public class SessionPreparerTest {
private static final File invalidTestApp = new File("src/test/apps/illegalApp");
private static final Version version123 = new Version(1, 2, 3);
private static final Version version321 = new Version(3, 2, 1);
- private KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
- private X509Certificate certificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=subject"),
+ private final KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ private final X509Certificate certificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=subject"),
Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(12345)).build();
-
private final InMemoryFlagSource flagSource = new InMemoryFlagSource();
private MockCurator curator;
private ConfigCurator configCurator;
private SessionPreparer preparer;
private TestComponentRegistry componentRegistry;
- private MockFileDistributionFactory fileDistributionFactory;
- private MockSecretStore secretStore = new MockSecretStore();
+ private final MockSecretStore secretStore = new MockSecretStore();
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Before
- public void setUp() {
+ public void setUp() throws IOException {
curator = new MockCurator();
configCurator = ConfigCurator.create(curator);
- componentRegistry = new TestComponentRegistry.Builder().curator(curator).build();
- fileDistributionFactory = (MockFileDistributionFactory)componentRegistry.getFileDistributionProvider();
+ componentRegistry = new TestComponentRegistry.Builder()
+ .curator(curator)
+ .configServerConfig(new ConfigserverConfig.Builder()
+ .fileReferencesDir(folder.newFolder().getAbsolutePath())
+ .configServerDBDir(folder.newFolder().getAbsolutePath())
+ .configDefinitionsDir(folder.newFolder().getAbsolutePath())
+ .build())
+ .build();
preparer = createPreparer();
}
@@ -115,7 +119,7 @@ public class SessionPreparerTest {
private SessionPreparer createPreparer(HostProvisionerProvider hostProvisionerProvider) {
ModelFactoryRegistry modelFactoryRegistry =
- new ModelFactoryRegistry(Arrays.asList(new TestModelFactory(version123), new TestModelFactory(version321)));
+ new ModelFactoryRegistry(List.of(new TestModelFactory(version123), new TestModelFactory(version321)));
return createPreparer(modelFactoryRegistry, hostProvisionerProvider);
}
@@ -123,7 +127,7 @@ public class SessionPreparerTest {
HostProvisionerProvider hostProvisionerProvider) {
return new SessionPreparer(
modelFactoryRegistry,
- componentRegistry.getFileDistributionProvider(),
+ componentRegistry.getFileDistributionFactory(),
hostProvisionerProvider,
new PermanentApplicationPackage(componentRegistry.getConfigserverConfig()),
componentRegistry.getConfigserverConfig(),
@@ -152,14 +156,13 @@ public class SessionPreparerTest {
@Test
public void require_that_filedistribution_is_ignored_on_dryrun() throws IOException {
- prepare(testApp, new PrepareParams.Builder().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()).build());
- assertThat(fileDistributionFactory.mockFileDistributionProvider.timesCalled, is(0));
+ PrepareResult result = prepare(testApp, new PrepareParams.Builder().dryRun(true).build());
+ assertTrue(result.getFileRegistries().get(version321).export().isEmpty());
}
@Test
public void require_that_application_is_prepared() throws Exception {
prepare(testApp);
- assertThat(fileDistributionFactory.mockFileDistributionProvider.timesCalled, is(1)); // Only builds the newest version
assertTrue(configCurator.exists(sessionsPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute()));
}
@@ -327,11 +330,11 @@ public class SessionPreparerTest {
prepare(app, new PrepareParams.Builder().build());
}
- private void prepare(File app, PrepareParams params) throws IOException {
+ private PrepareResult prepare(File app, PrepareParams params) throws IOException {
FilesApplicationPackage applicationPackage = getApplicationPackage(app);
- preparer.prepare(new HostRegistry<>(), getLogger(), params,
- Optional.empty(), tenantPath, Instant.now(), applicationPackage.getAppDir(),
- applicationPackage, new SessionZooKeeperClient(curator, sessionsPath));
+ return preparer.prepare(new HostRegistry<>(), getLogger(), params,
+ Optional.empty(), tenantPath, Instant.now(), applicationPackage.getAppDir(),
+ applicationPackage, new SessionZooKeeperClient(curator, sessionsPath));
}
private FilesApplicationPackage getApplicationPackage(File testFile) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
index 5ae5910d827..7f38083797e 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
@@ -3,17 +3,18 @@ package com.yahoo.vespa.config.server.session;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.path.Path;
import com.yahoo.vespa.config.server.application.ApplicationSet;
-import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
import com.yahoo.vespa.config.server.host.HostValidator;
import com.yahoo.vespa.curator.mock.MockCurator;
import java.io.File;
import java.time.Instant;
-import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
+import java.util.Set;
/**
* @author Ulf Lilleengen
@@ -28,12 +29,12 @@ public class SessionTest {
}
@Override
- public ConfigChangeActions prepare(HostValidator<ApplicationId> hostValidator, DeployLogger logger, PrepareParams params,
- Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath,
- Instant now, File serverDbSessionDir, ApplicationPackage applicationPackage,
- SessionZooKeeperClient sessionZooKeeperClient) {
+ public PrepareResult prepare(HostValidator<ApplicationId> hostValidator, DeployLogger logger, PrepareParams params,
+ Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath,
+ Instant now, File serverDbSessionDir, ApplicationPackage applicationPackage,
+ SessionZooKeeperClient sessionZooKeeperClient) {
isPrepared = true;
- return new ConfigChangeActions(new ArrayList<>());
+ return new PrepareResult(AllocatedHosts.withHosts(Set.of()), List.of());
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
index 5633ec2c5f8..1d7df7acfd0 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
@@ -85,11 +85,6 @@ public class SessionZooKeeperClientTest {
}
@Test
- public void require_that_default_name_is_returned_if_node_does_not_exist() {
- assertThat(createSessionZKClient("3").readApplicationId().application().value(), is("default"));
- }
-
- @Test
public void require_that_create_time_can_be_written_and_read() {
SessionZooKeeperClient zkc = createSessionZKClient("3");
curator.delete(Path.fromString("3"));
diff --git a/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java b/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java
deleted file mode 100644
index ee12c7d4c9f..00000000000
--- a/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core;
-
-/**
- * @author gjoranv
- */
-public interface BundleLoaderProperties {
-
- // TODO: This should be removed. The prefix is used to separate the bundles in BundlesConfig
- // into those that are transferred with filedistribution and those that are preinstalled
- // on disk. Instead, the model should have put them in two different configs. I.e. create a new
- // config 'preinstalled-bundles.def'.
- String DISK_BUNDLE_PREFIX = "file:";
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java
new file mode 100644
index 00000000000..f87dd3f42d2
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java
@@ -0,0 +1,132 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.core.config;
+
+import com.yahoo.config.FileReference;
+import com.yahoo.osgi.Osgi;
+import org.osgi.framework.Bundle;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Manages the set of installed and active/inactive bundles.
+ *
+ * @author gjoranv
+ * @author Tony Vaagenes
+ */
+public class ApplicationBundleLoader {
+ private static final Logger log = Logger.getLogger(ApplicationBundleLoader.class.getName());
+
+ /* Map of file refs of active bundles (not scheduled for uninstall) to the installed bundle.
+ *
+ * Used to:
+ * 1. Avoid installing already installed bundles. Just an optimization, installing the same bundle location is a NOP
+ * 2. Start bundles (all are started every time)
+ * 3. Calculate the set of bundles to uninstall
+ */
+ private final Map<FileReference, Bundle> reference2Bundle = new LinkedHashMap<>();
+
+ private final Osgi osgi;
+ private final FileAcquirerBundleInstaller bundleInstaller;
+
+ public ApplicationBundleLoader(Osgi osgi, FileAcquirerBundleInstaller bundleInstaller) {
+ this.osgi = osgi;
+ this.bundleInstaller = bundleInstaller;
+ }
+
+ /**
+ * Installs the given set of bundles and returns the set of bundles that is no longer used
+ * by the application, and should therefore be scheduled for uninstall.
+ */
+ public synchronized Set<Bundle> useBundles(List<FileReference> newFileReferences) {
+
+ Set<FileReference> obsoleteReferences = getObsoleteFileReferences(newFileReferences);
+ Set<Bundle> bundlesToUninstall = getObsoleteBundles(obsoleteReferences);
+ log.info("Bundles to schedule for uninstall: " + bundlesToUninstall);
+
+ osgi.allowDuplicateBundles(bundlesToUninstall);
+ removeInactiveFileReferences(obsoleteReferences);
+
+ installBundles(newFileReferences);
+ BundleStarter.startBundles(reference2Bundle.values());
+ log.info(installedBundlesMessage());
+
+ return bundlesToUninstall;
+ }
+
+ private Set<FileReference> getObsoleteFileReferences(List<FileReference> newReferences) {
+ Set<FileReference> obsoleteReferences = new HashSet<>(reference2Bundle.keySet());
+ obsoleteReferences.removeAll(newReferences);
+ return obsoleteReferences;
+ }
+
+ /**
+ * Returns the bundles that will not be retained by the new application generation.
+ */
+ private Set<Bundle> getObsoleteBundles(Set<FileReference> obsoleteReferences) {
+ return obsoleteReferences.stream().map(reference2Bundle::get).collect(Collectors.toSet());
+ }
+
+ private void removeInactiveFileReferences(Set<FileReference> fileReferencesToRemove) {
+ fileReferencesToRemove.forEach(reference2Bundle::remove);
+ }
+
+ private void installBundles(List<FileReference> references) {
+ Set<FileReference> bundlesToInstall = new HashSet<>(references);
+
+ // This is just an optimization, as installing a bundle with the same location id returns the already installed bundle.
+ bundlesToInstall.removeAll(reference2Bundle.keySet());
+
+ if (!bundlesToInstall.isEmpty()) {
+ if (bundleInstaller.hasFileDistribution()) {
+ installWithFileDistribution(bundlesToInstall, bundleInstaller);
+ } else {
+ log.warning("Can't retrieve bundles since file distribution is disabled.");
+ }
+ }
+ }
+
+ private void installWithFileDistribution(Set<FileReference> bundlesToInstall,
+ FileAcquirerBundleInstaller bundleInstaller) {
+ for (FileReference reference : bundlesToInstall) {
+ try {
+ log.info("Installing bundle with reference '" + reference.value() + "'");
+ List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
+
+ // If more than one bundle was installed, and the OSGi framework is the real Felix one,
+ // it means that the X-JDisc-Preinstall-Bundle header was used.
+ // However, test osgi frameworks may return multiple bundles when installing a single bundle.
+ if (bundles.size() > 1 && osgi.hasFelixFramework()) {
+ // TODO: remove if-statement below when the last model with preinstall has rolled out of hosted
+ if (! bundles.get(0).getSymbolicName().equals("config-model-fat-amended"))
+ throw new RuntimeException("Bundle '" + bundles.get(0).getSymbolicName() + "' tried to pre-install bundles from disk.");
+ }
+ reference2Bundle.put(reference, bundles.get(0));
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle with reference '" + reference + "'", e);
+ }
+ }
+ }
+
+ private String installedBundlesMessage() {
+ StringBuilder sb = new StringBuilder("Installed bundles: {" );
+ for (Bundle b : osgi.getBundles())
+ sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
+ sb.setLength(sb.length() - 2);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ // Only for testing
+ List<FileReference> getActiveFileReferences() {
+ return new ArrayList<>(reference2Bundle.keySet());
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleInstaller.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleInstaller.java
deleted file mode 100644
index fc919571b6c..00000000000
--- a/container-core/src/main/java/com/yahoo/container/core/config/BundleInstaller.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core.config;
-
-import com.yahoo.config.FileReference;
-import com.yahoo.osgi.Osgi;
-import org.osgi.framework.Bundle;
-
-import java.util.List;
-
-/**
- * @author gjoranv
- */
-public interface BundleInstaller {
-
- /**
- * Installs the bundle with the given file reference, plus all bundles in its X-JDisc-Preinstall-Bundle directive.
- * Returns all bundles installed to the given OSGi framework as a result of this call.
- */
- List<Bundle> installBundles(FileReference reference, Osgi osgi) throws InterruptedException;
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java
deleted file mode 100644
index 5e9b42a5fda..00000000000
--- a/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core.config;
-
-import com.yahoo.collections.PredicateSplit;
-import com.yahoo.config.FileReference;
-import com.yahoo.container.Container;
-import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
-import com.yahoo.osgi.Osgi;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleRevision;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-import static com.yahoo.collections.PredicateSplit.partition;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
-/**
- * Manages the set of installed and active/inactive bundles.
- *
- * @author gjoranv
- * @author Tony Vaagenes
- */
-public class BundleManager {
-
- /* Map of file refs of active bundles (not scheduled for uninstall) to a list of all bundles that were installed
- * (pre-install directive) by the bundle pointed to by the file ref (including itself).
- *
- * Used to:
- * 1. Avoid installing already installed bundles. Just an optimization, installing the same bundle location is a NOP
- * 2. Start bundles (all are started every time)
- * 3. Calculate the set of bundles to uninstall
- */
- private final Map<FileReference, List<Bundle>> reference2Bundles = new LinkedHashMap<>();
-
- private final Logger log = Logger.getLogger(BundleManager.class.getName());
- private final Osgi osgi;
-
- // A custom bundle installer for non-disk bundles, to be used for testing
- private BundleInstaller customBundleInstaller = null;
-
- public BundleManager(Osgi osgi) {
- this.osgi = osgi;
- }
-
- /**
- * Installs the given set of bundles and returns the set of bundles that is no longer used
- * by the application, and should therefore be scheduled for uninstall.
- */
- public synchronized Set<Bundle> use(List<FileReference> newFileReferences) {
- // Must be done before allowing duplicates because allowed duplicates affect osgi.getCurrentBundles
- Set<Bundle> bundlesToUninstall = getObsoleteBundles(newFileReferences);
-
- Set<FileReference> obsoleteReferences = getObsoleteFileReferences(newFileReferences);
- allowDuplicateBundles(obsoleteReferences);
- removeInactiveFileReferences(obsoleteReferences);
-
- installBundles(newFileReferences);
- startBundles();
-
- bundlesToUninstall.removeAll(allActiveBundles());
- log.info("Bundles to schedule for uninstall: " + bundlesToUninstall);
-
- log.info(installedBundlesMessage());
- return bundlesToUninstall;
- }
-
- /**
- * Returns the bundles that are not assumed to be retained by the new application generation.
- * Note that at this point we don't yet know the full set of new bundles, because of the potential
- * pre-install directives in the new bundles. However, only "disk bundles" (file:) can be listed
- * in the pre-install directive, so we know about all the obsolete application bundles.
- */
- private Set<Bundle> getObsoleteBundles(List<FileReference> newReferences) {
- Set<Bundle> bundlesToRemove = new HashSet<>(osgi.getCurrentBundles());
-
- for (FileReference fileReferenceToKeep : newReferences) {
- if (reference2Bundles.containsKey(fileReferenceToKeep)) {
- bundlesToRemove.removeAll(reference2Bundles.get(fileReferenceToKeep));
- }
- }
- bundlesToRemove.removeAll(osgi.getInitialBundles());
- return bundlesToRemove;
- }
-
-
- private Set<FileReference> getObsoleteFileReferences(List<FileReference> newReferences) {
- Set<FileReference> obsoleteReferences = new HashSet<>(reference2Bundles.keySet());
- obsoleteReferences.removeAll(newReferences);
- return obsoleteReferences;
- }
-
- /**
- * Allow duplicates (bsn+version) for each bundle that corresponds to obsolete file references,
- * and avoid allowing duplicates for bundles that were installed via the
- * X-JDisc-Preinstall-Bundle directive. These bundles are always "disk bundles" (library
- * bundles installed on the node, and not transferred via file distribution).
- * Such bundles will never have duplicates because they always have the same location id.
- */
- private void allowDuplicateBundles(Set<FileReference> obsoleteReferences) {
- // The bundle at index 0 for each file reference always corresponds to the bundle at the file reference location
- Set<Bundle> allowedDuplicates = obsoleteReferences.stream()
- .filter(reference -> ! isDiskBundle(reference))
- .map(reference -> reference2Bundles.get(reference).get(0))
- .collect(Collectors.toSet());
-
- log.info(() -> allowedDuplicates.isEmpty() ? "" : "Adding bundles to allowed duplicates: " + allowedDuplicates);
- osgi.allowDuplicateBundles(allowedDuplicates);
- }
-
- /**
- * Cleans up the map of active file references
- */
- private void removeInactiveFileReferences(Set<FileReference> fileReferencesToRemove) {
- // Clean up the map of active bundles
- fileReferencesToRemove.forEach(reference2Bundles::remove);
- }
-
- private void installBundles(List<FileReference> references) {
- Set<FileReference> bundlesToInstall = new HashSet<>(references);
-
- // This is just an optimization, as installing a bundle with the same location id returns the already installed bundle.
- bundlesToInstall.removeAll(reference2Bundles.keySet());
-
- PredicateSplit<FileReference> bundlesToInstall_isDisk = partition(bundlesToInstall, BundleManager::isDiskBundle);
- installBundlesFromDisk(bundlesToInstall_isDisk.trueValues);
- installBundlesFromFileDistribution(bundlesToInstall_isDisk.falseValues);
-
- // TODO: Remove. Bundles are also started in use()
- startBundles();
- }
-
- private static boolean isDiskBundle(FileReference fileReference) {
- return fileReference.value().startsWith(DISK_BUNDLE_PREFIX);
- }
-
- private void installBundlesFromDisk(List<FileReference> bundlesToInstall) {
- for (FileReference reference : bundlesToInstall) {
- try {
- installBundleFromDisk(reference);
- }
- catch(Exception e) {
- throw new RuntimeException("Could not install bundle '" + reference + "'", e);
- }
- }
- }
-
- private void installBundlesFromFileDistribution(List<FileReference> bundlesToInstall) {
- if (!bundlesToInstall.isEmpty()) {
- FileAcquirer fileAcquirer = Container.get().getFileAcquirer();
- boolean hasFileDistribution = (fileAcquirer != null);
- if (hasFileDistribution) {
- installWithFileDistribution(bundlesToInstall, new FileAcquirerBundleInstaller(fileAcquirer));
- } else if (customBundleInstaller != null) {
- installWithFileDistribution(bundlesToInstall, customBundleInstaller);
- } else {
- log.warning("Can't retrieve bundles since file distribution is disabled.");
- }
- }
- }
-
- private void installBundleFromDisk(FileReference reference) {
- log.info("Installing bundle from disk with reference '" + reference.value() + "'");
-
- var bundleInstaller = new DiskBundleInstaller();
- List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
- reference2Bundles.put(reference, bundles);
- }
-
- private void installWithFileDistribution(List<FileReference> bundlesToInstall, BundleInstaller bundleInstaller) {
- for (FileReference reference : bundlesToInstall) {
- try {
- log.info("Installing bundle with reference '" + reference.value() + "'");
- List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
- reference2Bundles.put(reference, bundles);
- }
- catch(Exception e) {
- throw new RuntimeException("Could not install bundle '" + reference + "'", e);
- }
- }
- }
-
- /**
- * Resolves and starts (calls the Bundles BundleActivator) all bundles. Bundle resolution must take place
- * after all bundles are installed to ensure that the framework can resolve dependencies between bundles.
- */
- private void startBundles() {
- for (List<Bundle> bundles : reference2Bundles.values()) {
- for (Bundle bundle : bundles) {
- try {
- if ( ! isFragment(bundle))
- bundle.start(); // NOP for already ACTIVE bundles
- } catch(Exception e) {
- throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e);
- }
- }
- }
- }
-
- private boolean isFragment(Bundle bundle) {
- BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
- if (bundleRevision == null)
- throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " +
- bundle.getSymbolicName() + ":" + bundle.getVersion());
- return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
- }
-
- private Set<Bundle> allActiveBundles() {
- return reference2Bundles.keySet().stream()
- .flatMap(reference -> reference2Bundles.get(reference).stream())
- .collect(Collectors.toSet());
- }
-
- private String installedBundlesMessage() {
- StringBuilder sb = new StringBuilder("Installed bundles: {" );
- for (Bundle b : osgi.getBundles())
- sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
- sb.setLength(sb.length() - 2);
- sb.append("}");
- return sb.toString();
- }
-
- // Only for testing
- void useCustomBundleInstaller(BundleInstaller bundleInstaller) {
- customBundleInstaller = bundleInstaller;
- }
-
- // Only for testing
- List<FileReference> getActiveFileReferences() {
- return new ArrayList<>(reference2Bundles.keySet());
- }
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java
new file mode 100644
index 00000000000..4a87c27b990
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java
@@ -0,0 +1,40 @@
+package com.yahoo.container.core.config;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.util.Collection;
+
+/**
+ * Utility to start a collection of bundles.
+ *
+ * @author gjoranv
+ */
+public class BundleStarter {
+
+ private BundleStarter() { }
+
+ /**
+ * Resolves and starts (calls the Bundles BundleActivator) all bundles. Bundle resolution must take place
+ * after all bundles are installed to ensure that the framework can resolve dependencies between bundles.
+ */
+ static void startBundles(Collection<Bundle> bundles) {
+ for (var bundle : bundles) {
+ try {
+ if ( ! isFragment(bundle))
+ bundle.start(); // NOP for already ACTIVE bundles
+ } catch(Exception e) {
+ throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e);
+ }
+ }
+ }
+
+ private static boolean isFragment(Bundle bundle) {
+ BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
+ if (bundleRevision == null)
+ throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " +
+ bundle.getSymbolicName() + ":" + bundle.getVersion());
+ return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java b/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
index 3edabe9f861..a4cce2e38db 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
@@ -1,28 +1,21 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.core.config;
-import com.yahoo.config.FileReference;
import com.yahoo.osgi.Osgi;
import org.osgi.framework.Bundle;
import java.io.File;
import java.util.List;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
/**
* @author gjoranv
*/
-public class DiskBundleInstaller implements BundleInstaller {
-
- @Override
- public List<Bundle> installBundles(FileReference reference, Osgi osgi) {
- assert(reference.value().startsWith(DISK_BUNDLE_PREFIX));
- String referenceFileName = reference.value().substring(DISK_BUNDLE_PREFIX.length());
+public class DiskBundleInstaller {
- File file = new File(referenceFileName);
+ public List<Bundle> installBundles(String bundlePath, Osgi osgi) {
+ File file = new File(bundlePath);
if ( ! file.exists()) {
- throw new IllegalArgumentException("Reference '" + reference.value() + "' not found on disk.");
+ throw new IllegalArgumentException("Bundle file '" + bundlePath + "' not found on disk.");
}
return osgi.install(file.getAbsolutePath());
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/FileAcquirerBundleInstaller.java b/container-core/src/main/java/com/yahoo/container/core/config/FileAcquirerBundleInstaller.java
index 72951e67b4e..51d77462652 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/FileAcquirerBundleInstaller.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/FileAcquirerBundleInstaller.java
@@ -13,9 +13,11 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
+ * Retrieves bundles with file distribution, and installs them to the OSGi framework.
+ *
* @author gjoranv
*/
-public class FileAcquirerBundleInstaller implements BundleInstaller {
+public class FileAcquirerBundleInstaller {
private static Logger log = Logger.getLogger(FileAcquirerBundleInstaller.class.getName());
private final FileAcquirer fileAcquirer;
@@ -24,7 +26,6 @@ public class FileAcquirerBundleInstaller implements BundleInstaller {
this.fileAcquirer = fileAcquirer;
}
- @Override
public List<Bundle> installBundles(FileReference reference, Osgi osgi) throws InterruptedException {
File file = fileAcquirer.waitFor(reference, 7, TimeUnit.DAYS);
@@ -46,6 +47,10 @@ public class FileAcquirerBundleInstaller implements BundleInstaller {
return osgi.install(file.getAbsolutePath());
}
+ public boolean hasFileDistribution() {
+ return fileAcquirer != null;
+ }
+
private static boolean notReadable(File file) {
return ! Files.isReadable(file.toPath());
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
index b983cbcfe37..13d50b9b30f 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
@@ -16,6 +16,7 @@ import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.container.di.osgi.BundleClasses;
import com.yahoo.container.di.osgi.OsgiUtil;
import com.yahoo.container.logging.AccessLog;
+import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
import com.yahoo.jdisc.application.OsgiFramework;
import com.yahoo.jdisc.handler.RequestHandler;
import com.yahoo.jdisc.service.ClientProvider;
@@ -68,7 +69,6 @@ public class HandlersConfigurerDi {
}
private final com.yahoo.container.Container vespaContainer;
- private final OsgiWrapper osgiWrapper;
private final Container container;
private volatile ComponentGraph currentGraph = new ComponentGraph(0);
@@ -81,7 +81,7 @@ public class HandlersConfigurerDi {
OsgiFramework osgiFramework) {
this(subscriberFactory, vespaContainer, configId, deconstructor, discInjector,
- new ContainerAndDiOsgi(osgiFramework));
+ new ContainerAndDiOsgi(osgiFramework, vespaContainer.getFileAcquirer()));
}
// Only public for testing
@@ -93,7 +93,6 @@ public class HandlersConfigurerDi {
OsgiWrapper osgiWrapper) {
this.vespaContainer = vespaContainer;
- this.osgiWrapper = osgiWrapper;
container = new Container(subscriberFactory, configId, deconstructor, osgiWrapper);
getNewComponentGraph(discInjector, false);
}
@@ -101,12 +100,16 @@ public class HandlersConfigurerDi {
private static class ContainerAndDiOsgi extends OsgiImpl implements OsgiWrapper {
private final OsgiFramework osgiFramework;
- private final BundleManager bundleManager;
+ private final ApplicationBundleLoader applicationBundleLoader;
+ private final PlatformBundleLoader platformBundleLoader;
- public ContainerAndDiOsgi(OsgiFramework osgiFramework) {
+ public ContainerAndDiOsgi(OsgiFramework osgiFramework, FileAcquirer fileAcquirer) {
super(osgiFramework);
this.osgiFramework = osgiFramework;
- bundleManager = new BundleManager(new OsgiImpl(osgiFramework));
+
+ OsgiImpl osgi = new OsgiImpl(osgiFramework);
+ applicationBundleLoader = new ApplicationBundleLoader(osgi, new FileAcquirerBundleInstaller(fileAcquirer));
+ platformBundleLoader = new PlatformBundleLoader(osgi);
}
@@ -131,9 +134,15 @@ public class HandlersConfigurerDi {
}
@Override
- public Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ public void installPlatformBundles(Collection<String> bundlePaths) {
+ log.fine("Installing platform bundles.");
+ platformBundleLoader.useBundles(new ArrayList<>(bundlePaths));
+ }
+
+ @Override
+ public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
log.info("Installing bundles from the latest application");
- return bundleManager.use(new ArrayList<>(bundles));
+ return applicationBundleLoader.useBundles(new ArrayList<>(bundles));
}
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java
new file mode 100644
index 00000000000..0ab89e223f6
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java
@@ -0,0 +1,72 @@
+package com.yahoo.container.core.config;
+
+import com.yahoo.osgi.Osgi;
+import org.osgi.framework.Bundle;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Used to install the bundles that are added as platform bundles by the config-model.
+ *
+ * All platform bundles reside on disk, and they are never uninstalled.
+ * Platform bundles are allowed to pre-install other bundles on disk via the
+ * X-JDisc-Preinstall-Bundle manifest header.
+ *
+ * Attempts to install additional bundles, after the first call, should be a NOP.
+ *
+ * @author gjoranv
+ */
+public class PlatformBundleLoader {
+ private static final Logger log = Logger.getLogger(PlatformBundleLoader.class.getName());
+
+ private final Osgi osgi;
+ private final DiskBundleInstaller installer;
+
+ private Set<Bundle> installedBundles;
+ private boolean hasLoadedBundles = false;
+
+ public PlatformBundleLoader(Osgi osgi) {
+ this(osgi, new DiskBundleInstaller());
+ }
+
+ PlatformBundleLoader(Osgi osgi, DiskBundleInstaller installer) {
+ this.osgi = osgi;
+ this.installer = installer;
+ }
+
+ public void useBundles(List<String> bundlePaths) {
+ if (hasLoadedBundles) {
+ log.fine(() -> "Platform bundles have already been installed." +
+ "\nInstalled bundles: " + installedBundles +
+ "\nGiven files: " + bundlePaths);
+ return;
+ }
+ installedBundles = install(bundlePaths);
+ BundleStarter.startBundles(installedBundles);
+ hasLoadedBundles = true;
+ }
+
+ private Set<Bundle> install(List<String> bundlesToInstall) {
+ var allInstalled = new LinkedHashSet<Bundle>();
+ for (String bundlePath : bundlesToInstall) {
+ try {
+ allInstalled.addAll(installBundleFromDisk(bundlePath));
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle '" + bundlePath + "'", e);
+ }
+ }
+ return allInstalled;
+ }
+
+ private List<Bundle> installBundleFromDisk(String bundlePath) {
+ log.info("Installing bundle from disk: " + bundlePath);
+ List<Bundle> bundles = installer.installBundles(bundlePath, osgi);
+ log.fine("Installed " + bundles.size() + " bundles for file " + bundlePath);
+ return bundles;
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
index 9f49b016b68..503bf2f2db1 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
@@ -41,7 +41,8 @@ public class HandlersConfigurerTestWrapper {
private final static String testFiles[] = {
"components.cfg",
"handlers.cfg",
- "bundles.cfg",
+ "platform-bundles.cfg",
+ "application-bundles.cfg",
"string.cfg",
"int.cfg",
"renderers.cfg",
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
index ac0fbd71671..98c927b8efd 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
@@ -16,11 +16,6 @@ import static java.util.Collections.emptyList;
public class MockOsgiWrapper implements OsgiWrapper {
@Override
- public List<Bundle> getInitialBundles() {
- return emptyList();
- }
-
- @Override
public Bundle[] getBundles() {
return new Bundle[0];
}
diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java
index f3149ed4998..b2a156862eb 100644
--- a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java
+++ b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java
@@ -35,11 +35,13 @@ public class LogHandler extends ThreadedHttpRequestHandler {
Instant to = Optional.ofNullable(request.getProperty("to"))
.map(Long::valueOf).map(Instant::ofEpochMilli).orElse(Instant.MAX);
+ Optional<String> hostname = Optional.ofNullable(request.getProperty("hostname"));
+
return new HttpResponse(200) {
@Override
public void render(OutputStream outputStream) {
try {
- logReader.writeLogs(outputStream, from, to);
+ logReader.writeLogs(outputStream, from, to, hostname);
}
catch (Throwable t) {
log.log(Level.WARNING, "Failed reading logs from " + from + " to " + to, t);
diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
index 3cf849a6835..8e4b9aea9b8 100644
--- a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
+++ b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.Optional;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,7 +58,7 @@ class LogReader {
this.logFilePattern = logFilePattern;
}
- void writeLogs(OutputStream out, Instant from, Instant to) {
+ void writeLogs(OutputStream out, Instant from, Instant to, Optional<String> hostname) {
double fromSeconds = from.getEpochSecond() + from.getNano() / 1e9;
double toSeconds = to.getEpochSecond() + to.getNano() / 1e9;
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
@@ -67,7 +68,7 @@ class LogReader {
try {
// Logs in each sub-list contain entries covering the same time interval, so do a merge sort while reading
for (Path log : logs)
- logLineIterators.add(new LogLineIterator(log, fromSeconds, toSeconds));
+ logLineIterators.add(new LogLineIterator(log, fromSeconds, toSeconds, hostname));
Iterator<LineWithTimestamp> lines = Iterators.mergeSorted(logLineIterators,
Comparator.comparingDouble(LineWithTimestamp::timestamp));
@@ -96,14 +97,16 @@ class LogReader {
private final BufferedReader reader;
private final double from;
private final double to;
+ private final Optional<String> hostname;
private LineWithTimestamp next;
- private LogLineIterator(Path log, double from, double to) throws IOException {
+ private LogLineIterator(Path log, double from, double to, Optional<String> hostname) throws IOException {
boolean zipped = log.toString().endsWith(".gz");
InputStream in = Files.newInputStream(log);
this.reader = new BufferedReader(new InputStreamReader(zipped ? new GZIPInputStream(in) : in, UTF_8));
this.from = from;
this.to = to;
+ this.hostname = hostname;
this.next = readNext();
}
@@ -131,6 +134,9 @@ class LogReader {
if (parts.length != 7)
continue;
+ if (hostname.map(host -> !host.equals(parts[1])).orElse(false))
+ continue;
+
double timestamp = Double.parseDouble(parts[0]);
if (timestamp > to)
return null;
diff --git a/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java b/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
index d809c493565..6a700a65a03 100644
--- a/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
+++ b/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
@@ -12,15 +12,11 @@ import java.util.List;
/**
* @author Tony Vaagenes
+ * @author gjoranv
*/
public class MockOsgi extends NonWorkingOsgiFramework implements Osgi {
@Override
- public List<Bundle> getInitialBundles() {
- return Collections.emptyList();
- }
-
- @Override
public Bundle[] getBundles() {
return new Bundle[0];
}
diff --git a/container-core/src/main/java/com/yahoo/osgi/Osgi.java b/container-core/src/main/java/com/yahoo/osgi/Osgi.java
index 8f0acf41f30..513e7883594 100644
--- a/container-core/src/main/java/com/yahoo/osgi/Osgi.java
+++ b/container-core/src/main/java/com/yahoo/osgi/Osgi.java
@@ -9,11 +9,10 @@ import java.util.List;
/**
* @author Tony Vaagenes
+ * @author gjoranv
*/
public interface Osgi {
- List<Bundle> getInitialBundles();
-
Bundle[] getBundles();
/** Returns all bundles that have not been scheduled for uninstall. */
@@ -25,4 +24,7 @@ public interface Osgi {
void allowDuplicateBundles(Collection<Bundle> bundles);
+ default boolean hasFelixFramework() {
+ return false;
+ }
}
diff --git a/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java b/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
index ed93d15c975..b34442d50a9 100644
--- a/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
+++ b/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
@@ -16,6 +16,7 @@ import java.util.logging.Logger;
/**
* @author Tony Vaagenes
* @author bratseth
+ * @author gjoranv
*/
public class OsgiImpl implements Osgi {
private static final Logger log = Logger.getLogger(OsgiImpl.class.getName());
@@ -42,11 +43,6 @@ public class OsgiImpl implements Osgi {
}
@Override
- public List<Bundle> getInitialBundles() {
- return initialBundles;
- }
-
- @Override
public Bundle[] getBundles() {
List<Bundle> bundles = jdiscOsgi.bundles();
return bundles.toArray(new Bundle[bundles.size()]);
@@ -155,6 +151,11 @@ public class OsgiImpl implements Osgi {
jdiscOsgi.allowDuplicateBundles(bundles);
}
+ @Override
+ public boolean hasFelixFramework() {
+ return jdiscOsgi.isFelixFramework();
+ }
+
private static Bundle firstNonFrameworkBundle(List<Bundle> bundles) {
for (Bundle b : bundles) {
if (! (b instanceof Framework))
diff --git a/container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java b/container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java
index 414e6b05128..b56e5d99123 100644
--- a/container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java
+++ b/container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java
@@ -2,6 +2,9 @@
package com.yahoo.container.core.config;
import com.yahoo.config.FileReference;
+import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
+import com.yahoo.filedistribution.fileacquirer.MockFileAcquirer;
+import com.yahoo.osgi.Osgi;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
@@ -16,27 +19,27 @@ import static org.junit.Assert.assertTrue;
/**
* @author gjoranv
*/
-public class BundleManagerTest {
+public class ApplicationBundleLoaderTest {
private static final FileReference BUNDLE_1_REF = new FileReference("bundle-1");
private static final Bundle BUNDLE_1 = new TestBundle(BUNDLE_1_REF.value());
private static final FileReference BUNDLE_2_REF = new FileReference("bundle-2");
private static final Bundle BUNDLE_2 = new TestBundle(BUNDLE_2_REF.value());
- private BundleManager bundleLoader;
+ private ApplicationBundleLoader bundleLoader;
private TestOsgi osgi;
@Before
public void setup() {
osgi = new TestOsgi(testBundles());
- var bundleInstaller = new TestBundleInstaller();
- bundleLoader = new BundleManager(osgi);
- bundleLoader.useCustomBundleInstaller(bundleInstaller);
+ var bundleInstaller = new TestBundleInstaller(MockFileAcquirer.returnFile(null));
+
+ bundleLoader = new ApplicationBundleLoader(osgi, bundleInstaller);
}
@Test
public void bundles_are_installed_and_started() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
assertEquals(1, osgi.getInstalledBundles().size());
// The bundle is installed and started
@@ -51,8 +54,8 @@ public class BundleManagerTest {
@Test
public void new_bundle_can_be_installed_in_reconfig() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
- Set<Bundle> obsoleteBundles = bundleLoader.use(List.of(BUNDLE_1_REF, BUNDLE_2_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ Set<Bundle> obsoleteBundles = bundleLoader.useBundles(List.of(BUNDLE_1_REF, BUNDLE_2_REF));
// No bundles are obsolete
assertTrue(obsoleteBundles.isEmpty());
@@ -76,8 +79,8 @@ public class BundleManagerTest {
@Test
public void unused_bundle_is_marked_obsolete_after_reconfig() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
- Set<Bundle> obsoleteBundles = bundleLoader.use(List.of(BUNDLE_2_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ Set<Bundle> obsoleteBundles = bundleLoader.useBundles(List.of(BUNDLE_2_REF));
// The returned set of obsolete bundles contains bundle-1
assertEquals(1, obsoleteBundles.size());
@@ -103,4 +106,17 @@ public class BundleManagerTest {
BUNDLE_2_REF.value(), BUNDLE_2);
}
+ static class TestBundleInstaller extends FileAcquirerBundleInstaller {
+
+ TestBundleInstaller(FileAcquirer fileAcquirer) {
+ super(fileAcquirer);
+ }
+
+ @Override
+ public List<Bundle> installBundles(FileReference reference, Osgi osgi) {
+ return osgi.install(reference.value());
+ }
+
+ }
+
}
diff --git a/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java b/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java
new file mode 100644
index 00000000000..931b0c547fd
--- /dev/null
+++ b/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java
@@ -0,0 +1,64 @@
+package com.yahoo.container.core.config;
+
+import com.yahoo.osgi.Osgi;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ */
+public class PlatformBundleLoaderTest {
+
+ private static final String BUNDLE_1_REF = "bundle-1";
+ private static final Bundle BUNDLE_1 = new TestBundle(BUNDLE_1_REF);
+ private static final String BUNDLE_2_REF = "bundle-2";
+ private static final Bundle BUNDLE_2 = new TestBundle(BUNDLE_2_REF);
+
+ private PlatformBundleLoader bundleLoader;
+ private TestOsgi osgi;
+
+ @Before
+ public void setup() {
+ osgi = new TestOsgi(testBundles());
+ bundleLoader = new PlatformBundleLoader(osgi, new TestBundleInstaller());
+ }
+
+ @Test
+ public void bundles_are_installed_and_started() {
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ assertEquals(1, osgi.getInstalledBundles().size());
+
+ // The bundle is installed and started
+ TestBundle installedBundle = (TestBundle)osgi.getInstalledBundles().get(0);
+ assertEquals(BUNDLE_1.getSymbolicName(), installedBundle.getSymbolicName());
+ assertTrue(installedBundle.started);
+ }
+
+ @Test
+ public void bundles_cannot_be_added_by_later_calls() {
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_2_REF)); // Should be a NOP
+
+ assertEquals(1, osgi.getInstalledBundles().size());
+ assertEquals(BUNDLE_1.getSymbolicName(), osgi.getInstalledBundles().get(0).getSymbolicName());
+ }
+
+ private static Map<String, Bundle> testBundles() {
+ return Map.of(BUNDLE_1_REF, BUNDLE_1,
+ BUNDLE_2_REF, BUNDLE_2);
+ }
+
+ static class TestBundleInstaller extends DiskBundleInstaller {
+ @Override
+ public List<Bundle> installBundles(String bundlePath, Osgi osgi) {
+ return osgi.install(bundlePath);
+ }
+ }
+}
diff --git a/container-core/src/test/java/com/yahoo/container/core/config/TestBundleInstaller.java b/container-core/src/test/java/com/yahoo/container/core/config/TestBundleInstaller.java
deleted file mode 100644
index 43a5268eabf..00000000000
--- a/container-core/src/test/java/com/yahoo/container/core/config/TestBundleInstaller.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core.config;
-
-import com.yahoo.config.FileReference;
-import com.yahoo.osgi.Osgi;
-import org.osgi.framework.Bundle;
-
-import java.util.List;
-
-/**
- * @author gjoranv
- */
-class TestBundleInstaller implements BundleInstaller {
-
- @Override
- public List<Bundle> installBundles(FileReference reference, Osgi osgi) {
- return osgi.install(reference.value());
- }
-
-}
diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java
index ab0d0d54675..97aa8864eae 100644
--- a/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java
+++ b/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java
@@ -9,6 +9,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Instant;
+import java.util.Optional;
import java.util.concurrent.Executor;
import static org.junit.Assert.assertEquals;
@@ -47,7 +48,7 @@ public class LogHandlerTest {
}
@Override
- protected void writeLogs(OutputStream out, Instant from, Instant to) {
+ protected void writeLogs(OutputStream out, Instant from, Instant to, Optional<String> hostname) {
try {
if (to.isAfter(Instant.ofEpochMilli(1000))) {
out.write("newer log".getBytes());
diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
index 3f7a78e13be..ad9398a5eec 100644
--- a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
+++ b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
@@ -5,7 +5,6 @@ import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.Before;
import org.junit.Test;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -14,8 +13,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
+import java.util.Optional;
import java.util.regex.Pattern;
-import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -26,12 +25,12 @@ public class LogReaderTest {
private final FileSystem fileSystem = TestFileSystem.create();
private final Path logDirectory = fileSystem.getPath("/opt/vespa/logs");
- private static final String logv11 = "3600.2\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tfourth\n";
- private static final String logv = "90000.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tlast\n";
- private static final String log100 = "0.2\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tsecond\n";
- private static final String log101 = "0.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n";
- private static final String log110 = "3600.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tthird\n";
- private static final String log200 = "86400.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)\n";
+ private static final String logv11 = "3600.2\tnode1.com\t5480\tcontainer\tstdout\tinfo\tfourth\n";
+ private static final String logv = "90000.1\tnode1.com\t5480\tcontainer\tstdout\tinfo\tlast\n";
+ private static final String log100 = "0.2\tnode2.com\t5480\tcontainer\tstdout\tinfo\tsecond\n";
+ private static final String log101 = "0.1\tnode2.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n";
+ private static final String log110 = "3600.1\tnode1.com\t5480\tcontainer\tstderr\twarning\tthird\n";
+ private static final String log200 = "86400.1\tnode2.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)\n";
@Before
public void setup() throws IOException {
@@ -52,32 +51,41 @@ public class LogReaderTest {
}
@Test
- public void testThatLogsOutsideRangeAreExcluded() throws Exception {
+ public void testThatLogsOutsideRangeAreExcluded() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
- logReader.writeLogs(baos, Instant.ofEpochMilli(150), Instant.ofEpochMilli(3601050));
+ logReader.writeLogs(baos, Instant.ofEpochMilli(150), Instant.ofEpochMilli(3601050), Optional.empty());
assertEquals(log100 + logv11 + log110, baos.toString(UTF_8));
}
@Test
- public void testThatLogsNotMatchingRegexAreExcluded() throws Exception {
+ public void testThatLogsNotMatchingRegexAreExcluded() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*-1.*"));
- logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)));
+ logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.empty());
assertEquals(log101 + logv11, baos.toString(UTF_8));
}
@Test
- public void testZippedStreaming() throws IOException {
+ public void testZippedStreaming() {
ByteArrayOutputStream zippedBaos = new ByteArrayOutputStream();
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
- logReader.writeLogs(zippedBaos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)));
+ logReader.writeLogs(zippedBaos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.empty());
assertEquals(log101 + log100 + logv11 + log110 + log200 + logv, zippedBaos.toString(UTF_8));
}
+ @Test
+ public void logsForSingeNodeIsRetrieved() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
+ logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.of("node2.com"));
+
+ assertEquals(log101 + log100 + log200, baos.toString(UTF_8));
+ }
+
private byte[] compress(String input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream zip = new GZIPOutputStream(baos);
diff --git a/container-di/CMakeLists.txt b/container-di/CMakeLists.txt
index c2b033baa92..02b2b0d34d9 100644
--- a/container-di/CMakeLists.txt
+++ b/container-di/CMakeLists.txt
@@ -1,5 +1,7 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
install_config_definition(src/main/resources/configdefinitions/bundles.def container.bundles.def)
+install_config_definition(src/main/resources/configdefinitions/application-bundles.def com.yahoo.container.di.config.application-bundles.def)
+install_config_definition(src/main/resources/configdefinitions/platform-bundles.def com.yahoo.container.di.config.platform-bundles.def)
install_config_definition(src/main/resources/configdefinitions/components.def container.components.def)
install_config_definition(src/main/resources/configdefinitions/jersey-bundles.def container.di.config.jersey-bundles.def)
install_config_definition(src/main/resources/configdefinitions/jersey-injection.def container.di.config.jersey-injection.def)
diff --git a/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java b/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java
index 6ed9c2a2994..d8350ec2f6b 100644
--- a/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java
+++ b/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java
@@ -74,9 +74,7 @@ public final class ConfigRetriever {
return getConfigs(componentConfigKeys, leastGeneration, false);
}
- /**
- * Try to get config just once
- */
+ // TODO: duplicate code, let getConfigs call this.
Optional<ConfigSnapshot> getConfigsOnce(Set<ConfigKey<? extends ConfigInstance>> componentConfigKeys,
long leastGeneration,
boolean restartOnRedeploy) {
diff --git a/container-di/src/main/java/com/yahoo/container/di/Container.java b/container-di/src/main/java/com/yahoo/container/di/Container.java
index ef7813ce368..af580767a17 100644
--- a/container-di/src/main/java/com/yahoo/container/di/Container.java
+++ b/container-di/src/main/java/com/yahoo/container/di/Container.java
@@ -5,15 +5,17 @@ import com.google.inject.Injector;
import com.yahoo.config.ConfigInstance;
import com.yahoo.config.ConfigurationRuntimeException;
import com.yahoo.config.subscription.ConfigInterruptedException;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.di.ConfigRetriever.BootstrapConfigs;
+import com.yahoo.container.di.ConfigRetriever.ComponentsConfigs;
import com.yahoo.container.di.ConfigRetriever.ConfigSnapshot;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
import com.yahoo.container.di.componentgraph.core.ComponentNode;
import com.yahoo.container.di.componentgraph.core.JerseyNode;
import com.yahoo.container.di.componentgraph.core.Node;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.di.config.RestApiContext;
import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.vespa.config.ConfigKey;
@@ -23,6 +25,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@@ -40,12 +43,14 @@ public class Container {
private static final Logger log = Logger.getLogger(Container.class.getName());
private final SubscriberFactory subscriberFactory;
- private ConfigKey<BundlesConfig> bundlesConfigKey;
+ private ConfigKey<ApplicationBundlesConfig> applicationBundlesConfigKey;
+ private ConfigKey<PlatformBundlesConfig> platformBundlesConfigKey;
private ConfigKey<ComponentsConfig> componentsConfigKey;
private final ComponentDeconstructor componentDeconstructor;
private final Osgi osgi;
private final ConfigRetriever configurer;
+ private List<String> platformBundles; // Used to verify that platform bundles don't change.
private long previousConfigGeneration = -1L;
private long leastGeneration = -1L;
@@ -54,9 +59,10 @@ public class Container {
this.componentDeconstructor = componentDeconstructor;
this.osgi = osgi;
- bundlesConfigKey = new ConfigKey<>(BundlesConfig.class, configId);
+ applicationBundlesConfigKey = new ConfigKey<>(ApplicationBundlesConfig.class, configId);
+ platformBundlesConfigKey = new ConfigKey<>(PlatformBundlesConfig.class, configId);
componentsConfigKey = new ConfigKey<>(ComponentsConfig.class, configId);
- var bootstrapKeys = Set.of(bundlesConfigKey, componentsConfigKey);
+ var bootstrapKeys = Set.of(applicationBundlesConfigKey, platformBundlesConfigKey, componentsConfigKey);
this.configurer = new ConfigRetriever(bootstrapKeys, subscriberFactory::getSubscriber);
}
@@ -74,7 +80,6 @@ public class Container {
deconstructObsoleteComponents(oldGraph, newGraph, obsoleteBundles);
return newGraph;
} catch (Throwable t) {
- // TODO: Wrap ComponentConstructorException in an Error when generation==0 (+ unit test that Error is thrown)
invalidateGeneration(oldGraph.generation(), t);
throw t;
}
@@ -98,28 +103,26 @@ public class Container {
"Got bootstrap configs out of sequence for old config generation %d.\n" + "Previous config generation is %d",
getBootstrapGeneration(), previousConfigGeneration));
}
- log.log(FINE,
- String.format(
- "Got new bootstrap generation\n" + "bootstrap generation = %d\n" + "components generation: %d\n"
- + "previous generation: %d\n",
- getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration));
+ log.log(FINE, "Got new bootstrap generation\n" + configGenerationsString());
- Collection<Bundle> bundlesToRemove = installBundles(snapshot.configs());
+ if (graph.generation() == 0) {
+ platformBundles = getConfig(platformBundlesConfigKey, snapshot.configs()).bundlePaths();
+ osgi.installPlatformBundles(platformBundles);
+ } else {
+ throwIfPlatformBundlesChanged(snapshot);
+ }
+ Collection<Bundle> bundlesToRemove = installApplicationBundles(snapshot.configs());
obsoleteBundles.addAll(bundlesToRemove);
graph = createComponentsGraph(snapshot.configs(), getBootstrapGeneration(), fallbackInjector);
// Continues loop
- } else if (snapshot instanceof ConfigRetriever.ComponentsConfigs) {
+ } else if (snapshot instanceof ComponentsConfigs) {
break;
}
}
- log.log(FINE,
- String.format(
- "Got components configs,\n" + "bootstrap generation = %d\n" + "components generation: %d\n"
- + "previous generation: %d",
- getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration));
+ log.log(FINE, "Got components configs,\n" + configGenerationsString());
return createAndConfigureComponentsGraph(snapshot.configs(), fallbackInjector);
}
@@ -131,6 +134,17 @@ public class Container {
return configurer.getComponentsGeneration();
}
+ private String configGenerationsString() {
+ return String.format("bootstrap generation = %d\n" + "components generation: %d\n" + "previous generation: %d",
+ getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration);
+ }
+
+ private void throwIfPlatformBundlesChanged(ConfigSnapshot snapshot) {
+ var checkPlatformBundles = getConfig(platformBundlesConfigKey, snapshot.configs()).bundlePaths();
+ if (! checkPlatformBundles.equals(platformBundles))
+ throw new RuntimeException("Platform bundles are not allowed to change!\nOld: " + platformBundles + "\nNew: " + checkPlatformBundles);
+ }
+
private ComponentGraph createAndConfigureComponentsGraph(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> componentsConfigs,
Injector fallbackInjector) {
ComponentGraph componentGraph = createComponentsGraph(componentsConfigs, getComponentsGeneration(), fallbackInjector);
@@ -151,9 +165,9 @@ public class Container {
componentDeconstructor.deconstruct(oldComponents.keySet(), obsoleteBundles);
}
- private Set<Bundle> installBundles(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs) {
- BundlesConfig bundlesConfig = getConfig(bundlesConfigKey, configsIncludingBootstrapConfigs);
- return osgi.useBundles(bundlesConfig.bundle());
+ private Set<Bundle> installApplicationBundles(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs) {
+ ApplicationBundlesConfig applicationBundlesConfig = getConfig(applicationBundlesConfigKey, configsIncludingBootstrapConfigs);
+ return osgi.useApplicationBundles(applicationBundlesConfig.bundles());
}
private ComponentGraph createComponentsGraph(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs,
diff --git a/container-di/src/main/java/com/yahoo/container/di/Osgi.java b/container-di/src/main/java/com/yahoo/container/di/Osgi.java
index ab7da7665b6..940986e2f38 100644
--- a/container-di/src/main/java/com/yahoo/container/di/Osgi.java
+++ b/container-di/src/main/java/com/yahoo/container/di/Osgi.java
@@ -25,11 +25,15 @@ public interface Osgi {
return new BundleClasses(new MockBundle(), Collections.emptySet());
}
+ default void installPlatformBundles(Collection<String> bundlePaths) {
+ System.out.println("installPlatformBundles " + bundlePaths);
+ }
+
/**
* Returns the set of bundles that is not used by the current application generation,
* and therefore should be scheduled for uninstalling.
*/
- default Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ default Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
System.out.println("useBundles " + bundles.stream().map(Object::toString).collect(Collectors.joining(", ")));
return emptySet();
}
diff --git a/container-di/src/main/resources/configdefinitions/application-bundles.def b/container-di/src/main/resources/configdefinitions/application-bundles.def
new file mode 100644
index 00000000000..7e03b1e3ac8
--- /dev/null
+++ b/container-di/src/main/resources/configdefinitions/application-bundles.def
@@ -0,0 +1,5 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package=com.yahoo.container.di.config
+
+# References to user bundles to install.
+bundles[] file
diff --git a/container-di/src/main/resources/configdefinitions/bundles.def b/container-di/src/main/resources/configdefinitions/bundles.def
index 9e10d863106..79e24742398 100644
--- a/container-di/src/main/resources/configdefinitions/bundles.def
+++ b/container-di/src/main/resources/configdefinitions/bundles.def
@@ -1,5 +1,5 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=container
-# References to all 3rd-party bundles to be installed.
+# References to both application and platform bundles to install.
bundle[] file
diff --git a/container-di/src/main/resources/configdefinitions/platform-bundles.def b/container-di/src/main/resources/configdefinitions/platform-bundles.def
new file mode 100644
index 00000000000..a30a846b565
--- /dev/null
+++ b/container-di/src/main/resources/configdefinitions/platform-bundles.def
@@ -0,0 +1,5 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package=com.yahoo.container.di.config
+
+# Paths to platform bundles to install.
+bundlePaths[] string
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
index eac64c20274..19f277ff8fb 100644
--- a/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
@@ -267,7 +267,8 @@ public class ContainerTest extends ContainerTestBase {
"inject[0].forClass \"" + injectedClass.getName() + "\"\n";
dirConfigSource.writeConfig("components", componentsConfig);
- dirConfigSource.writeConfig("bundles", "");
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
dirConfigSource.writeConfig("jersey-injection", injectionConfig);
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
index 18236a6bde9..f1f3c4a2ae4 100644
--- a/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
@@ -61,7 +61,7 @@ public class ContainerTestBase {
}
@Override
- public Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
return emptySet();
}
@@ -81,7 +81,8 @@ public class ContainerTestBase {
}
protected void writeBootstrapConfigs(ComponentEntry... componentEntries) {
- dirConfigSource.writeConfig("bundles", "");
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
StringBuilder components = new StringBuilder();
for (int i = 0; i < componentEntries.length; i++) {
components.append(componentEntries[i].asConfig(i));
diff --git a/container-disc/pom.xml b/container-disc/pom.xml
index 48872d0665b..1caf66e29dc 100644
--- a/container-disc/pom.xml
+++ b/container-disc/pom.xml
@@ -181,7 +181,8 @@
<artifactId>bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
- <discApplicationClass>com.yahoo.container.jdisc.ConfiguredApplication</discApplicationClass>
+ <discApplicationClass>com.yahoo.container.jdisc.ConfiguredApplication</discApplicationClass>
+ <buildLegacyVespaPlatformBundle>true</buildLegacyVespaPlatformBundle>
<discPreInstallBundle>
<!-- Vespa bundles -->
config-bundle-jar-with-dependencies.jar,
diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json
index fcbe23aeb61..a338bb5d8e7 100644
--- a/container-search/abi-spec.json
+++ b/container-search/abi-spec.json
@@ -527,6 +527,31 @@
],
"fields": []
},
+ "com.yahoo.prelude.query.GeoLocationItem": {
+ "superClass": "com.yahoo.prelude.query.TermItem",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.prelude.Location)",
+ "public void <init>(com.yahoo.prelude.Location, java.lang.String)",
+ "public com.yahoo.prelude.Location getLocation()",
+ "public java.lang.String getRawWord()",
+ "public com.yahoo.prelude.query.Item$ItemType getItemType()",
+ "public java.lang.String getName()",
+ "public java.lang.String stringValue()",
+ "public void setValue(java.lang.String)",
+ "public int hashCode()",
+ "public boolean equals(java.lang.Object)",
+ "public java.lang.String getIndexedString()",
+ "protected void encodeThis(java.nio.ByteBuffer)",
+ "public int getNumWords()",
+ "public boolean isStemmed()",
+ "public boolean isWords()"
+ ],
+ "fields": []
+ },
"com.yahoo.prelude.query.HasIndexItem": {
"superClass": "java.lang.Object",
"interfaces": [],
@@ -697,7 +722,7 @@
"public static final enum com.yahoo.prelude.query.Item$ItemType REGEXP",
"public static final enum com.yahoo.prelude.query.Item$ItemType WORD_ALTERNATIVES",
"public static final enum com.yahoo.prelude.query.Item$ItemType NEAREST_NEIGHBOR",
- "public static final enum com.yahoo.prelude.query.Item$ItemType LOCATION_TERM",
+ "public static final enum com.yahoo.prelude.query.Item$ItemType GEO_LOCATION_TERM",
"public final int code"
]
},
@@ -6122,8 +6147,6 @@
"public"
],
"methods": [
- "public void <init>(com.yahoo.search.query.profile.compiled.DimensionalValue$Value)",
- "public void <init>(java.util.List)",
"public java.lang.Object get(java.util.Map)",
"public boolean isEmpty()",
"public java.lang.String toString()"
@@ -6145,6 +6168,8 @@
"public com.yahoo.search.query.profile.types.QueryProfileType queryProfileType()",
"public com.yahoo.search.query.profile.compiled.ValueWithSource withValue(java.lang.Object)",
"public java.util.Optional variant()",
+ "public int hashCode()",
+ "public boolean equals(java.lang.Object)",
"public java.lang.String toString()"
],
"fields": []
diff --git a/container-search/src/main/java/com/yahoo/prelude/Location.java b/container-search/src/main/java/com/yahoo/prelude/Location.java
index 908bf835e3c..3d3eed3b3df 100644
--- a/container-search/src/main/java/com/yahoo/prelude/Location.java
+++ b/container-search/src/main/java/com/yahoo/prelude/Location.java
@@ -9,7 +9,7 @@ import java.util.StringTokenizer;
/**
* Location data for a geographical query.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
* @author arnej27959
*/
public class Location {
@@ -127,7 +127,7 @@ public class Location {
throw new IllegalArgumentException("n/s location must be in range [-90,+90]");
}
if (radius_in_degrees < 0) {
- pr = 512 * 1024 * 1024;
+ pr = -1;
}
x = px;
y = py;
@@ -142,7 +142,7 @@ public class Location {
throw new IllegalArgumentException("can only set geo circle once");
}
if (radius_in_units < 0) {
- throw new IllegalArgumentException("radius must be positive");
+ radius_in_units = -1;
}
x = px;
y = py;
@@ -248,6 +248,13 @@ public class Location {
}
public String toString() {
+ return render(false);
+ }
+ public String backendString() {
+ return render(true);
+ }
+
+ private String render(boolean forBackend) {
StringBuilder ser = new StringBuilder();
if (attribute != null) {
ser.append(attribute).append(':');
@@ -271,7 +278,7 @@ public class Location {
if (dimensions == 2) {
ser.append(",").append(y);
}
- ser.append(",").append(r).
+ ser.append(",").append(forBackend ? backendRadius() : r).
append(",").append(tableId).
append(",").append(s).
append(",").append(replace);
@@ -358,11 +365,16 @@ public class Location {
/**
* Obtain circle radius (in degrees).
+ * Note that "no radius" or "infinite radius" is represented as -1.
* May only be called when isGeoCircle() returns true.
**/
public double degRadius() {
checkGeoCircle();
- return 0.000001 * r;
+ return (r < 0) ? -1.0 : (0.000001 * r);
+ }
+
+ private int backendRadius() {
+ return (r < 0) ? (512 * 1024 * 1024) : r;
}
/**
@@ -370,7 +382,7 @@ public class Location {
* For internal use.
*/
public int encode(ByteBuffer buffer) {
- byte[] loc = Utf8.toBytes(toString());
+ byte[] loc = Utf8.toBytes(backendString());
buffer.put(loc);
return loc.length;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java b/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java
new file mode 100644
index 00000000000..8202c8fb279
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java
@@ -0,0 +1,119 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.prelude.query;
+
+import com.google.common.annotations.Beta;
+import com.yahoo.prelude.Location;
+import java.nio.ByteBuffer;
+
+/**
+ * This represents a geo-location in the query tree.
+ * Used for closeness(fieldname) and distance(fieldname) rank features.
+ * @author arnej
+ */
+@Beta
+public class GeoLocationItem extends TermItem {
+
+ private Location location;
+
+ /**
+ * Construct from a Location, which must be geo circle with an attribute set.
+ **/
+ public GeoLocationItem(Location location) {
+ this(location, location.getAttribute());
+ if (! location.hasAttribute()) {
+ throw new IllegalArgumentException("missing attribute on location: "+location);
+ }
+ }
+
+ /**
+ * Construct from a Location and a field name.
+ * The Location must be a geo circle.
+ * If the Location has an attribute set, it must match the field name.
+ **/
+ public GeoLocationItem(Location location, String fieldName) {
+ super(fieldName, false);
+ if (location.hasAttribute() && ! location.getAttribute().equals(fieldName)) {
+ throw new IllegalArgumentException("inconsistent attribute on location: "+location.getAttribute()+" versus fieldName: "+fieldName);
+ }
+ if (! location.isGeoCircle()) {
+ throw new IllegalArgumentException("GeoLocationItem only supports Geo Circles, got: "+location);
+ }
+ if (location.hasBoundingBox()) {
+ throw new IllegalArgumentException("GeoLocationItem does not support bounding box yet, got: "+location);
+ }
+ this.location = new Location(location.toString());
+ this.location.setAttribute(null); // keep this in (superclass) indexName only
+ setNormalizable(false);
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ @Override
+ public String getRawWord() {
+ return stringValue();
+ }
+
+ @Override
+ public ItemType getItemType() {
+ return ItemType.GEO_LOCATION_TERM;
+ }
+
+ @Override
+ public String getName() {
+ return "GEO_LOCATION";
+ }
+
+ @Override
+ public String stringValue() {
+ return location.toString();
+ }
+
+ @Override
+ public void setValue(String value) {
+ throw new UnsupportedOperationException("Cannot setValue("+value+") on "+getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(super.hashCode(), location);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if ( ! super.equals(object)) return false;
+ GeoLocationItem other = (GeoLocationItem) object; // Ensured by superclass
+ if ( ! location.equals(other.location)) return false;
+ return true;
+ }
+
+ @Override
+ public String getIndexedString() {
+ return location.toString();
+ }
+
+ @Override
+ protected void encodeThis(ByteBuffer buffer) {
+ super.encodeThis(buffer); // takes care of index bytes
+ // TODO: use a better format for encoding the location on the wire.
+ putString(location.backendString(), buffer);
+ }
+
+ @Override
+ public int getNumWords() {
+ return 1;
+ }
+
+ @Override
+ public boolean isStemmed() {
+ return true;
+ }
+
+ @Override
+ public boolean isWords() {
+ return false;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/Item.java b/container-search/src/main/java/com/yahoo/prelude/query/Item.java
index bd368864e9a..c4978b2a378 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/Item.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/Item.java
@@ -61,7 +61,7 @@ public abstract class Item implements Cloneable {
REGEXP(24),
WORD_ALTERNATIVES(25),
NEAREST_NEIGHBOR(26),
- LOCATION_TERM(27);
+ GEO_LOCATION_TERM(27);
public final int code;
diff --git a/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java b/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
index b4b23a0e94a..1867da0317b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
@@ -84,6 +84,7 @@ public class StatisticsSearcher extends Searcher {
private enum DegradedReason { match_phase, adaptive_timeout, timeout, non_ideal_state }
private Metric metric;
+ private Map<String, Metric.Context> chainContexts = new CopyOnWriteHashMap<>();
private Map<String, Metric.Context> statePageOnlyContexts = new CopyOnWriteHashMap<>();
private Map<String, Map<DegradedReason, Metric.Context>> degradedReasonContexts = new CopyOnWriteHashMap<>();
private Map<String, Map<String, Metric.Context>> relevanceContexts = new CopyOnWriteHashMap<>();
@@ -152,11 +153,15 @@ public class StatisticsSearcher extends Searcher {
peakQpsReporter.countQuery();
}
- private Metric.Context getChainMetricContext(String chainName, String endpoint) {
- Map<String, String> dimensions = new HashMap<>();
- dimensions.put("chain", chainName);
- dimensions.put("endpoint", endpoint);
- return this.metric.createContext(dimensions);
+ private Metric.Context getChainMetricContext(String chainName) {
+ Metric.Context context = chainContexts.get(chainName);
+ if (context == null) {
+ Map<String, String> dimensions = new HashMap<>();
+ dimensions.put("chain", chainName);
+ context = this.metric.createContext(dimensions);
+ chainContexts.put(chainName, context);
+ }
+ return context;
}
private Metric.Context getDegradedMetricContext(String chainName, Coverage coverage) {
@@ -223,7 +228,7 @@ public class StatisticsSearcher extends Searcher {
return execution.search(query);
}
- Metric.Context metricContext = getChainMetricContext(execution.chain().getId().stringValue(), query.getHttpRequest().getHeader("Host"));
+ Metric.Context metricContext = getChainMetricContext(execution.chain().getId().stringValue());
incrQueryCount(metricContext);
logQuery(query);
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
index 9b661368972..0e8759f740e 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
@@ -137,7 +137,7 @@ public class RpcFillInvoker extends FillInvoker {
root.setString("ranking", rankProfile);
}
if (location != null) {
- root.setString("location", location.toString());
+ root.setString("location", location.backendString());
}
Cursor gids = root.setArray("gids");
for (FastHit hit : hits) {
diff --git a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
index 9910eb9532d..0d9acea7643 100644
--- a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
+++ b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
@@ -3,9 +3,12 @@ package com.yahoo.search.query;
import com.google.common.base.Preconditions;
import com.yahoo.collections.LazyMap;
+import com.yahoo.geo.DistanceParser;
+import com.yahoo.geo.ParsedDegree;
import com.yahoo.language.Language;
import com.yahoo.language.process.Normalizer;
import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.Location;
import com.yahoo.prelude.query.AndItem;
import com.yahoo.prelude.query.BoolItem;
import com.yahoo.prelude.query.CompositeItem;
@@ -15,6 +18,7 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.Limit;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.NearItem;
import com.yahoo.prelude.query.NearestNeighborItem;
import com.yahoo.prelude.query.NotItem;
@@ -47,6 +51,7 @@ import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.SlimeUtils;
+import com.yahoo.slime.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -61,6 +66,65 @@ import static com.yahoo.slime.Type.LONG;
import static com.yahoo.slime.Type.OBJECT;
import static com.yahoo.slime.Type.STRING;
+import static com.yahoo.search.yql.YqlParser.ACCENT_DROP;
+import static com.yahoo.search.yql.YqlParser.ALTERNATIVES;
+import static com.yahoo.search.yql.YqlParser.AND_SEGMENTING;
+import static com.yahoo.search.yql.YqlParser.ANNOTATIONS;
+import static com.yahoo.search.yql.YqlParser.APPROXIMATE;
+import static com.yahoo.search.yql.YqlParser.ASCENDING_HITS_ORDER;
+import static com.yahoo.search.yql.YqlParser.BOUNDS;
+import static com.yahoo.search.yql.YqlParser.BOUNDS_LEFT_OPEN;
+import static com.yahoo.search.yql.YqlParser.BOUNDS_OPEN;
+import static com.yahoo.search.yql.YqlParser.BOUNDS_RIGHT_OPEN;
+import static com.yahoo.search.yql.YqlParser.CONNECTION_ID;
+import static com.yahoo.search.yql.YqlParser.CONNECTION_WEIGHT;
+import static com.yahoo.search.yql.YqlParser.CONNECTIVITY;
+import static com.yahoo.search.yql.YqlParser.DEFAULT_TARGET_NUM_HITS;
+import static com.yahoo.search.yql.YqlParser.DESCENDING_HITS_ORDER;
+import static com.yahoo.search.yql.YqlParser.DISTANCE;
+import static com.yahoo.search.yql.YqlParser.DOT_PRODUCT;
+import static com.yahoo.search.yql.YqlParser.END_ANCHOR;
+import static com.yahoo.search.yql.YqlParser.EQUIV;
+import static com.yahoo.search.yql.YqlParser.FILTER;
+import static com.yahoo.search.yql.YqlParser.GEO_LOCATION;
+import static com.yahoo.search.yql.YqlParser.HIT_LIMIT;
+import static com.yahoo.search.yql.YqlParser.HNSW_EXPLORE_ADDITIONAL_HITS;
+import static com.yahoo.search.yql.YqlParser.IMPLICIT_TRANSFORMS;
+import static com.yahoo.search.yql.YqlParser.LABEL;
+import static com.yahoo.search.yql.YqlParser.NEAR;
+import static com.yahoo.search.yql.YqlParser.NEAREST_NEIGHBOR;
+import static com.yahoo.search.yql.YqlParser.NFKC;
+import static com.yahoo.search.yql.YqlParser.NORMALIZE_CASE;
+import static com.yahoo.search.yql.YqlParser.ONEAR;
+import static com.yahoo.search.yql.YqlParser.ORIGIN;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_LENGTH;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_OFFSET;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_ORIGINAL;
+import static com.yahoo.search.yql.YqlParser.PHRASE;
+import static com.yahoo.search.yql.YqlParser.PREDICATE;
+import static com.yahoo.search.yql.YqlParser.PREFIX;
+import static com.yahoo.search.yql.YqlParser.RANGE;
+import static com.yahoo.search.yql.YqlParser.RANK;
+import static com.yahoo.search.yql.YqlParser.RANKED;
+import static com.yahoo.search.yql.YqlParser.SAME_ELEMENT;
+import static com.yahoo.search.yql.YqlParser.SCORE_THRESHOLD;
+import static com.yahoo.search.yql.YqlParser.SIGNIFICANCE;
+import static com.yahoo.search.yql.YqlParser.START_ANCHOR;
+import static com.yahoo.search.yql.YqlParser.STEM;
+import static com.yahoo.search.yql.YqlParser.SUBSTRING;
+import static com.yahoo.search.yql.YqlParser.SUFFIX;
+import static com.yahoo.search.yql.YqlParser.TARGET_HITS;
+import static com.yahoo.search.yql.YqlParser.TARGET_NUM_HITS;
+import static com.yahoo.search.yql.YqlParser.THRESHOLD_BOOST_FACTOR;
+import static com.yahoo.search.yql.YqlParser.UNIQUE_ID;
+import static com.yahoo.search.yql.YqlParser.URI;
+import static com.yahoo.search.yql.YqlParser.USE_POSITION_DATA;
+import static com.yahoo.search.yql.YqlParser.USER_INPUT_LANGUAGE;
+import static com.yahoo.search.yql.YqlParser.WAND;
+import static com.yahoo.search.yql.YqlParser.WEAK_AND;
+import static com.yahoo.search.yql.YqlParser.WEIGHT;
+import static com.yahoo.search.yql.YqlParser.WEIGHTED_SET;
+
/**
* The Select query language.
*
@@ -70,6 +134,14 @@ import static com.yahoo.slime.Type.STRING;
*/
public class SelectParser implements Parser {
+ private static final String AND = "and";
+ private static final String AND_NOT = "and_not";
+ private static final String CALL = "call";
+ private static final String CONTAINS = "contains";
+ private static final String EQ = "equals";
+ private static final String MATCHES = "matches";
+ private static final String OR = "or";
+
Parsable query;
private final IndexFacts indexFacts;
private final Map<Integer, TaggableItem> identifiedItems = LazyMap.newHashMap();
@@ -77,65 +149,7 @@ public class SelectParser implements Parser {
private final Normalizer normalizer;
private IndexFacts.Session indexFactsSession;
- // YQL parameters and functions
- private static final String DESCENDING_HITS_ORDER = "descending";
- private static final String ASCENDING_HITS_ORDER = "ascending";
- private static final Integer DEFAULT_TARGET_NUM_HITS = 10;
- private static final String ORIGIN_LENGTH = "length";
- private static final String ORIGIN_OFFSET = "offset";
- private static final String ORIGIN = "origin";
- private static final String ORIGIN_ORIGINAL = "original";
- private static final String CONNECTION_ID = "id";
- private static final String CONNECTION_WEIGHT = "weight";
- private static final String CONNECTIVITY = "connectivity";
- private static final String ANNOTATIONS = "annotations";
- private static final String NFKC = "nfkc";
- private static final String USER_INPUT_LANGUAGE = "language";
- private static final String ACCENT_DROP = "accentDrop";
- private static final String ALTERNATIVES = "alternatives";
- private static final String AND_SEGMENTING = "andSegmenting";
- private static final String APPROXIMATE = "approximate";
- private static final String DISTANCE = "distance";
- private static final String DOT_PRODUCT = "dotProduct";
- private static final String EQUIV = "equiv";
- private static final String FILTER = "filter";
- private static final String HIT_LIMIT = "hitLimit";
- private static final String HNSW_EXPLORE_ADDITIONAL_HITS = "hnsw.exploreAdditionalHits";
- private static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
- private static final String LABEL = "label";
- private static final String NEAR = "near";
- private static final String NEAREST_NEIGHBOR = "nearestNeighbor";
- private static final String NORMALIZE_CASE = "normalizeCase";
- private static final String ONEAR = "onear";
- private static final String PHRASE = "phrase";
- private static final String PREDICATE = "predicate";
- private static final String PREFIX = "prefix";
- private static final String RANKED = "ranked";
- private static final String RANK = "rank";
- private static final String SAME_ELEMENT = "sameElement";
- private static final String SCORE_THRESHOLD = "scoreThreshold";
- private static final String SIGNIFICANCE = "significance";
- private static final String STEM = "stem";
- private static final String SUBSTRING = "substring";
- private static final String SUFFIX = "suffix";
- private static final String TARGET_HITS = "targetHits";
- private static final String TARGET_NUM_HITS = "targetNumHits";
- private static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
- private static final String UNIQUE_ID = "id";
- private static final String USE_POSITION_DATA = "usePositionData";
- private static final String WAND = "wand";
- private static final String WEAK_AND = "weakAnd";
- private static final String WEIGHTED_SET = "weightedSet";
- private static final String WEIGHT = "weight";
- private static final String AND = "and";
- private static final String AND_NOT = "and_not";
- private static final String OR = "or";
- private static final String EQ = "equals";
- private static final String RANGE = "range";
- private static final String CONTAINS = "contains";
- private static final String MATCHES = "matches";
- private static final String CALL = "call";
- private static final List<String> FUNCTION_CALLS = Arrays.asList(WAND, WEIGHTED_SET, DOT_PRODUCT, NEAREST_NEIGHBOR, PREDICATE, RANK, WEAK_AND);
+ private static final List<String> FUNCTION_CALLS = Arrays.asList(WAND, WEIGHTED_SET, DOT_PRODUCT, GEO_LOCATION, NEAREST_NEIGHBOR, PREDICATE, RANK, WEAK_AND);
public SelectParser(ParserEnvironment environment) {
indexFacts = environment.getIndexFacts();
@@ -153,7 +167,7 @@ public class SelectParser implements Parser {
}
private QueryTree buildTree() {
- Inspector inspector = SlimeUtils.jsonToSlime(this.query.getSelect().getWhereString().getBytes()).get();
+ Inspector inspector = SlimeUtils.jsonToSlime(this.query.getSelect().getWhereString()).get();
if (inspector.field("error_message").valid()) {
throw new QueryException("Illegal query: " + inspector.field("error_message").asString() +
" at: '" + new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
@@ -213,7 +227,7 @@ public class SelectParser implements Parser {
/** Translates a list of grouping requests on JSON form to a list in the grouping language form */
private List<String> toGroupingRequests(String groupingJson) {
- Inspector inspector = SlimeUtils.jsonToSlime(groupingJson.getBytes()).get();
+ Inspector inspector = SlimeUtils.jsonToSlime(groupingJson).get();
if (inspector.field("error_message").valid()) {
throw new QueryException("Illegal query: " + inspector.field("error_message").asString() +
" at: '" + new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
@@ -264,6 +278,8 @@ public class SelectParser implements Parser {
return buildWeightedSet(key, value);
case DOT_PRODUCT:
return buildDotProduct(key, value);
+ case GEO_LOCATION:
+ return buildGeoLocation(key, value);
case NEAREST_NEIGHBOR:
return buildNearestNeighbor(key, value);
case PREDICATE:
@@ -410,6 +426,47 @@ public class SelectParser implements Parser {
return orItem;
}
+ private Item buildGeoLocation(String key, Inspector value) {
+ HashMap<Integer, Inspector> children = childMap(value);
+ Preconditions.checkArgument(children.size() == 4, "Expected 4 arguments, got %s.", children.size());
+ String field = children.get(0).asString();
+ var arg1 = children.get(1);
+ var arg2 = children.get(2);
+ var arg3 = children.get(3);
+ var loc = new Location();
+ if (arg3.type() != Type.STRING) {
+ throw new IllegalArgumentException("Invalid geoLocation radius type "+arg3.type()+" for "+arg3);
+ }
+ double radius = DistanceParser.parse(arg3.asString());
+ if (arg1.type() == Type.STRING && arg2.type() == Type.STRING) {
+ var c1input = children.get(1).asString();
+ var c2input = children.get(2).asString();
+ var coord_1 = ParsedDegree.fromString(c1input, true, false);
+ var coord_2 = ParsedDegree.fromString(c2input, false, true);
+ if (coord_1.isLatitude && coord_2.isLongitude) {
+ loc.setGeoCircle(coord_1.degrees, coord_2.degrees, radius);
+ } else if (coord_2.isLatitude && coord_1.isLongitude) {
+ loc.setGeoCircle(coord_2.degrees, coord_1.degrees, radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinates '"+c1input+"' and '"+c2input+"'");
+ }
+ } else if (arg1.type() == Type.DOUBLE && arg2.type() == Type.DOUBLE) {
+ loc.setGeoCircle(arg1.asDouble(), arg2.asDouble(), radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinate types "+arg1.type()+" and "+arg2.type());
+ }
+ var item = new GeoLocationItem(loc, field);
+ Inspector annotations = getAnnotations(value);
+ if (annotations != null){
+ annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
+ if (LABEL.equals(annotation_name)) {
+ item.setLabel(annotation_value.asString());
+ }
+ });
+ }
+ return item;
+ }
+
private Item buildNearestNeighbor(String key, Inspector value) {
HashMap<Integer, Inspector> children = childMap(value);
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
index 88014eef46d..27f600f9ad6 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
@@ -57,27 +57,44 @@ public class Binding implements Comparable<Binding> {
return new Binding(generality, context);
}
- private Binding(int generality, Map<String, String> binding) {
+ /** Creates a binding from a map containing the exact bindings this will have */
+ private Binding(int generality, Map<String, String> bindings) {
this.generality = generality;
// Map -> arrays to limit memory consumption and speed up evaluation
- dimensions = new String[binding.size()];
- dimensionValues = new String[binding.size()];
+ dimensions = new String[bindings.size()];
+ dimensionValues = new String[bindings.size()];
int i = 0;
- int bindingHash = 0;
- for (Map.Entry<String,String> entry : binding.entrySet()) {
+ for (Map.Entry<String,String> entry : bindings.entrySet()) {
dimensions[i] = entry.getKey();
dimensionValues[i] = entry.getValue();
- bindingHash += i * entry.getKey().hashCode() + 11 * i * entry.getValue().hashCode();
i++;
}
- this.hashCode = bindingHash;
+ this.hashCode = Arrays.hashCode(dimensions) + 11 * Arrays.hashCode(dimensionValues);
}
+ Binding(DimensionalValue.BindingSpec spec, Map<String, String> bindings) {
+ this.generality = 0; // Not used here
+
+ // Map -> arrays to limit memory consumption and speed up evaluation
+ dimensions = spec.dimensions();
+ dimensionValues = new String[spec.dimensions().length];
+ for (int i = 0; i < dimensions.length; i++) {
+ dimensionValues[i] = bindings.get(dimensions[i]);
+ }
+ this.hashCode = Arrays.hashCode(dimensions) + 11 * Arrays.hashCode(dimensionValues);
+ }
+
+
/** Returns true only if this binding is null (contains no values for its dimensions (if any) */
public boolean isNull() { return dimensions.length == 0; }
+ /** Do not change the returtned array */
+ String[] dimensions() { return dimensions; }
+
+ String[] dimensionValues() { return dimensionValues; }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder("Binding[");
@@ -116,7 +133,7 @@ public class Binding implements Comparable<Binding> {
/**
* Implements a partial ordering where more specific bindings come before less specific ones,
* taking both the number of bindings and their positions into account (earlier dimensions
- * take precedence over later ones.
+ * take precedence over later ones).
* <p>
* The order is not well defined for bindings in different dimensional spaces.
*/
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
index 0137b848dac..c1ee49913fe 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
@@ -6,6 +6,7 @@ import com.yahoo.search.query.profile.DimensionBinding;
import com.yahoo.search.query.profile.SubstituteString;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -20,20 +21,22 @@ import java.util.Set;
*/
public class DimensionalValue<VALUE> {
- private final List<Value<VALUE>> values;
+ private final Map<Binding, VALUE> indexedVariants;
+ private final List<BindingSpec> bindingSpecs;
- /** Create a set of variants which is a single value regardless of dimensions */
- public DimensionalValue(Value<VALUE> value) {
- this.values = Collections.singletonList(value);
- }
+ private DimensionalValue(List<Value<VALUE>> variants) {
+ Collections.sort(variants);
- public DimensionalValue(List<Value<VALUE>> valueVariants) {
- if (valueVariants.size() == 1) { // special cased for efficiency
- this.values = Collections.singletonList(valueVariants.get(0));
- }
- else {
- this.values = new ArrayList<>(valueVariants);
- Collections.sort(this.values);
+ // If there are inconsistent definitions of the same property, we should pick the first in the sort order
+ this.indexedVariants = new HashMap<>();
+ for (Value<VALUE> variant : variants)
+ indexedVariants.putIfAbsent(variant.binding(), variant.value());
+
+ this.bindingSpecs = new ArrayList<>();
+ for (Value<VALUE> variant : variants) {
+ BindingSpec spec = new BindingSpec(variant.binding());
+ if ( ! bindingSpecs.contains(spec))
+ bindingSpecs.add(spec);
}
}
@@ -41,18 +44,21 @@ public class DimensionalValue<VALUE> {
public VALUE get(Map<String, String> context) {
if (context == null)
context = Collections.emptyMap();
- for (Value<VALUE> value : values) {
- if (value.matches(context))
- return value.value();
+
+ for (BindingSpec spec : bindingSpecs) {
+ if ( ! spec.matches(context)) continue;
+ VALUE value = indexedVariants.get(new Binding(spec, context));
+ if (value != null)
+ return value;
}
return null;
}
- public boolean isEmpty() { return values.isEmpty(); }
+ public boolean isEmpty() { return indexedVariants.isEmpty(); }
@Override
public String toString() {
- return values.toString();
+ return indexedVariants.toString();
}
public static class Builder<VALUE> {
@@ -93,7 +99,7 @@ public class DimensionalValue<VALUE> {
/** A value for a particular binding */
private static class Value<VALUE> implements Comparable<Value> {
- private VALUE value = null;
+ private VALUE value;
/** The minimal binding this holds for */
private Binding binding;
@@ -126,9 +132,6 @@ public class DimensionalValue<VALUE> {
return " value '" + value + "' for " + binding;
}
- /**
- * A single value with the minimal set of dimension combinations it holds for.
- */
private static class Builder<VALUE> {
private final VALUE value;
@@ -212,4 +215,38 @@ public class DimensionalValue<VALUE> {
}
+ /** A list of dimensions for which there exist one or more bindings in this */
+ static class BindingSpec {
+
+ /** The dimensions of this. Unenforced invariant: Content never changes. */
+ private final String[] dimensions;
+
+ public BindingSpec(Binding binding) {
+ this.dimensions = binding.dimensions();
+ }
+
+ /** Do not change the returned array */
+ String[] dimensions() { return dimensions; }
+
+ /** Returns whether this context contains all the keys of this */
+ public boolean matches(Map<String, String> context) {
+ for (int i = 0; i < dimensions.length; i++)
+ if ( ! context.containsKey(dimensions[i])) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(dimensions);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if ( ! (other instanceof BindingSpec)) return false;
+ return Arrays.equals(((BindingSpec)other).dimensions, this.dimensions);
+ }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
index bc49e116c6e..d2c4eaaec9b 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
@@ -2,9 +2,9 @@
package com.yahoo.search.query.profile.compiled;
import com.yahoo.search.query.profile.DimensionValues;
-import com.yahoo.search.query.profile.QueryProfile;
import com.yahoo.search.query.profile.types.QueryProfileType;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -68,6 +68,24 @@ public class ValueWithSource {
public Optional<DimensionValues> variant() { return Optional.ofNullable(variant); }
@Override
+ public int hashCode() {
+ // Value is always a value object. Don't include source in identity.
+ return Objects.hash(value, isUnoverridable, type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof ValueWithSource)) return false;
+
+ ValueWithSource other = (ValueWithSource)o;
+ if ( ! Objects.equals(this.value, other.value)) return false;
+ if ( ! Objects.equals(this.isUnoverridable, other.isUnoverridable)) return false;
+ if ( ! Objects.equals(this.type, other.type)) return false;
+ return true;
+ }
+
+ @Override
public String toString() {
return value +
" (from query profile '" + source + "'" +
diff --git a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
index dd52b9e19b8..22328fb026e 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
@@ -16,6 +16,7 @@ import static com.yahoo.search.yql.YqlParser.DOT_PRODUCT;
import static com.yahoo.search.yql.YqlParser.END_ANCHOR;
import static com.yahoo.search.yql.YqlParser.EQUIV;
import static com.yahoo.search.yql.YqlParser.FILTER;
+import static com.yahoo.search.yql.YqlParser.GEO_LOCATION;
import static com.yahoo.search.yql.YqlParser.HIT_LIMIT;
import static com.yahoo.search.yql.YqlParser.IMPLICIT_TRANSFORMS;
import static com.yahoo.search.yql.YqlParser.LABEL;
@@ -72,6 +73,7 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IndexedItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.MarkerWordItem;
import com.yahoo.prelude.query.NearItem;
import com.yahoo.prelude.query.NearestNeighborItem;
@@ -689,6 +691,26 @@ public class VespaSerializer {
}
+ private static class GeoLocationSerializer extends Serializer<GeoLocationItem> {
+ @Override
+ void onExit(StringBuilder destination, GeoLocationItem item) { }
+ @Override
+ boolean serialize(StringBuilder destination, GeoLocationItem item) {
+ String annotations = leafAnnotations(item);
+ if (annotations.length() > 0) {
+ destination.append("([{").append(annotations).append("}]");
+ }
+ destination.append(GEO_LOCATION).append('(');
+ destination.append(item.getIndexName()).append(", ");
+ var loc = item.getLocation();
+ destination.append(loc.degNS()).append(", ");
+ destination.append(loc.degEW()).append(", ");
+ destination.append('"').append(loc.degRadius()).append(" deg").append('"');
+ destination.append(')');
+ return false;
+ }
+ }
+
private static class NearestNeighborSerializer extends Serializer<NearestNeighborItem> {
@Override
@@ -1163,6 +1185,7 @@ public class VespaSerializer {
dispatchBuilder.put(EquivItem.class, new EquivSerializer());
dispatchBuilder.put(ExactStringItem.class, new WordSerializer());
dispatchBuilder.put(IntItem.class, new NumberSerializer());
+ dispatchBuilder.put(GeoLocationItem.class, new GeoLocationSerializer());
dispatchBuilder.put(BoolItem.class, new BoolSerializer());
dispatchBuilder.put(MarkerWordItem.class, new WordSerializer()); // gotcha
dispatchBuilder.put(NearItem.class, new NearSerializer());
diff --git a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
index 7d17fe4f09d..6a464a1503b 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
@@ -19,11 +19,14 @@ import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.yahoo.collections.LazyMap;
import com.yahoo.collections.LazySet;
+import com.yahoo.geo.DistanceParser;
+import com.yahoo.geo.ParsedDegree;
import com.yahoo.language.Language;
import com.yahoo.language.detect.Detector;
import com.yahoo.language.process.Normalizer;
import com.yahoo.language.process.Segmenter;
import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.Location;
import com.yahoo.prelude.query.AndItem;
import com.yahoo.prelude.query.AndSegmentItem;
import com.yahoo.prelude.query.BoolItem;
@@ -34,6 +37,7 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.Limit;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.NearItem;
import com.yahoo.prelude.query.NearestNeighborItem;
import com.yahoo.prelude.query.NotItem;
@@ -94,8 +98,8 @@ import com.yahoo.search.query.parser.ParserFactory;
*/
public class YqlParser implements Parser {
- private static final String DESCENDING_HITS_ORDER = "descending";
- private static final String ASCENDING_HITS_ORDER = "ascending";
+ public static final String DESCENDING_HITS_ORDER = "descending";
+ public static final String ASCENDING_HITS_ORDER = "ascending";
private enum SegmentWhen {
NEVER, POSSIBLY, ALWAYS;
@@ -107,12 +111,12 @@ public class YqlParser implements Parser {
private static final Integer DEFAULT_HITS = 10;
private static final Integer DEFAULT_OFFSET = 0;
- private static final Integer DEFAULT_TARGET_NUM_HITS = 10;
+ public static final Integer DEFAULT_TARGET_NUM_HITS = 10;
private static final String ACCENT_DROP_DESCRIPTION = "setting for whether to remove accents if field implies it";
- private static final String ANNOTATIONS = "annotations";
+ public static final String ANNOTATIONS = "annotations";
private static final String FILTER_DESCRIPTION = "term filter setting";
private static final String IMPLICIT_TRANSFORMS_DESCRIPTION = "setting for whether built-in query transformers should touch the term";
- private static final String NFKC = "nfkc";
+ public static final String NFKC = "nfkc";
private static final String NORMALIZE_CASE_DESCRIPTION = "setting for whether to do case normalization if field implies it";
private static final String ORIGIN_DESCRIPTION = "string origin for a term";
private static final String RANKED_DESCRIPTION = "setting for whether to use term for ranking";
@@ -121,7 +125,7 @@ public class YqlParser implements Parser {
private static final String USER_INPUT_ALLOW_EMPTY = "allowEmpty";
private static final String USER_INPUT_DEFAULT_INDEX = "defaultIndex";
private static final String USER_INPUT_GRAMMAR = "grammar";
- private static final String USER_INPUT_LANGUAGE = "language";
+ public static final String USER_INPUT_LANGUAGE = "language";
private static final String USER_INPUT_RAW = "raw";
private static final String USER_INPUT_SEGMENT = "segment";
private static final String USER_INPUT = "userInput";
@@ -134,55 +138,56 @@ public class YqlParser implements Parser {
public static final String SORTING_LOCALE = "locale";
public static final String SORTING_STRENGTH = "strength";
- static final String ACCENT_DROP = "accentDrop";
- static final String ALTERNATIVES = "alternatives";
- static final String AND_SEGMENTING = "andSegmenting";
- static final String APPROXIMATE = "approximate";
- static final String BOUNDS = "bounds";
- static final String BOUNDS_LEFT_OPEN = "leftOpen";
- static final String BOUNDS_OPEN = "open";
- static final String BOUNDS_RIGHT_OPEN = "rightOpen";
- static final String CONNECTION_ID = "id";
- static final String CONNECTION_WEIGHT = "weight";
- static final String CONNECTIVITY = "connectivity";
- static final String DISTANCE = "distance";
- static final String DOT_PRODUCT = "dotProduct";
- static final String EQUIV = "equiv";
- static final String FILTER = "filter";
- static final String HIT_LIMIT = "hitLimit";
- static final String HNSW_EXPLORE_ADDITIONAL_HITS = "hnsw.exploreAdditionalHits";
- static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
- static final String LABEL = "label";
- static final String NEAR = "near";
- static final String NEAREST_NEIGHBOR = "nearestNeighbor";
- static final String NORMALIZE_CASE = "normalizeCase";
- static final String ONEAR = "onear";
- static final String ORIGIN_LENGTH = "length";
- static final String ORIGIN_OFFSET = "offset";
- static final String ORIGIN = "origin";
- static final String ORIGIN_ORIGINAL = "original";
- static final String PHRASE = "phrase";
- static final String PREDICATE = "predicate";
- static final String PREFIX = "prefix";
- static final String RANGE = "range";
- static final String RANKED = "ranked";
- static final String RANK = "rank";
- static final String SAME_ELEMENT = "sameElement";
- static final String SCORE_THRESHOLD = "scoreThreshold";
- static final String SIGNIFICANCE = "significance";
- static final String STEM = "stem";
- static final String SUBSTRING = "substring";
- static final String SUFFIX = "suffix";
- static final String TARGET_HITS = "targetHits";
- static final String TARGET_NUM_HITS = "targetNumHits";
- static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
- static final String UNIQUE_ID = "id";
- static final String USE_POSITION_DATA = "usePositionData";
- static final String WAND = "wand";
- static final String WEAK_AND = "weakAnd";
- static final String WEIGHTED_SET = "weightedSet";
- static final String WEIGHT = "weight";
- static final String URI = "uri";
+ public static final String ACCENT_DROP = "accentDrop";
+ public static final String ALTERNATIVES = "alternatives";
+ public static final String AND_SEGMENTING = "andSegmenting";
+ public static final String APPROXIMATE = "approximate";
+ public static final String BOUNDS = "bounds";
+ public static final String BOUNDS_LEFT_OPEN = "leftOpen";
+ public static final String BOUNDS_OPEN = "open";
+ public static final String BOUNDS_RIGHT_OPEN = "rightOpen";
+ public static final String CONNECTION_ID = "id";
+ public static final String CONNECTION_WEIGHT = "weight";
+ public static final String CONNECTIVITY = "connectivity";
+ public static final String DISTANCE = "distance";
+ public static final String DOT_PRODUCT = "dotProduct";
+ public static final String EQUIV = "equiv";
+ public static final String FILTER = "filter";
+ public static final String GEO_LOCATION = "geoLocation";
+ public static final String HIT_LIMIT = "hitLimit";
+ public static final String HNSW_EXPLORE_ADDITIONAL_HITS = "hnsw.exploreAdditionalHits";
+ public static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
+ public static final String LABEL = "label";
+ public static final String NEAR = "near";
+ public static final String NEAREST_NEIGHBOR = "nearestNeighbor";
+ public static final String NORMALIZE_CASE = "normalizeCase";
+ public static final String ONEAR = "onear";
+ public static final String ORIGIN_LENGTH = "length";
+ public static final String ORIGIN_OFFSET = "offset";
+ public static final String ORIGIN = "origin";
+ public static final String ORIGIN_ORIGINAL = "original";
+ public static final String PHRASE = "phrase";
+ public static final String PREDICATE = "predicate";
+ public static final String PREFIX = "prefix";
+ public static final String RANGE = "range";
+ public static final String RANKED = "ranked";
+ public static final String RANK = "rank";
+ public static final String SAME_ELEMENT = "sameElement";
+ public static final String SCORE_THRESHOLD = "scoreThreshold";
+ public static final String SIGNIFICANCE = "significance";
+ public static final String STEM = "stem";
+ public static final String SUBSTRING = "substring";
+ public static final String SUFFIX = "suffix";
+ public static final String TARGET_HITS = "targetHits";
+ public static final String TARGET_NUM_HITS = "targetNumHits";
+ public static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
+ public static final String UNIQUE_ID = "id";
+ public static final String USE_POSITION_DATA = "usePositionData";
+ public static final String WAND = "wand";
+ public static final String WEAK_AND = "weakAnd";
+ public static final String WEIGHTED_SET = "weightedSet";
+ public static final String WEIGHT = "weight";
+ public static final String URI = "uri";
private final IndexFacts indexFacts;
private final List<ConnectedItem> connectedItems = new ArrayList<>();
@@ -372,6 +377,8 @@ public class YqlParser implements Parser {
return buildWeightedSet(ast);
case DOT_PRODUCT:
return buildDotProduct(ast);
+ case GEO_LOCATION:
+ return buildGeoLocation(ast);
case NEAREST_NEIGHBOR:
return buildNearestNeighbor(ast);
case PREDICATE:
@@ -413,6 +420,29 @@ public class YqlParser implements Parser {
return fillWeightedSet(ast, args.get(1), new DotProductItem(getIndex(args.get(0))));
}
+ private Item buildGeoLocation(OperatorNode<ExpressionOperator> ast) {
+ List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1);
+ Preconditions.checkArgument(args.size() == 4, "Expected 4 arguments, got %s.", args.size());
+ String field = fetchFieldRead(args.get(0));
+ var coord_1 = ParsedDegree.fromString(fetchFieldRead(args.get(1)), true, false);
+ var coord_2 = ParsedDegree.fromString(fetchFieldRead(args.get(2)), false, true);
+ double radius = DistanceParser.parse(fetchFieldRead(args.get(3)));
+ var loc = new Location();
+ if (coord_1.isLatitude && coord_2.isLongitude) {
+ loc.setGeoCircle(coord_1.degrees, coord_2.degrees, radius);
+ } else if (coord_2.isLatitude && coord_1.isLongitude) {
+ loc.setGeoCircle(coord_2.degrees, coord_1.degrees, radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinates '"+coord_1+"' and '"+coord_2+"'");
+ }
+ var item = new GeoLocationItem(loc, field);
+ String label = getAnnotation(ast, LABEL, String.class, null, "item label");
+ if (label != null) {
+ item.setLabel(label);
+ }
+ return item;
+ }
+
private Item buildNearestNeighbor(OperatorNode<ExpressionOperator> ast) {
List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1);
Preconditions.checkArgument(args.size() == 2, "Expected 2 arguments, got %s.", args.size());
@@ -438,7 +468,7 @@ public class YqlParser implements Parser {
item.setAllowApproximate(allowApproximate);
String label = getAnnotation(ast, LABEL, String.class, null, "item label");
if (label != null) {
- item.setLabel(label);
+ item.setLabel(label);
}
return item;
}
@@ -902,6 +932,8 @@ public class YqlParser implements Parser {
private static String fetchFieldRead(OperatorNode<ExpressionOperator> ast) {
switch (ast.getOperator()) {
+ case LITERAL:
+ return ast.getArgument(0).toString();
case READ_FIELD:
return ast.getArgument(1);
case PROPREF:
diff --git a/container-search/src/test/java/com/yahoo/prelude/searcher/test/PosSearcherTestCase.java b/container-search/src/test/java/com/yahoo/prelude/searcher/test/PosSearcherTestCase.java
index aa3fa53119e..aa48e8494f2 100644
--- a/container-search/src/test/java/com/yahoo/prelude/searcher/test/PosSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/searcher/test/PosSearcherTestCase.java
@@ -123,7 +123,8 @@ public class PosSearcherTestCase {
q.properties().set("pos.ll", "N0;E0");
q.properties().set("pos.radius", "-1");
doSearch(searcher, q, 0, 10);
- assertEquals("(2,0,0,536870912,0,1,0,4294967295)", q.getRanking().getLocation().toString());
+ assertEquals("(2,0,0,-1,0,1,0,4294967295)", q.getRanking().getLocation().toString());
+ assertEquals("(2,0,0,536870912,0,1,0,4294967295)", q.getRanking().getLocation().backendString());
}
/**
diff --git a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java
index e85940278e3..445073ced3a 100644
--- a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java
@@ -14,7 +14,6 @@ import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.query.profile.config.QueryProfileXMLReader;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.HashMap;
diff --git a/container-search/src/test/java/com/yahoo/search/yql/VespaSerializerTestCase.java b/container-search/src/test/java/com/yahoo/search/yql/VespaSerializerTestCase.java
index d770b08d31a..f8e930fa19d 100644
--- a/container-search/src/test/java/com/yahoo/search/yql/VespaSerializerTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/yql/VespaSerializerTestCase.java
@@ -119,6 +119,14 @@ public class VespaSerializerTestCase {
}
@Test
+ public void testGeoLocation() {
+ parseAndConfirm("geoLocation(workplace, 63.418417, 10.433033, \"0.5 deg\")");
+ parseAndConfirm("geoLocation(headquarters, 37.41638, -122.024683, \"180.0 deg\")");
+ parseAndConfirm("geoLocation(home, -17.0, 42.0, \"0.0 deg\")");
+ parseAndConfirm("geoLocation(workplace, -12.0, -34.0, \"-1.0 deg\")");
+ }
+
+ @Test
public void testNear() {
parseAndConfirm("title contains near(\"a\", \"b\")");
parseAndConfirm("title contains ([{\"distance\": 50}]near(\"a\", \"b\"))");
diff --git a/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java b/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
index a151244525a..62a9e27cd96 100644
--- a/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
@@ -548,6 +548,24 @@ public class YqlParserTestCase {
}
@Test
+ public void testGeoLocation() {
+ assertParse("select foo from bar where geoLocation(workplace, 63.418417, 10.433033, \"0.5 deg\");",
+ "GEO_LOCATION workplace:(2,10433033,63418417,500000,0,1,0,1921876103)");
+ assertParse("select foo from bar where geoLocation(headquarters, \"37.416383\", \"-122.024683\", \"100 miles\");",
+ "GEO_LOCATION headquarters:(2,-122024683,37416383,1450561,0,1,0,3411238761)");
+ assertParse("select foo from bar where geoLocation(home, \"E10.433033\", \"N63.418417\", \"5km\");",
+ "GEO_LOCATION home:(2,10433033,63418417,45066,0,1,0,1921876103)");
+
+ assertParseFail("select foo from bar where geoLocation(qux, 1, 2);",
+ new IllegalArgumentException("Expected 4 arguments, got 3."));
+ assertParseFail("select foo from bar where geoLocation(qux, 2.0, \"N5.0\", \"0.5 deg\");",
+ new IllegalArgumentException(
+ "Invalid geoLocation coordinates 'Latitude: 2.0 degrees' and 'Latitude: 5.0 degrees'"));
+ assertParse("select foo from bar where geoLocation(workplace, -12, -34, \"-77 d\");",
+ "GEO_LOCATION workplace:(2,-34000000,-12000000,-1,0,1,0,4201111954)");
+ }
+
+ @Test
public void testNearestNeighbor() {
assertParse("select foo from bar where nearestNeighbor(semantic_embedding, my_vector);",
"NEAREST_NEIGHBOR {field=semantic_embedding,queryTensorName=my_vector,hnsw.exploreAdditionalHits=0,approximate=true,targetHits=0}");
diff --git a/container-search/src/test/java/com/yahoo/select/SelectTestCase.java b/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
index 4691ef42e55..f297fd69f24 100644
--- a/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
+++ b/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
@@ -522,6 +522,18 @@ public class SelectTestCase {
}
@Test
+ public void testGeoLocation() {
+ assertParse("{ \"geoLocation\": [ \"workplace\", 63.418417, 10.433033, \"0.5 deg\" ] }",
+ "GEO_LOCATION workplace:(2,10433033,63418417,500000,0,1,0,1921876103)");
+ assertParse("{ \"geoLocation\": [ \"headquarters\", \"37.416383\", \"-122.024683\", \"100 miles\" ] }",
+ "GEO_LOCATION headquarters:(2,-122024683,37416383,1450561,0,1,0,3411238761)");
+ assertParse("{ \"geoLocation\": [ \"home\", \"E10.433033\", \"N63.418417\", \"5km\" ] }",
+ "GEO_LOCATION home:(2,10433033,63418417,45066,0,1,0,1921876103)");
+ assertParse("{ \"geoLocation\": [ \"workplace\", -12.0, -34.0, \"-77 deg\" ] }",
+ "GEO_LOCATION workplace:(2,-34000000,-12000000,-1,0,1,0,4201111954)");
+ }
+
+ @Test
public void testNearestNeighbor() {
assertParse("{ \"nearestNeighbor\": [ \"f1field\", \"q2prop\" ] }",
"NEAREST_NEIGHBOR {field=f1field,queryTensorName=q2prop,hnsw.exploreAdditionalHits=0,approximate=true,targetHits=0}");
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 16464f7cea6..c04adcec594 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
@@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import java.io.InputStream;
@@ -32,7 +33,7 @@ public interface ConfigServer {
PreparedApplication deploy(DeploymentData deployment);
- void restart(DeploymentId deployment, Optional<Hostname> hostname);
+ void restart(DeploymentId deployment, RestartFilter restartFilter);
void deactivate(DeploymentId deployment) throws NotFoundException;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationStore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationStore.java
index 44db38c3ec2..a86dc6895ae 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationStore.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationStore.java
@@ -22,10 +22,7 @@ public interface ApplicationStore {
byte[] get(TenantName tenant, ApplicationName application, ApplicationVersion applicationVersion);
/** Find application package by given build number */
- default Optional<byte[]> find(TenantName tenant, ApplicationName application, long buildNumber) {
- // TODO(mpolden): Remove default once all implemenations catch up
- return Optional.empty();
- }
+ Optional<byte[]> find(TenantName tenant, ApplicationName application, long buildNumber);
/** Stores the given tenant application package of the given version. */
void put(TenantName tenant, ApplicationName application, ApplicationVersion applicationVersion, byte[] applicationPackage);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java
index 30bf23f18a9..6848c67fed2 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.jira;
+import java.io.InputStream;
import java.util.List;
/**
@@ -14,5 +15,5 @@ public interface Jira {
void commentIssue(JiraIssue issue, JiraComment comment);
- void addAttachment(JiraIssue issue, String filename, String fileContent);
+ void addAttachment(JiraIssue issue, String filename, InputStream fileContent);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/RestartFilter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/RestartFilter.java
new file mode 100644
index 00000000000..685ed392c00
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/RestartFilter.java
@@ -0,0 +1,51 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.noderepository;
+
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.HostName;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
+
+import java.util.Optional;
+
+/**
+ * Attributes to filter when restarting nodes in a deployment.
+ * If all attributes are empty, all nodes are restarted.
+ * Used in {@link ConfigServer#restart(DeploymentId, RestartFilter)}
+ *
+ * @author olaa
+ */
+public class RestartFilter {
+
+ private Optional<HostName> hostName = Optional.empty();
+ private Optional<ClusterSpec.Type> clusterType = Optional.empty();
+ private Optional<ClusterSpec.Id> clusterId = Optional.empty();
+
+ public Optional<HostName> getHostName() {
+ return hostName;
+ }
+
+ public Optional<ClusterSpec.Type> getClusterType() {
+ return clusterType;
+ }
+
+ public Optional<ClusterSpec.Id> getClusterId() {
+ return clusterId;
+ }
+
+ public RestartFilter withHostName(Optional<HostName> hostName) {
+ this.hostName = hostName;
+ return this;
+ }
+
+ public RestartFilter withClusterType(Optional<ClusterSpec.Type> clusterType) {
+ this.clusterType = clusterType;
+ return this;
+ }
+
+ public RestartFilter withClusterId(Optional<ClusterSpec.Id> clusterId) {
+ this.clusterId = clusterId;
+ return this;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index d644cf21638..c03b38867f9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -46,6 +46,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepo
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackageValidator;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -680,10 +681,10 @@ public class ApplicationController {
/**
* Tells config server to schedule a restart of all nodes in this deployment
*
- * @param hostname If non-empty, restart will only be scheduled for this host
+ * @param restartFilter Variables to filter which nodes to restart.
*/
- public void restart(DeploymentId deploymentId, Optional<Hostname> hostname) {
- configServer.restart(deploymentId, hostname);
+ public void restart(DeploymentId deploymentId, RestartFilter restartFilter) {
+ configServer.restart(deploymentId, restartFilter);
}
/**
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 3b1926fa6e9..26270c092d5 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
@@ -5,13 +5,11 @@ import com.google.common.base.Suppliers;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.flags.BooleanFlag;
-import com.yahoo.vespa.flags.FetchVector;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
@@ -64,14 +62,12 @@ public class RoutingController {
private final Controller controller;
private final RoutingPolicies routingPolicies;
private final RotationRepository rotationRepository;
- private final BooleanFlag allowDirectRouting;
public RoutingController(Controller controller, RotationsConfig rotationsConfig) {
this.controller = Objects.requireNonNull(controller, "controller must be non-null");
this.routingPolicies = new RoutingPolicies(controller);
this.rotationRepository = new RotationRepository(rotationsConfig, controller.applications(),
controller.curator());
- this.allowDirectRouting = Flags.ALLOW_DIRECT_ROUTING.bindTo(controller.flagSource());
}
public RoutingPolicies policies() {
@@ -93,7 +89,7 @@ public class RoutingController {
for (var routingMethod : controller.zoneRegistry().routingMethods(policy.id().zone())) {
if (routingMethod.isDirect() && !isSystemApplication && !canRouteDirectlyTo(deployment, application.get())) continue;
endpoints.add(policy.endpointIn(controller.system(), routingMethod, controller.zoneRegistry()));
- endpoints.add(policy.weightedEndpointIn(controller.system(), routingMethod));
+ endpoints.add(policy.regionEndpointIn(controller.system(), routingMethod));
}
}
return EndpointList.copyOf(endpoints);
@@ -115,22 +111,23 @@ public class RoutingController {
var deployments = rotation.regions().stream()
.map(region -> new DeploymentId(instance.id(), ZoneId.from(Environment.prod, region)))
.collect(Collectors.toList());
- computeGlobalEndpoints(RoutingId.of(instance.id(), rotation.endpointId()),
+ computeGlobalEndpoints(RoutingId.of(instance.id(), rotation.endpointId()), rotation.clusterId(),
application, deployments).requiresRotation()
.forEach(endpoints::add);
}
// Add global endpoints provided by routing policies
- var deploymentsByRoutingId = new LinkedHashMap<RoutingId, List<DeploymentId>>();
+ var deploymentsByEndpointKey = new LinkedHashMap<EndpointKey, List<DeploymentId>>();
for (var policy : routingPolicies.get(instance.id()).values()) {
if (!policy.status().isActive()) continue;
for (var endpointId : policy.endpoints()) {
- var routingId = RoutingId.of(instance.id(), endpointId);
- deploymentsByRoutingId.putIfAbsent(routingId, new ArrayList<>());
- deploymentsByRoutingId.get(routingId).add(new DeploymentId(instance.id(), policy.id().zone()));
+ var endpointKey = new EndpointKey(RoutingId.of(instance.id(), endpointId), policy.id().cluster());
+ deploymentsByEndpointKey.computeIfAbsent(endpointKey, (k) -> new ArrayList<>())
+ .add(new DeploymentId(instance.id(), policy.id().zone()));
}
}
- deploymentsByRoutingId.forEach((routingId, deployments) -> {
- computeGlobalEndpoints(routingId, application, deployments).not().requiresRotation().forEach(endpoints::add);
+ deploymentsByEndpointKey.forEach((endpointKey, deployments) -> {
+ computeGlobalEndpoints(endpointKey.routingId, endpointKey.cluster, application,
+ deployments).not().requiresRotation().forEach(endpoints::add);
});
return EndpointList.copyOf(endpoints);
}
@@ -265,7 +262,7 @@ public class RoutingController {
private boolean canRouteDirectlyTo(DeploymentId deploymentId, Application application) {
if (controller.system().isPublic()) return true; // Public always supports direct routing
if (controller.system().isCd()) return true; // CD deploys directly so we cannot enforce all requirements below
- if(deploymentId.zoneId().environment().isManuallyDeployed()) return true; // Manually deployed zones does not include any use cases where direct routing is not supported
+ if (deploymentId.zoneId().environment().isManuallyDeployed()) return true; // Manually deployed zones always support direct routing
// Check Athenz service presence. The test framework uses this identity when sending requests to the
// deployment's container(s).
@@ -285,39 +282,34 @@ public class RoutingController {
.or(() -> application.latestVersion().flatMap(ApplicationVersion::compileVersion));
if (compileVersion.isEmpty()) return false;
if (compileVersion.get().isBefore(DIRECT_ROUTING_MIN_VERSION)) return false;
-
- // Check feature flag
- // TODO(mpolden): Remove once we make this default
- return this.allowDirectRouting.with(FetchVector.Dimension.APPLICATION_ID,
- deploymentId.applicationId().serializedForm())
- .value();
+ return true;
}
/** Compute global endpoints for given routing ID, application and deployments */
- private EndpointList computeGlobalEndpoints(RoutingId routingId, Application application, List<DeploymentId> deployments) {
+ private EndpointList computeGlobalEndpoints(RoutingId routingId, ClusterSpec.Id cluster, Application application, List<DeploymentId> deployments) {
var endpoints = new ArrayList<Endpoint>();
var directMethods = 0;
- var targets = deployments.stream().map(DeploymentId::zoneId).collect(Collectors.toList());
+ var zones = deployments.stream().map(DeploymentId::zoneId).collect(Collectors.toList());
for (var method : routingMethodsOfAll(deployments, application)) {
if (method.isDirect() && ++directMethods > 1) {
throw new IllegalArgumentException("Invalid routing methods for " + routingId + ": Exceeded maximum " +
"direct methods");
}
endpoints.add(Endpoint.of(routingId.application())
- .named(routingId.endpointId(), targets)
+ .target(routingId.endpointId(), cluster, zones)
.on(Port.fromRoutingMethod(method))
.routingMethod(method)
.in(controller.system()));
// TODO(mpolden): Remove this once all applications have migrated away from legacy endpoints
if (method == RoutingMethod.shared) {
endpoints.add(Endpoint.of(routingId.application())
- .named(routingId.endpointId(), targets)
+ .target(routingId.endpointId(), cluster, zones)
.on(Port.plain(4080))
.legacy()
.routingMethod(method)
.in(controller.system()));
endpoints.add(Endpoint.of(routingId.application())
- .named(routingId.endpointId(), targets)
+ .target(routingId.endpointId(), cluster, zones)
.on(Port.tls(4443))
.legacy()
.routingMethod(method)
@@ -327,4 +319,30 @@ public class RoutingController {
return EndpointList.copyOf(endpoints);
}
+ private static class EndpointKey {
+
+ private final RoutingId routingId;
+ private final ClusterSpec.Id cluster;
+
+ public EndpointKey(RoutingId routingId, ClusterSpec.Id cluster) {
+ this.routingId = routingId;
+ this.cluster = cluster;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EndpointKey that = (EndpointKey) o;
+ return routingId.equals(that.routingId) &&
+ cluster.equals(that.cluster);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(routingId, cluster);
+ }
+
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
index 083984bd13c..d0e8a0f2816 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
@@ -5,11 +5,13 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.deployment.ZipBuilder;
import com.yahoo.yolean.Exceptions;
import java.io.ByteArrayInputStream;
@@ -19,7 +21,10 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -184,4 +189,27 @@ public class ApplicationPackage {
}
+ /** Creates a valid application package that will remove all application's deployments */
+ public static ApplicationPackage deploymentRemoval() {
+ DeploymentSpec deploymentSpec = DeploymentSpec.empty;
+ ValidationOverrides validationOverrides = allValidationOverrides();
+ try (ZipBuilder zipBuilder = new ZipBuilder(deploymentSpec.xmlForm().length() + validationOverrides.xmlForm().length() + 500)) {
+ zipBuilder.add("validation-overrides.xml", validationOverrides.xmlForm().getBytes(UTF_8));
+ zipBuilder.add("deployment.xml", deploymentSpec.xmlForm().getBytes(UTF_8));
+
+ zipBuilder.close();
+ return new ApplicationPackage(zipBuilder.toByteArray());
+ }
+ }
+
+ private static ValidationOverrides allValidationOverrides() {
+ String until = DateTimeFormatter.ISO_LOCAL_DATE.format(Instant.now().plus(Duration.ofDays(25)).atZone(ZoneOffset.UTC));
+ StringBuilder validationOverridesContents = new StringBuilder(1000);
+ validationOverridesContents.append("<validation-overrides version=\"1.0\">\n");
+ for (ValidationId validationId: ValidationId.values())
+ validationOverridesContents.append("\t<allow until=\"").append(until).append("\">").append(validationId.value()).append("</allow>\n");
+ validationOverridesContents.append("</validation-overrides>\n");
+
+ return ValidationOverrides.fromXml(validationOverridesContents.toString());
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
index b245718171f..3e72936575d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
@@ -51,7 +51,8 @@ public class ApplicationPackageValidator {
/** Verify that we have the security/clients.pem file for public systems */
private void validateSecurityClientsPem(ApplicationPackage applicationPackage) {
- if (controller.system().isPublic() && applicationPackage.trustedCertificates().isEmpty())
+ if (!controller.system().isPublic() || applicationPackage.deploymentSpec().steps().isEmpty()) return;
+ if (applicationPackage.trustedCertificates().isEmpty())
throw new IllegalArgumentException("Missing required file 'security/clients.pem'");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
index 62804074337..341bf7de9d6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
@@ -17,8 +17,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
- * Represents an application's endpoint. The endpoint scope can either be global or a specific zone. This is visible to
- * the tenant and is used by the tenant when accessing deployments.
+ * Represents an application's endpoint in hosted Vespa. This encapsulates all logic for building URLs and DNS names for
+ * application endpoints.
*
* @author mpolden
*/
@@ -29,7 +29,8 @@ public class Endpoint {
private static final String PUBLIC_DNS_SUFFIX = ".public.vespa.oath.cloud";
private static final String PUBLIC_CD_DNS_SUFFIX = ".public-cd.vespa.oath.cloud";
- private final String name;
+ private final EndpointId id;
+ private final ClusterSpec.Id cluster;
private final URI url;
private final List<ZoneId> zones;
private final Scope scope;
@@ -37,16 +38,20 @@ public class Endpoint {
private final RoutingMethod routingMethod;
private final boolean tls;
- private Endpoint(String name, URI url, List<ZoneId> zones, Scope scope, Port port, boolean legacy, RoutingMethod routingMethod) {
- Objects.requireNonNull(name, "name must be non-null");
+ private Endpoint(EndpointId id, ClusterSpec.Id cluster, URI url, List<ZoneId> zones, Scope scope, Port port, boolean legacy, RoutingMethod routingMethod) {
+ Objects.requireNonNull(cluster, "cluster must be non-null");
Objects.requireNonNull(zones, "zones must be non-null");
Objects.requireNonNull(scope, "scope must be non-null");
Objects.requireNonNull(port, "port must be non-null");
Objects.requireNonNull(routingMethod, "routingMethod must be non-null");
- if ((scope == Scope.zone || scope == Scope.weighted) && zones.size() != 1) {
- throw new IllegalArgumentException("A single zone must be given for " + scope + "-scoped endpoints");
+ if (scope == Scope.global) {
+ if (id == null) throw new IllegalArgumentException("Endpoint ID must be set for global endpoints");
+ } else {
+ if (id != null) throw new IllegalArgumentException("Endpoint ID cannot be set for " + scope + " endpoints");
+ if (zones.size() != 1) throw new IllegalArgumentException("A single zone must be given for " + scope + " endpoints");
}
- this.name = name;
+ this.id = id;
+ this.cluster = cluster;
this.url = url;
this.zones = List.copyOf(zones);
this.scope = scope;
@@ -55,10 +60,11 @@ public class Endpoint {
this.tls = port.tls;
}
- private Endpoint(String name, ApplicationId application, List<ZoneId> zones, Scope scope, SystemName system,
+ private Endpoint(EndpointId id, ClusterSpec.Id cluster, ApplicationId application, List<ZoneId> zones, Scope scope, SystemName system,
Port port, boolean legacy, RoutingMethod routingMethod) {
- this(name,
- createUrl(name,
+ this(id,
+ cluster,
+ createUrl(endpointOrClusterAsString(id, cluster),
Objects.requireNonNull(application, "application must be non-null"),
zones,
scope,
@@ -70,14 +76,19 @@ public class Endpoint {
}
/**
- * Returns the name of this endpoint (the first component of the DNS name). Depending on the endpoint type, this
- * can be one of the following:
- * - A wildcard (any scope)
- * - A cluster name (only zone scope)
- * - An endpoint ID (only global scope)
+ * Returns the name of this endpoint (the first component of the DNS name). This can be one of the following:
+ *
+ * - The wildcard character '*' (for wildcard endpoints, with any scope)
+ * - The cluster ID (zone scope)
+ * - The endpoint ID (global scope)
*/
public String name() {
- return name;
+ return endpointOrClusterAsString(id, cluster);
+ }
+
+ /** Returns the cluster ID to which this routes traffic */
+ public ClusterSpec.Id cluster() {
+ return cluster;
}
/** Returns the URL used to access this */
@@ -106,7 +117,7 @@ public class Endpoint {
return legacy;
}
- /** Returns the routing used for this */
+ /** Returns the routing method used for this */
public RoutingMethod routingMethod() {
return routingMethod;
}
@@ -125,7 +136,7 @@ public class Endpoint {
public String upstreamIdOf(DeploymentId deployment) {
if (scope != Scope.global) throw new IllegalArgumentException("Scope " + scope + " does not have upstream name");
if (!routingMethod.isShared()) throw new IllegalArgumentException("Routing method " + routingMethod + " does not have upstream name");
- return upstreamIdOf(name, deployment.applicationId(), deployment.zoneId());
+ return upstreamIdOf(name(), deployment.applicationId(), deployment.zoneId());
}
@Override
@@ -151,6 +162,10 @@ public class Endpoint {
return dnsSuffix(system, false);
}
+ private static String endpointOrClusterAsString(EndpointId id, ClusterSpec.Id cluster) {
+ return id == null ? cluster.value() : id.id();
+ }
+
private static URI createUrl(String name, ApplicationId application, List<ZoneId> zones, Scope scope,
SystemName system, Port port, boolean legacy, RoutingMethod routingMethod) {
String scheme = port.tls ? "https" : "http";
@@ -190,7 +205,7 @@ public class Endpoint {
if (scope == Scope.global) return "global";
var zone = zones.get(0);
var region = zone.region().value();
- if (scope == Scope.weighted) region += "-w";
+ if (scope == Scope.region) region += "-w";
if (!legacy && zone.environment().isProduction()) return region; // Skip prod environment for non-legacy endpoints
return region + "." + zone.environment().value();
}
@@ -261,15 +276,15 @@ public class Endpoint {
/** An endpoint's scope */
public enum Scope {
- /** Endpoint points to all zones */
+ /** Endpoint points to one or more zones. Traffic is routed to the zone closest to the client */
global,
+ /** Endpoint points to one more zones in the same geographical region. Traffic is routed equally across zones */
+ region,
+
/** Endpoint points to a single zone */
zone,
- /** Endpoint points to a single region */
- weighted,
-
}
/** Represents an endpoint's HTTP port */
@@ -323,7 +338,7 @@ public class Endpoint {
if (!systemApplication.hasEndpoint()) throw new IllegalArgumentException(systemApplication + " has no endpoint");
RoutingMethod routingMethod = RoutingMethod.exclusive;
Port port = url.getPort() == -1 ? Port.tls() : Port.tls(url.getPort()); // System application endpoints are always TLS
- return new Endpoint("", url, List.of(zone), Scope.zone, port, false, routingMethod);
+ return new Endpoint(null, ClusterSpec.Id.from("admin"), url, List.of(zone), Scope.zone, port, false, routingMethod);
}
public static class EndpointBuilder {
@@ -337,60 +352,55 @@ public class Endpoint {
private Port port;
private RoutingMethod routingMethod = RoutingMethod.shared;
private boolean legacy = false;
- private boolean wildcard = false;
private EndpointBuilder(ApplicationId application) {
this.application = application;
}
- /** Sets the cluster target for this */
+ /** Sets the zone target for this */
public EndpointBuilder target(ClusterSpec.Id cluster, ZoneId zone) {
- if (endpointId != null || wildcard) {
- throw new IllegalArgumentException("Cannot set multiple target types");
- }
+ checkScope();
this.cluster = cluster;
this.scope = Scope.zone;
this.zones = List.of(zone);
return this;
}
- /** Sets the endpoint target ID for this (as defined in deployments.xml) */
- public EndpointBuilder named(EndpointId endpointId) {
- return named(endpointId, List.of());
+ /** Sets the global target with given ID and pointing to the default cluster */
+ public EndpointBuilder target(EndpointId endpointId) {
+ return target(endpointId, ClusterSpec.Id.from("default"), List.of());
}
- /** Sets the endpoint ID for this (as defined in deployments.xml) */
- public EndpointBuilder named(EndpointId endpointId, List<ZoneId> targets) {
- if (cluster != null || wildcard) {
- throw new IllegalArgumentException("Cannot set multiple target types");
- }
+ /** Sets the global target with given ID, zones and cluster (as defined in deployments.xml) */
+ public EndpointBuilder target(EndpointId endpointId, ClusterSpec.Id cluster, List<ZoneId> zones) {
+ checkScope();
this.endpointId = endpointId;
- this.zones = targets;
+ this.cluster = cluster;
+ this.zones = zones;
this.scope = Scope.global;
return this;
}
/** Sets the global wildcard target for this */
public EndpointBuilder wildcard() {
- return wildcard(Scope.global, List.of());
+ return target(EndpointId.of("*"), ClusterSpec.Id.from("*"), List.of());
}
/** Sets the zone wildcard target for this */
public EndpointBuilder wildcard(ZoneId zone) {
- return wildcard(Scope.zone, List.of(zone));
+ return target(ClusterSpec.Id.from("*"), zone);
}
- private EndpointBuilder wildcard(Scope scope, List<ZoneId> zones) {
- if (endpointId != null || cluster != null) {
- throw new IllegalArgumentException("Cannot set multiple target types");
- }
- this.wildcard = true;
- this.scope = scope;
- this.zones = zones;
+ /** Sets the region target for this, deduced from given zone */
+ public EndpointBuilder targetRegion(ClusterSpec.Id cluster, ZoneId zone) {
+ checkScope();
+ this.cluster = cluster;
+ this.scope = Scope.region;
+ this.zones = List.of(effectiveZone(zone));
return this;
}
- /** Sets the port of this */
+ /** Sets the port of this */
public EndpointBuilder on(Port port) {
this.port = port;
return this;
@@ -408,35 +418,21 @@ public class Endpoint {
return this;
}
- /** Make this a weighted endpoint */
- public EndpointBuilder weighted() {
- if (scope != Scope.zone && scope != Scope.weighted) {
- throw new IllegalArgumentException("Endpoint must target zone before making it weighted");
- }
- this.scope = Scope.weighted;
- this.zones = List.of(effectiveZone(zones.get(0)));
- return this;
- }
-
/** Sets the system that owns this */
public Endpoint in(SystemName system) {
- String name;
- if (wildcard) {
- name = "*";
- } else if (endpointId != null) {
- name = endpointId.id();
- } else if (cluster != null) {
- name = cluster.value();
- } else {
- throw new IllegalArgumentException("Must set either cluster, rotation or wildcard target");
- }
if (system.isPublic() && routingMethod != RoutingMethod.exclusive) {
throw new IllegalArgumentException("Public system only supports routing method " + RoutingMethod.exclusive);
}
if (routingMethod.isDirect() && !port.isDefault()) {
throw new IllegalArgumentException("Routing method " + routingMethod + " can only use default port");
}
- return new Endpoint(name, application, zones, scope, system, port, legacy, routingMethod);
+ return new Endpoint(endpointId, cluster, application, zones, scope, system, port, legacy, routingMethod);
+ }
+
+ private void checkScope() {
+ if (scope != null) {
+ throw new IllegalArgumentException("Cannot change endpoint scope. Already set to " + scope);
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
index 4da34dad737..e847667bf45 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.collections.AbstractFilteringList;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.zone.ZoneId;
import java.util.Collection;
@@ -33,6 +34,11 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList>
return matching(endpoint -> endpoint.name().equals(id.id()));
}
+ /** Returns the subset of endpoints pointing to given cluster */
+ public EndpointList cluster(ClusterSpec.Id cluster) {
+ return matching(endpoint -> endpoint.cluster().equals(cluster));
+ }
+
/** Returns the subset of endpoints which target all of the given zones */
public EndpointList targets(List<ZoneId> zones) {
return matching(endpoint -> endpoint.zones().containsAll(zones));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java
index 425364f6741..1cf42cf0073 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java
@@ -12,10 +12,6 @@ import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.secretstore.SecretNotFoundException;
import com.yahoo.container.jdisc.secretstore.SecretStore;
-
-import java.util.LinkedHashSet;
-import java.util.logging.Level;
-
import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.flags.BooleanFlag;
@@ -24,8 +20,8 @@ import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.Instance;
-import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
@@ -41,6 +37,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -48,6 +45,7 @@ import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -303,8 +301,8 @@ public class EndpointCertificateManager {
List<Endpoint.EndpointBuilder> endpoints = new ArrayList<>();
- if(zone.environment().isProduction()) {
- endpoints.add(Endpoint.of(applicationId).named(EndpointId.defaultId()));
+ if (zone.environment().isProduction()) {
+ endpoints.add(Endpoint.of(applicationId).target(EndpointId.defaultId()));
endpoints.add(Endpoint.of(applicationId).wildcard());
}
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 6bc0ec6baf8..89f57f6ea9b 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
@@ -27,7 +27,6 @@ import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
@@ -38,6 +37,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentFailureMails;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
@@ -244,10 +244,10 @@ public class InternalStepRunner implements StepRunner {
.flatMap(action -> action.services.stream())
.map(service -> service.hostName)
.sorted().distinct()
- .map(Hostname::new)
+ .map(HostName::from)
.forEach(hostname -> {
- controller.applications().restart(new DeploymentId(id, type.zone(controller.system())), Optional.of(hostname));
- logger.log("Schedule service restart on host " + hostname.id() + ".");
+ controller.applications().restart(new DeploymentId(id, type.zone(controller.system())), new RestartFilter().withHostName(Optional.of(hostname)));
+ logger.log("Schedule service restart on host " + hostname.value() + ".");
});
logger.log("Deployment successful.");
if (prepareResponse.message != null)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 9a5f9b1074a..ac19b5dd5e0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -47,7 +47,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.collect.ImmutableList.copyOf;
@@ -58,7 +57,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toUnmodifiableList;
-import static java.util.stream.Collectors.toUnmodifiableMap;
/**
* A singleton owned by the controller, which contains the state and methods for controlling deployment jobs.
@@ -379,8 +377,8 @@ public class JobController {
* Accepts and stores a new application package and test jar pair under a generated application version key.
*/
public ApplicationVersion submit(TenantAndApplicationId id, Optional<SourceRevision> revision, Optional<String> authorEmail,
- Optional<String> sourceUrl, Optional<String> commit,
- long projectId, ApplicationPackage applicationPackage, byte[] testPackageBytes) {
+ Optional<String> sourceUrl, long projectId, ApplicationPackage applicationPackage,
+ byte[] testPackageBytes) {
AtomicReference<ApplicationVersion> version = new AtomicReference<>();
controller.applications().lockApplicationOrThrow(id, application -> {
long run = 1 + application.get().latestVersion()
@@ -390,7 +388,7 @@ public class JobController {
applicationPackage.compileVersion(),
applicationPackage.buildTime(),
sourceUrl,
- commit));
+ revision.map(SourceRevision::commit)));
controller.applications().applicationStore().put(id.tenant(),
id.application(),
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
index 786819d9442..1f20e48edf5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -17,6 +17,7 @@ import com.yahoo.yolean.Exceptions;
import java.time.Duration;
import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
/**
@@ -38,14 +39,15 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
- confirmApplicationOwnerships();
- ensureConfirmationResponses();
- updateConfirmedApplicationOwners();
+ protected boolean maintain() {
+ return confirmApplicationOwnerships() &
+ ensureConfirmationResponses() &
+ updateConfirmedApplicationOwners();
}
/** File an ownership issue with the owners of all applications we know about. */
- private void confirmApplicationOwnerships() {
+ private boolean confirmApplicationOwnerships() {
+ AtomicBoolean success = new AtomicBoolean(true);
applications()
.withProjectId()
.withProductionDeployment()
@@ -63,10 +65,11 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
}).ifPresent(newIssueId -> store(newIssueId, application.id()));
}
catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
+ success.set(false);
log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + application.id() + "': " + Exceptions.toMessageString(e));
}
});
-
+ return success.get();
}
private ApplicationSummary summaryOf(TenantAndApplicationId application) {
@@ -85,7 +88,8 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
}
/** Escalate ownership issues which have not been closed before a defined amount of time has passed. */
- private void ensureConfirmationResponses() {
+ private boolean ensureConfirmationResponses() {
+ AtomicBoolean success = new AtomicBoolean(true);
for (Application application : applications())
application.ownershipIssueId().ifPresent(issueId -> {
try {
@@ -93,12 +97,14 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
ownershipIssues.ensureResponse(issueId, tenant.contact());
}
catch (RuntimeException e) {
+ success.set(false);
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
}
});
+ return success.get();
}
- private void updateConfirmedApplicationOwners() {
+ private boolean updateConfirmedApplicationOwners() {
applications()
.withProjectId()
.withProductionDeployment()
@@ -112,6 +118,7 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
controller().applications().store(lockedApplication.withOwner(owner)));
});
});
+ return true;
}
private ApplicationList applications() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
index 4b96bd404ee..c0d79861fae 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
@@ -13,9 +13,9 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.Issue;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueHandler;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
* Automatically fetches and handles scheduled events from AWS:
* 1. Deprovisions the affected hosts if applicable
* 2. Submits an issue detailing the event if some hosts are not processed by 1.
+ *
* @author mgimle
*/
public class CloudEventReporter extends ControllerMaintainer {
@@ -44,40 +45,47 @@ public class CloudEventReporter extends ControllerMaintainer {
}
@Override
- protected void maintain() {
- log.log(Level.INFO, "Fetching events for cloud hosts.");
+ protected boolean maintain() {
for (var awsRegion : zonesByCloudNativeRegion.keySet()) {
List<CloudEvent> events = eventFetcher.getEvents(awsRegion);
for (var event : events) {
log.info(String.format("Retrieved event %s, affecting the following instances: %s",
event.instanceEventId,
event.affectedInstances));
- List<String> deprovisionedHosts = deprovisionHosts(awsRegion, event);
- submitIssue(event, deprovisionedHosts);
+ List<Node> needsManualIntervention = handleInstances(awsRegion, event);
+ if (!needsManualIntervention.isEmpty())
+ submitIssue(event);
}
}
+ return true;
}
- private List<String> deprovisionHosts(String awsRegion, CloudEvent event) {
- return zonesByCloudNativeRegion.get(awsRegion)
- .stream()
- .flatMap(zone ->
- nodeRepository.list(zone.getId())
- .stream()
- .filter(shouldDeprovisionHost(event))
- .map(node -> {
- if (!node.wantToDeprovision() || !node.wantToRetire())
- log.info(String.format("Setting host %s to wantToRetire and wantToDeprovision", node.hostname().value()));
- nodeRepository.retireAndDeprovision(zone.getId(), node.hostname().value());
- return node.hostname().value();
- })
- )
- .collect(Collectors.toList());
+ /**
+ * Handles affected instances in the following way:
+ * 1. Ignore if unknown instance, presumably belongs to different system
+ * 2. Retire and deprovision if tenant host
+ * 3. Submit issue if infrastructure host, as it requires manual intervention
+ */
+ private List<Node> handleInstances(String awsRegion, CloudEvent event) {
+ List<Node> needsManualIntervention = new ArrayList<>();
+ for (var zone : zonesByCloudNativeRegion.get(awsRegion)) {
+ for (var node : nodeRepository.list(zone.getId())) {
+ if (!isAffected(node, event)){
+ continue;
+ }
+ if (node.type() == NodeType.host) {
+ log.info(String.format("Setting host %s to wantToRetire and wantToDeprovision", node.hostname().value()));
+ nodeRepository.retireAndDeprovision(zone.getId(), node.hostname().value());
+ }
+ else {
+ needsManualIntervention.add(node);
+ }
+ }
+ }
+ return needsManualIntervention;
}
- private void submitIssue(CloudEvent event, List<String> deprovisionedHosts) {
- if (event.affectedInstances.size() == deprovisionedHosts.size())
- return;
+ private void submitIssue(CloudEvent event) {
Issue issue = eventFetcher.createIssue(event);
if (!issueHandler.issueExists(issue)) {
issueHandler.file(issue);
@@ -85,11 +93,9 @@ public class CloudEventReporter extends ControllerMaintainer {
}
}
- private Predicate<Node> shouldDeprovisionHost(CloudEvent event) {
- return node ->
- node.type() == NodeType.host &&
- event.affectedInstances.stream()
- .anyMatch(instance -> node.hostname().value().contains(instance));
+ private boolean isAffected(Node node, CloudEvent event) {
+ return event.affectedInstances.stream()
+ .anyMatch(instance -> node.hostname().value().contains(instance));
}
private Map<String, List<ZoneApi>> getZonesByCloudNativeRegion() {
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 4aba8d881bf..e19f3b4f9a2 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
@@ -35,8 +35,9 @@ public class ContactInformationMaintainer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
TenantController tenants = controller().tenants();
+ boolean success = true;
for (Tenant tenant : tenants.asList()) {
log.log(INFO, "Updating contact information for " + tenant);
try {
@@ -55,11 +56,13 @@ public class ContactInformationMaintainer extends ControllerMaintainer {
throw new IllegalArgumentException("Unexpected tenant type '" + tenant.type() + "'.");
}
} catch (Exception e) {
+ success = false;
log.log(Level.WARNING, "Failed to update contact information for " + tenant + ": " +
Exceptions.toMessageString(e) + ". Retrying in " +
interval());
}
}
+ return success;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
index 2b7c78f96d0..9bf6352813a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
@@ -1,12 +1,15 @@
// Copyright 2017 Yahoo Holdings. 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.concurrent.maintenance.JobMetrics;
import com.yahoo.concurrent.maintenance.Maintainer;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
import java.time.Duration;
import java.util.EnumSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
@@ -30,7 +33,8 @@ public abstract class ControllerMaintainer extends Maintainer {
}
public ControllerMaintainer(Controller controller, Duration interval, String name, Set<SystemName> activeSystems) {
- super(name, interval, controller.clock().instant(), controller.jobControl(), controller.curator().cluster());
+ super(name, interval, controller.clock().instant(), controller.jobControl(),
+ jobMetrics(controller.metric()), controller.curator().cluster());
this.controller = controller;
this.activeSystems = Set.copyOf(Objects.requireNonNull(activeSystems));
}
@@ -43,4 +47,10 @@ public abstract class ControllerMaintainer extends Maintainer {
super.run();
}
+ private static JobMetrics jobMetrics(Metric metric) {
+ return new JobMetrics((job, consecutiveFailures) -> {
+ metric.set("maintenance.consecutiveFailures", consecutiveFailures, metric.createContext(Map.of("job", job)));
+ });
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index ca695a2d234..10b21ece233 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
@@ -69,7 +69,7 @@ public class ControllerMaintenance extends AbstractComponent {
nameServiceDispatcher = new NameServiceDispatcher(controller, Duration.ofSeconds(10));
costReportMaintainer = new CostReportMaintainer(controller, Duration.ofHours(2), controller.serviceRegistry().costReportConsumer());
resourceMeterMaintainer = new ResourceMeterMaintainer(controller, Duration.ofMinutes(1), metric, controller.serviceRegistry().meteringService());
- cloudEventReporter = new CloudEventReporter(controller, Duration.ofDays(1));
+ cloudEventReporter = new CloudEventReporter(controller, Duration.ofMinutes(30));
rotationStatusUpdater = new RotationStatusUpdater(controller, maintenanceInterval);
resourceTagMaintainer = new ResourceTagMaintainer(controller, Duration.ofMinutes(30), controller.serviceRegistry().resourceTagger());
systemRoutingPolicyMaintainer = new SystemRoutingPolicyMaintainer(controller, Duration.ofMinutes(10));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
index d028a88fb92..28b64b5bfe0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
@@ -31,9 +31,10 @@ public class CostReportMaintainer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
var csv = CostCalculator.resourceShareByPropertyToCsv(nodeRepository, controller(), clock, consumer.fixedAllocations());
consumer.consume(csv);
+ return true;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
index bb2161bca1d..7bd2c737fcb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. 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.concurrent.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -24,7 +23,8 @@ public class DeploymentExpirer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
+ boolean success = true;
for (Application application : controller().applications().readable())
for (Instance instance : application.instances().values())
for (Deployment deployment : instance.deployments().values()) {
@@ -34,11 +34,13 @@ public class DeploymentExpirer extends ControllerMaintainer {
log.log(Level.INFO, "Expiring deployment of " + instance.id() + " in " + deployment.zone());
controller().applications().deactivate(instance.id(), deployment.zone());
} catch (Exception e) {
+ success = false;
log.log(Level.WARNING, "Could not expire " + deployment + " of " + instance +
": " + Exceptions.toMessageString(e) + ". Retrying in " +
interval());
}
}
+ return success;
}
/** Returns whether given deployment has expired according to its TTL */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index 89f1e0fe840..a94e7407898 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
-import com.yahoo.concurrent.maintenance.JobControl;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.Application;
@@ -20,6 +19,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import static com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence.broken;
@@ -44,10 +44,10 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
}
@Override
- protected void maintain() {
- maintainDeploymentIssues(applications());
- maintainPlatformIssue(applications());
- escalateInactiveDeploymentIssues(applications());
+ protected boolean maintain() {
+ return maintainDeploymentIssues(applications()) &
+ maintainPlatformIssue(applications()) &
+ escalateInactiveDeploymentIssues(applications());
}
/** Returns the applications to maintain issue status for. */
@@ -62,7 +62,7 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
* and store the issue id for the filed issues. Also, clear the issueIds of applications
* where deployment has not failed for this amount of time.
*/
- private void maintainDeploymentIssues(List<Application> applications) {
+ private boolean maintainDeploymentIssues(List<Application> applications) {
List<TenantAndApplicationId> failingApplications = controller().jobController().deploymentStatuses(ApplicationList.from(applications))
.failingApplicationChangeSince(controller().clock().instant().minus(maxFailureAge))
.mapToList(status -> status.application().id());
@@ -72,6 +72,7 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
fileDeploymentIssueFor(application);
else
store(application.id(), null);
+ return true;
}
/**
@@ -79,24 +80,26 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
* applications that have been failing the upgrade to the system version for
* longer than the set grace period, or update this list if the issue already exists.
*/
- private void maintainPlatformIssue(List<Application> applications) {
+ private boolean maintainPlatformIssue(List<Application> applications) {
+ boolean success = true;
if (controller().system() == SystemName.cd)
- return;
+ return success;
Version systemVersion = controller().systemVersion();
if ((controller().versionStatus().version(systemVersion).confidence() != broken))
- return;
+ return success;
DeploymentStatusList statuses = controller().jobController().deploymentStatuses(ApplicationList.from(applications));
if (statuses.failingUpgradeToVersionSince(systemVersion, controller().clock().instant().minus(upgradeGracePeriod)).isEmpty())
- return;
+ return success;
List<ApplicationId> failingApplications = statuses.failingUpgradeToVersionSince(systemVersion, controller().clock().instant())
.mapToList(status -> status.application().id().defaultInstance());
// TODO jonmv: Send only tenant and application, here and elsewhere in this.
deploymentIssues.fileUnlessOpen(failingApplications, systemVersion);
+ return success;
}
private Tenant ownerOf(TenantAndApplicationId applicationId) {
@@ -121,7 +124,8 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
}
/** Escalate issues for which there has been no activity for a certain amount of time. */
- private void escalateInactiveDeploymentIssues(Collection<Application> applications) {
+ private boolean escalateInactiveDeploymentIssues(Collection<Application> applications) {
+ AtomicBoolean success = new AtomicBoolean(true);
applications.forEach(application -> application.deploymentIssueId().ifPresent(issueId -> {
try {
Tenant tenant = ownerOf(application.id());
@@ -130,9 +134,11 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
tenant.type() == Tenant.Type.athenz ? tenant.contact() : Optional.empty());
}
catch (RuntimeException e) {
+ success.set(false);
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
}
}));
+ return success.get();
}
private void store(TenantAndApplicationId id, IssueId issueId) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index c03be2ca1d1..c8416578932 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. 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.concurrent.maintenance.JobControl;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -39,7 +38,7 @@ public class DeploymentMetricsMaintainer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
AtomicInteger failures = new AtomicInteger(0);
AtomicInteger attempts = new AtomicInteger(0);
AtomicReference<Exception> lastException = new AtomicReference<>(null);
@@ -91,6 +90,7 @@ public class DeploymentMetricsMaintainer extends ControllerMaintainer {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
+ return lastException.get() == null;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
index 7006458538d..7952355d5fb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
@@ -35,8 +35,9 @@ public abstract class InfrastructureUpgrader<VERSION> extends ControllerMaintain
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
targetVersion().ifPresent(target -> upgradeAll(target, SystemApplication.all()));
+ return true;
}
/** Deploy a list of system applications until they converge on the given version */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
index cfe9257bdf8..e0f2f0718ef 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
@@ -48,9 +48,10 @@ public class JobRunner extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
jobs.active().forEach(this::advance);
jobs.collectGarbage();
+ return true;
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
index cc4a8c628eb..0c5ef123eef 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
@@ -68,12 +68,13 @@ public class MetricsReporter extends ControllerMaintainer {
}
@Override
- public void maintain() {
+ public boolean maintain() {
reportDeploymentMetrics();
reportRemainingRotations();
reportQueuedNameServiceRequests();
reportInfrastructureUpgradeMetrics();
reportAuditLog();
+ return true;
}
private void reportAuditLog() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
index 9febc73a5a7..e223809a211 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
@@ -38,12 +38,13 @@ public class NameServiceDispatcher extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
+ boolean success = true;
try (var lock = db.lockNameServiceQueue()) {
var queue = db.readNameServiceQueue();
var instant = clock.instant();
var remaining = queue.dispatchTo(nameService, requestCount);
- if (queue == remaining) return; // Queue unchanged
+ if (queue == remaining) return success; // Queue unchanged
var dispatched = queue.first(requestCount);
if (!dispatched.requests().isEmpty()) {
@@ -53,6 +54,7 @@ public class NameServiceDispatcher extends ControllerMaintainer {
}
db.writeNameServiceQueue(remaining);
}
+ return success;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
index a62b1745145..20febfaea1d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. 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.concurrent.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import com.yahoo.yolean.Exceptions;
@@ -19,14 +18,16 @@ public class OsVersionStatusUpdater extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
try {
OsVersionStatus newStatus = OsVersionStatus.compute(controller());
controller().updateOsVersionStatus(newStatus);
+ return true;
} catch (Exception e) {
log.log(Level.WARNING, "Failed to compute version status: " + Exceptions.toMessageString(e) +
". Retrying in " + interval());
}
+ return false;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index 5dd62251759..a032f266de5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -19,12 +19,13 @@ public class OutstandingChangeDeployer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
for (Application application : ApplicationList.from(controller().applications().readable())
.withProductionDeployment()
.withDeploymentSpec()
.asList())
controller().applications().deploymentTrigger().triggerNewRevision(application.id());
+ return true;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
index 32b65f05cac..a626f21359a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. 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.concurrent.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.Controller;
import java.time.Duration;
@@ -18,8 +17,9 @@ public class ReadyJobsTrigger extends ControllerMaintainer {
}
@Override
- public void maintain() {
+ public boolean maintain() {
controller().applications().deploymentTrigger().triggerReadyJobs();
+ return true;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index 76a186a2f6b..f460561df08 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -50,13 +50,15 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
try {
collectResourceSnapshots();
+ return true;
} catch (Exception e) {
log.log(Level.WARNING, "Failed to collect resource snapshots. Retrying in " + interval() + ". Error: " +
Exceptions.toMessageString(e));
}
+ return false;
}
private void collectResourceSnapshots() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
index 31434de472d..863302223ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
@@ -1,7 +1,6 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
-import com.yahoo.concurrent.maintenance.JobControl;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
@@ -27,7 +26,7 @@ public class ResourceTagMaintainer extends ControllerMaintainer {
}
@Override
- public void maintain() {
+ public boolean maintain() {
controller().zoneRegistry().zones()
.ofCloud(CloudName.from("aws"))
.reachable()
@@ -37,8 +36,7 @@ public class ResourceTagMaintainer extends ControllerMaintainer {
if (taggedResources > 0)
log.log(Level.INFO, "Tagged " + taggedResources + " resources in " + zone.getId());
});
-
-
+ return true;
}
private Map<HostName, ApplicationId> getTenantOfParentHosts(ZoneId zoneId) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
index 245747a882f..935bcbec597 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
@@ -41,7 +41,7 @@ public class RotationStatusUpdater extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
var failures = new AtomicInteger(0);
var attempts = new AtomicInteger(0);
var lastException = new AtomicReference<Exception>(null);
@@ -78,6 +78,7 @@ public class RotationStatusUpdater extends ControllerMaintainer {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
+ return lastException.get() == null;
}
private RotationStatus getStatus(Instance instance) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
index 0fe6f7e0bfb..3b0a1fca4af 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
@@ -21,13 +21,14 @@ public class SystemRoutingPolicyMaintainer extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
for (var zone : controller().zoneRegistry().zones().all().ids()) {
for (var application : SystemApplication.values()) {
if (!application.hasEndpoint()) continue;
controller().routing().policies().refresh(application.id(), DeploymentSpec.empty, zone);
}
}
+ return true;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
index bf44c796f34..d9c78c8a442 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
+import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
@@ -62,15 +63,12 @@ public class SystemUpgrader extends InfrastructureUpgrader<Version> {
if (application.hasApplicationPackage()) {
// For applications with package we do not have a zone-wide version target. This means that we must check
// the wanted version of each node.
+ boolean zoneHasSharedRouting = controller().zoneRegistry().routingMethods(zone.getId()).stream()
+ .anyMatch(RoutingMethod::isShared);
return minVersion(zone, application, Node::wantedVersion)
- // Upgrade if target is after any wanted version
- .map(target::isAfter)
- // Skip upgrade if there are no nodes allocated. This is overloaded to mean that the zone is not
- // expected to have a deployment of this application.
- // TODO(mpolden): Once all zones are either directly routed or not: Change this to
- // always deploy proxy app and wait for convergence in zones that are not directly
- // routed.
- .orElse(false);
+ .map(target::isAfter) // Upgrade if target is after any wanted version
+ .orElse(zoneHasSharedRouting); // Always upgrade if zone uses shared routing, but has no nodes allocated yet
+
}
return controller().serviceRegistry().configServer().nodeRepository()
.targetVersionsOf(zone.getId())
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index 5f0f2e4ba4e..9ab2b0e77e8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -51,7 +51,7 @@ public class Upgrader extends ControllerMaintainer {
* Schedule application upgrades. Note that this implementation must be idempotent.
*/
@Override
- public void maintain() {
+ public boolean maintain() {
// Determine target versions for each upgrade policy
Version canaryTarget = controller().systemVersion();
Collection<Version> defaultTargets = targetVersions(Confidence.normal);
@@ -89,6 +89,7 @@ public class Upgrader extends ControllerMaintainer {
upgrade(instances.with(UpgradePolicy.canary), canaryTarget, instances.size());
defaultTargets.forEach(target -> upgrade(instances.with(UpgradePolicy.defaultPolicy), target, numberOfApplicationsToUpgrade()));
conservativeTargets.forEach(target -> upgrade(instances.with(UpgradePolicy.conservative), target, numberOfApplicationsToUpgrade()));
+ return true;
}
/** Returns the target versions for given confidence, one per major version in the system */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
index d8b74a4ae99..a3e9672b715 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
@@ -29,7 +29,7 @@ public class VersionStatusUpdater extends ControllerMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
try {
VersionStatus newStatus = VersionStatus.compute(controller());
controller().updateVersionStatus(newStatus);
@@ -37,10 +37,12 @@ public class VersionStatusUpdater extends ControllerMaintainer {
controller().serviceRegistry().systemMonitor().reportSystemVersion(version.versionNumber(),
convert(version.confidence()));
});
+ return true;
} catch (Exception e) {
log.log(Level.WARNING, "Failed to compute version status: " + Exceptions.toMessageString(e) +
". Retrying in " + interval());
}
+ return false;
}
static SystemMonitor.Confidence convert(VespaVersion.Confidence confidence) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
index 2429c5ee8c5..2697651f61b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
@@ -95,7 +95,6 @@ public class RoutingPolicySerializer {
}
public GlobalRouting globalRoutingFromSlime(Inspector object) {
- if (!object.valid()) return GlobalRouting.DEFAULT_STATUS;
var status = GlobalRouting.Status.valueOf(object.field(statusField).asString());
var agent = GlobalRouting.Agent.valueOf(object.field(agentField).asString());
var changedAt = Serializers.optionalInstant(object.field(changedAtField)).orElse(Instant.EPOCH);
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 dc3c14c76b7..5b35a1aa5c1 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
@@ -10,7 +10,9 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
@@ -42,7 +44,6 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbi
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RestartAction;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ServiceInfo;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
@@ -54,6 +55,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringData;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
@@ -284,6 +286,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/key")) return removeDeveloperKey(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deployment")) return removeAllProdDeployments(path.get("tenant"), path.get("application"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", "all");
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", path.get("choice"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/key")) return removeDeployKey(path.get("tenant"), path.get("application"), request);
@@ -953,8 +956,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.ifPresent(version -> toSlime(version, object.setObject("revision")));
}
- private void toSlime(Endpoint endpoint, String cluster, Cursor object) {
- object.setString("cluster", cluster);
+ private void toSlime(Endpoint endpoint, Cursor object) {
+ object.setString("cluster", endpoint.cluster().value());
object.setBool("tls", endpoint.tls());
object.setString("url", endpoint.url().toString());
object.setString("scope", endpointScopeString(endpoint.scope()));
@@ -971,16 +974,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Add zone endpoints
var endpointArray = response.setArray("endpoints");
- for (var endpoint : controller.routing().endpointsOf(deploymentId).scope(Endpoint.Scope.zone)) {
- toSlime(endpoint, endpoint.name(), endpointArray.addObject());
+ for (var endpoint : controller.routing().endpointsOf(deploymentId)
+ .scope(Endpoint.Scope.zone)
+ .not().legacy()) {
+ toSlime(endpoint, endpointArray.addObject());
}
// Add global endpoints
var globalEndpoints = controller.routing().endpointsOf(application, deploymentId.applicationId().instance())
.not().legacy()
.targets(deploymentId.zoneId());
for (var endpoint : globalEndpoints) {
- // TODO(mpolden): Pass cluster name. Cluster that a global endpoint points to is not available at this level.
- toSlime(endpoint, "", endpointArray.addObject());
+ toSlime(endpoint, endpointArray.addObject());
}
response.setString("nodes", withPath("/zone/v2/" + deploymentId.zoneId().environment() + "/" + deploymentId.zoneId().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString());
@@ -1385,11 +1389,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse restart(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
ZoneId.from(environment, region));
+ RestartFilter restartFilter = new RestartFilter()
+ .withHostName(Optional.ofNullable(request.getProperty("hostname")).map(HostName::from))
+ .withClusterType(Optional.ofNullable(request.getProperty("clusterType")).map(ClusterSpec.Type::from))
+ .withClusterId(Optional.ofNullable(request.getProperty("clusterId")).map(ClusterSpec.Id::from));
- // TODO: Propagate all filters
- Optional<Hostname> hostname = Optional.ofNullable(request.getProperty("hostname")).map(Hostname::new);
- controller.applications().restart(deploymentId, hostname);
-
+ controller.applications().restart(deploymentId, restartFilter);
return new MessageResponse("Requested restart of " + deploymentId);
}
@@ -1900,12 +1905,18 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
sourceRevision,
authorEmail,
sourceUrl,
- commit,
projectId,
applicationPackage,
dataParts.get(EnvironmentResource.APPLICATION_TEST_ZIP));
}
+ private HttpResponse removeAllProdDeployments(String tenant, String application) {
+ JobControllerApiHandlerHelper.submitResponse(controller.jobController(), tenant, application,
+ Optional.empty(), Optional.empty(), Optional.empty(), 1,
+ ApplicationPackage.deploymentRemoval(), new byte[0]);
+ return new MessageResponse("All deployments removed");
+ }
+
private static Map<String, byte[]> parseDataParts(HttpRequest request) {
String contentHash = request.getHeader("x-Content-Hash");
if (contentHash == null)
@@ -1946,6 +1957,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private static String endpointScopeString(Endpoint.Scope scope) {
switch (scope) {
+ case region: return "region";
case global: return "global";
case zone: return "zone";
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index f72439b694a..0e25ee1fe85 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -525,13 +525,12 @@ class JobControllerApiHandlerHelper {
*/
static HttpResponse submitResponse(JobController jobController, String tenant, String application,
Optional<SourceRevision> sourceRevision, Optional<String> authorEmail,
- Optional<String> sourceUrl, Optional<String> commit, long projectId,
+ Optional<String> sourceUrl, long projectId,
ApplicationPackage applicationPackage, byte[] testPackage) {
ApplicationVersion version = jobController.submit(TenantAndApplicationId.from(tenant, application),
sourceRevision,
authorEmail,
sourceUrl,
- commit,
projectId,
applicationPackage,
testPackage);
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 523f3533a7f..0356e11ae36 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
@@ -7,9 +7,6 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.flags.BooleanFlag;
-import com.yahoo.vespa.flags.FetchVector;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
@@ -48,12 +45,10 @@ public class RoutingPolicies {
private final Controller controller;
private final CuratorDb db;
- private final BooleanFlag weightedDnsPerRegion;
public RoutingPolicies(Controller controller) {
this.controller = Objects.requireNonNull(controller, "controller must be non-null");
this.db = controller.curator();
- this.weightedDnsPerRegion = Flags.WEIGHTED_DNS_PER_REGION.bindTo(controller.flagSource());
try (var lock = db.lockRoutingPolicies()) { // Update serialized format
for (var policy : db.readRoutingPolicies().entrySet()) {
db.writeRoutingPolicies(policy.getKey(), policy.getValue());
@@ -92,7 +87,7 @@ public class RoutingPolicies {
removeGlobalDnsUnreferencedBy(allocation, lock);
storePoliciesOf(allocation, lock);
removePoliciesUnreferencedBy(allocation, lock);
- updateGlobalDnsOf(application, get(allocation.deployment.applicationId()).values(), inactiveZones, lock);
+ updateGlobalDnsOf(get(allocation.deployment.applicationId()).values(), inactiveZones, lock);
}
}
@@ -102,8 +97,8 @@ public class RoutingPolicies {
db.writeZoneRoutingPolicy(new ZoneRoutingPolicy(zone, GlobalRouting.status(status, GlobalRouting.Agent.operator,
controller.clock().instant())));
Map<ApplicationId, Map<RoutingPolicyId, RoutingPolicy>> allPolicies = db.readRoutingPolicies();
- for (var kv : allPolicies.entrySet()) {
- updateGlobalDnsOf(kv.getKey(), kv.getValue().values(), Set.of(), lock);
+ for (var applicationPolicies : allPolicies.values()) {
+ updateGlobalDnsOf(applicationPolicies.values(), Set.of(), lock);
}
}
}
@@ -120,56 +115,7 @@ public class RoutingPolicies {
newPolicies.put(policy.id(), newPolicy);
}
db.writeRoutingPolicies(deployment.applicationId(), newPolicies);
- updateGlobalDnsOf(deployment.applicationId(), newPolicies.values(), Set.of(), lock);
- }
- }
-
- /** Update global DNS record for given policies */
- private void legacyUpdateGlobalDnsOf(Collection<RoutingPolicy> routingPolicies, Set<ZoneId> inactiveZones, @SuppressWarnings("unused") Lock lock) {
- // Create DNS record for each routing ID
- var routingTable = routingTableFrom(routingPolicies);
- for (Map.Entry<RoutingId, List<RoutingPolicy>> routeEntry : routingTable.entrySet()) {
- var targets = new LinkedHashSet<AliasTarget>();
- var staleTargets = new LinkedHashSet<AliasTarget>();
- for (var policy : routeEntry.getValue()) {
- if (policy.dnsZone().isEmpty()) continue;
- if (!controller.zoneRegistry().routingMethods(policy.id().zone()).contains(RoutingMethod.exclusive)) continue;
- var target = new LatencyAliasTarget(policy.canonicalName(), policy.dnsZone().get(), policy.id().zone());
- var zonePolicy = db.readZoneRoutingPolicy(policy.id().zone());
- // Remove target zone if global routing status is set out at:
- // - zone level (ZoneRoutingPolicy)
- // - deployment level (RoutingPolicy)
- // - application package level (deployment.xml)
- if (isConfiguredOut(policy, zonePolicy, inactiveZones)) {
- staleTargets.add(target);
- } else {
- targets.add(target);
- }
- }
- // If all targets are configured out, all targets are set in. We do this because otherwise removing 100% of
- // the ALIAS records would cause the global endpoint to stop resolving entirely (NXDOMAIN).
- if (targets.isEmpty() && !staleTargets.isEmpty()) {
- targets.addAll(staleTargets);
- staleTargets.clear();
- }
- if (!targets.isEmpty()) {
- var endpoints = controller.routing().endpointsOf(routeEntry.getKey().application())
- .named(routeEntry.getKey().endpointId())
- .not().requiresRotation();
- endpoints.forEach(endpoint -> controller.nameServiceForwarder().createAlias(RecordName.from(endpoint.dnsName()), targets, Priority.normal));
- }
- staleTargets.forEach(t -> controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS,
- RecordData.fqdn(t.name().value()),
- Priority.normal));
- }
- }
-
- // TODO(mpolden): Remove and inline call to updateGlobalDnsOf when feature flag disappears
- private void updateGlobalDnsOf(ApplicationId application, Collection<RoutingPolicy> routingPolicies, Set<ZoneId> inactiveZones, @SuppressWarnings("unused") Lock lock) {
- if (useWeightedDnsPerRegion(application)) {
- updateGlobalDnsOf(routingPolicies, inactiveZones, lock);
- } else {
- legacyUpdateGlobalDnsOf(routingPolicies, inactiveZones, lock);
+ updateGlobalDnsOf(newPolicies.values(), Set.of(), lock);
}
}
@@ -220,7 +166,7 @@ public class RoutingPolicies {
for (var policy : policies) {
if (policy.dnsZone().isEmpty()) continue;
if (!controller.zoneRegistry().routingMethods(policy.id().zone()).contains(routingMethod)) continue;
- Endpoint weighted = policy.weightedEndpointIn(controller.system(), routingMethod);
+ Endpoint regionEndpoint = policy.regionEndpointIn(controller.system(), routingMethod);
var zonePolicy = db.readZoneRoutingPolicy(policy.id().zone());
long weight = 1;
if (isConfiguredOut(policy, zonePolicy, inactiveZones)) {
@@ -229,9 +175,9 @@ public class RoutingPolicies {
}
var weightedTarget = new WeightedAliasTarget(policy.canonicalName(), policy.dnsZone().get(),
policy.id().zone(), weight);
- endpoints.computeIfAbsent(weighted, (k) -> new RegionEndpoint(new LatencyAliasTarget(HostName.from(weighted.dnsName()),
- policy.dnsZone().get(),
- policy.id().zone())))
+ endpoints.computeIfAbsent(regionEndpoint, (k) -> new RegionEndpoint(new LatencyAliasTarget(HostName.from(regionEndpoint.dnsName()),
+ policy.dnsZone().get(),
+ policy.id().zone())))
.zoneTargets()
.add(weightedTarget);
}
@@ -245,7 +191,7 @@ public class RoutingPolicies {
var policyId = new RoutingPolicyId(loadBalancer.application(), loadBalancer.cluster(), allocation.deployment.zoneId());
var existingPolicy = policies.get(policyId);
var newPolicy = new RoutingPolicy(policyId, loadBalancer.hostname(), loadBalancer.dnsZone(),
- allocation.endpointIdsOf(loadBalancer, useWeightedDnsPerRegion(loadBalancer.application())),
+ allocation.endpointIdsOf(loadBalancer),
new Status(isActive(loadBalancer), GlobalRouting.DEFAULT_STATUS));
// Preserve global routing status for existing policy
if (existingPolicy != null) {
@@ -301,10 +247,10 @@ public class RoutingPolicies {
}
/** Compute routing IDs from given load balancers */
- private Set<RoutingId> routingIdsFrom(LoadBalancerAllocation allocation) {
+ private static Set<RoutingId> routingIdsFrom(LoadBalancerAllocation allocation) {
Set<RoutingId> routingIds = new LinkedHashSet<>();
for (var loadBalancer : allocation.loadBalancers) {
- for (var endpointId : allocation.endpointIdsOf(loadBalancer, useWeightedDnsPerRegion(loadBalancer.application()))) {
+ for (var endpointId : allocation.endpointIdsOf(loadBalancer)) {
routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
}
}
@@ -344,10 +290,6 @@ public class RoutingPolicies {
return false;
}
- private boolean useWeightedDnsPerRegion(ApplicationId application) {
- return weightedDnsPerRegion.with(FetchVector.Dimension.APPLICATION_ID, application.serializedForm()).value();
- }
-
/** Represents records for a region-wide endpoint */
private static class RegionEndpoint {
@@ -409,7 +351,7 @@ public class RoutingPolicies {
}
/** Compute all endpoint IDs for given load balancer */
- private Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer, boolean useWeightedDnsPerRegion) {
+ private Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer) {
if (!deployment.zoneId().environment().isProduction()) { // Only production deployments have configurable endpoints
return Set.of();
}
@@ -417,7 +359,7 @@ public class RoutingPolicies {
if (instanceSpec.isEmpty()) {
return Set.of();
}
- if (useWeightedDnsPerRegion && instanceSpec.get().globalServiceId().filter(id -> id.equals(loadBalancer.cluster().value())).isPresent()) {
+ if (instanceSpec.get().globalServiceId().filter(id -> id.equals(loadBalancer.cluster().value())).isPresent()) {
// Legacy assignment always has the default endpoint Id
return Set.of(EndpointId.defaultId());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
index 7f4a707949b..f87c6e2d11c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
@@ -69,17 +69,17 @@ public class RoutingPolicy {
return new RoutingPolicy(id, canonicalName, dnsZone, endpoints, status);
}
- /** Returns the endpoint of this */
+ /** Returns the zone endpoint of this */
public Endpoint endpointIn(SystemName system, RoutingMethod routingMethod, ZoneRegistry zoneRegistry) {
Optional<Endpoint> infraEndpoint = SystemApplication.matching(id.owner())
.flatMap(app -> app.endpointIn(id.zone(), zoneRegistry));
if (infraEndpoint.isPresent()) return infraEndpoint.get();
- return endpoint(routingMethod).in(system);
+ return endpoint(routingMethod).target(id.cluster(), id.zone()).in(system);
}
- /** Returns the weighted endpoint of this */
- public Endpoint weightedEndpointIn(SystemName system, RoutingMethod routingMethod) {
- return endpoint(routingMethod).weighted().in(system);
+ /** Returns the region endpoint of this */
+ public Endpoint regionEndpointIn(SystemName system, RoutingMethod routingMethod) {
+ return endpoint(routingMethod).targetRegion(id.cluster(), id.zone()).in(system);
}
@Override
@@ -104,7 +104,6 @@ public class RoutingPolicy {
private Endpoint.EndpointBuilder endpoint(RoutingMethod routingMethod) {
return Endpoint.of(id.owner())
- .target(id.cluster(), id.zone())
.on(Port.fromRoutingMethod(routingMethod))
.routingMethod(routingMethod);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 9e6eb9ca2e1..26f718ae5ff 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -18,8 +18,6 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.path.Path;
-import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -29,6 +27,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.LatencyAliasTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.WeightedAliasTarget;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
@@ -822,10 +821,16 @@ public class ControllerTest {
context.submit(applicationPackage).deploy();
var expectedRecords = List.of(
- // The 'east' global endpoint, pointing to zone 2 with exclusive routing
+ // The weighted record for zone 2's region
+ new Record(Record.Type.ALIAS,
+ RecordName.from("application.tenant.us-east-3-w.vespa.oath.cloud"),
+ new WeightedAliasTarget(HostName.from("lb-0--tenant:application:default--prod.us-east-3"),
+ "dns-zone-1", ZoneId.from("prod.us-east-3"), 1).pack()),
+
+ // The 'east' global endpoint, pointing to the weighted record for zone 2's region
new Record(Record.Type.ALIAS,
RecordName.from("east.application.tenant.global.vespa.oath.cloud"),
- new LatencyAliasTarget(HostName.from("lb-0--tenant:application:default--prod.us-east-3"),
+ new LatencyAliasTarget(HostName.from("application.tenant.us-east-3-w.vespa.oath.cloud"),
"dns-zone-1", ZoneId.from("prod.us-east-3")).pack()),
// The 'default' global endpoint, pointing to both zones with shared routing, via rotation
@@ -861,26 +866,20 @@ public class ControllerTest {
.stream()
.map(Endpoint::routingMethod)
.collect(Collectors.toSet());
- ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.ALLOW_DIRECT_ROUTING.id(), false);
- // Without everything
+ // Without satisfying any requirement
context.submit(applicationPackageBuilder.build()).deploy();
assertEquals(Set.of(RoutingMethod.shared), routingMethods.get());
- // Without Athenz service
+ // Without satisfying Athenz service requirement
context.submit(applicationPackageBuilder.compileVersion(RoutingController.DIRECT_ROUTING_MIN_VERSION).build())
.deploy();
assertEquals(Set.of(RoutingMethod.shared), routingMethods.get());
- // Without feature flag
- applicationPackageBuilder = applicationPackageBuilder.compileVersion(RoutingController.DIRECT_ROUTING_MIN_VERSION)
- .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service"));
- context.submit(applicationPackageBuilder.build()).deploy();
- assertEquals(Set.of(RoutingMethod.shared), routingMethods.get());
-
- // With everything required
- ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.ALLOW_DIRECT_ROUTING.id(), true);
- context.submit(applicationPackageBuilder.build()).deploy();
+ // Satisfying all requirements
+ context.submit(applicationPackageBuilder.compileVersion(RoutingController.DIRECT_ROUTING_MIN_VERSION)
+ .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service"))
+ .build()).deploy();
assertEquals(Set.of(RoutingMethod.shared, RoutingMethod.sharedLayer4), routingMethods.get());
// Global endpoint is configured and includes directly routed endpoint name
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageTest.java
new file mode 100644
index 00000000000..f7f0c9ce58e
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageTest.java
@@ -0,0 +1,27 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.application.api.ValidationId;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author valerijf
+ */
+public class ApplicationPackageTest {
+ @Test
+ public void test_createEmptyForDeploymentRemoval() {
+ ApplicationPackage app = ApplicationPackage.deploymentRemoval();
+ assertEquals(DeploymentSpec.empty, app.deploymentSpec());
+ assertEquals(List.of(), app.trustedCertificates());
+
+ for (ValidationId validationId : ValidationId.values()) {
+ assertTrue(app.validationOverrides().allows(validationId, Instant.now()));
+ }
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
index 2e57a5eaaa1..eb97fa0725c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
@@ -29,43 +29,43 @@ public class EndpointTest {
Map<String, Endpoint> tests = Map.of(
// Legacy endpoint
"http://a1.t1.global.vespa.yahooapis.com:4080/",
- Endpoint.of(app1).named(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main),
// Legacy endpoint with TLS
"https://a1--t1.global.vespa.yahooapis.com:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main),
// Main endpoint
"https://a1--t1.global.vespa.oath.cloud:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.main),
// Main endpoint in CD
"https://cd--a1--t1.global.vespa.oath.cloud:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd),
// Main endpoint in CD
"https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/",
- Endpoint.of(app2).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+ Endpoint.of(app2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd),
// Main endpoint with direct routing and default TLS port
"https://a1.t1.global.vespa.oath.cloud/",
- Endpoint.of(app1).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint with custom rotation name
"https://r1.a1.t1.global.vespa.oath.cloud/",
- Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint for custom instance in default rotation
"https://i2.a2.t2.global.vespa.oath.cloud/",
- Endpoint.of(app2).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint for custom instance with custom rotation name
"https://r2.i2.a2.t2.global.vespa.oath.cloud/",
- Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint in public system
"https://a1.t1.global.public.vespa.oath.cloud/",
- Endpoint.of(app1).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public)
+ Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString()));
}
@@ -77,43 +77,43 @@ public class EndpointTest {
Map<String, Endpoint> tests = Map.of(
// Legacy endpoint
"http://a1.t1.global.vespa.yahooapis.com:4080/",
- Endpoint.of(app1).named(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main),
// Legacy endpoint with TLS
"https://a1--t1.global.vespa.yahooapis.com:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main),
// Main endpoint
"https://a1--t1.global.vespa.oath.cloud:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.main),
// Main endpoint in CD
"https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/",
- Endpoint.of(app2).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+ Endpoint.of(app2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd),
// Main endpoint in CD
"https://cd--a1--t1.global.vespa.oath.cloud:4443/",
- Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+ Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd),
// Main endpoint with direct routing and default TLS port
"https://a1.t1.global.vespa.oath.cloud/",
- Endpoint.of(app1).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint with custom rotation name
"https://r1.a1.t1.global.vespa.oath.cloud/",
- Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint for custom instance in default rotation
"https://i2.a2.t2.global.vespa.oath.cloud/",
- Endpoint.of(app2).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint for custom instance with custom rotation name
"https://r2.i2.a2.t2.global.vespa.oath.cloud/",
- Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
+ Endpoint.of(app2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main),
// Main endpoint in public system
"https://a1.t1.global.public.vespa.oath.cloud/",
- Endpoint.of(app1).named(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public)
+ Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString()));
}
@@ -178,7 +178,7 @@ public class EndpointTest {
// Default rotation
"https://a1.t1.global.public.vespa.oath.cloud/",
Endpoint.of(app1)
- .named(EndpointId.defaultId())
+ .target(EndpointId.defaultId())
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public),
@@ -233,30 +233,26 @@ public class EndpointTest {
Map<String, Endpoint> tests = Map.of(
"https://a1.t1.us-north-1-w.public.vespa.oath.cloud/",
Endpoint.of(app1)
- .target(cluster, ZoneId.from("prod", "us-north-1a"))
- .weighted()
+ .targetRegion(cluster, ZoneId.from("prod", "us-north-1a"))
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public),
"https://a1.t1.us-north-2-w.public.vespa.oath.cloud/",
Endpoint.of(app1)
- .target(cluster, ZoneId.from("prod", "us-north-2"))
- .weighted()
+ .targetRegion(cluster, ZoneId.from("prod", "us-north-2"))
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public),
"https://a1.t1.us-north-2-w.test.public.vespa.oath.cloud/",
Endpoint.of(app1)
- .target(cluster, ZoneId.from("test", "us-north-2"))
- .weighted()
+ .targetRegion(cluster, ZoneId.from("test", "us-north-2"))
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString()));
Endpoint endpoint = Endpoint.of(app1)
- .target(cluster, ZoneId.from("prod", "us-north-1a"))
- .weighted()
+ .targetRegion(cluster, ZoneId.from("prod", "us-north-1a"))
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.main);
@@ -271,20 +267,20 @@ public class EndpointTest {
var tests1 = Map.of(
// With default cluster
"a1.t1.us-north-1.prod",
- Endpoint.of(app1).named(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main),
+ Endpoint.of(app1).target(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main),
// With non-default cluster
"c1.a1.t1.us-north-1.prod",
- Endpoint.of(app1).named(EndpointId.of("c1")).on(Port.tls(4443)).in(SystemName.main)
+ Endpoint.of(app1).target(EndpointId.of("c1")).on(Port.tls(4443)).in(SystemName.main)
);
var tests2 = Map.of(
// With non-default instance
"i2.a2.t2.us-north-1.prod",
- Endpoint.of(app2).named(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main),
+ Endpoint.of(app2).target(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main),
// With non-default instance and cluster
"c2.i2.a2.t2.us-north-1.prod",
- Endpoint.of(app2).named(EndpointId.of("c2")).on(Port.tls(4443)).in(SystemName.main)
+ Endpoint.of(app2).target(EndpointId.of("c2")).on(Port.tls(4443)).in(SystemName.main)
);
tests1.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(app1, zone))));
tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(app2, zone))));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index acb8cf1a2a9..d90eb715499 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -13,8 +13,6 @@ import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
-import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -113,7 +111,6 @@ public class DeploymentContext {
this.runner = tester.runner();
this.tester = tester;
createTenantAndApplication();
- ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.ALLOW_DIRECT_ROUTING.id(), true);
}
private void createTenantAndApplication() {
@@ -247,7 +244,7 @@ public class DeploymentContext {
.projectId()
.orElse(1000); // These are really set through submission, so just pick one if it hasn't been set.
lastSubmission = jobs.submit(applicationId, sourceRevision, Optional.of("a@b"), Optional.empty(),
- Optional.empty(), projectId, applicationPackage, new byte[0]);
+ projectId, applicationPackage, new byte[0]);
return this;
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
index 6a12c4457db..2e8b3e46a87 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.SlimeUtils;
@@ -12,7 +11,6 @@ import com.yahoo.vespa.hosted.controller.application.EndpointId;
import org.junit.Test;
import java.io.IOException;
-import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
@@ -33,7 +31,7 @@ public class TestConfigSerializerTest {
JobType.systemTest,
true,
Map.of(zone, List.of(Endpoint.of(ApplicationId.defaultId())
- .named(EndpointId.of("ai"))
+ .target(EndpointId.of("ai"))
.on(Endpoint.Port.tls())
.in(SystemName.main))),
Map.of(zone, List.of("facts")));
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 fff63e1954e..251a5ce9acb 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
@@ -19,8 +19,6 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeploymentData
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
-import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
@@ -33,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.NotFoundEx
import com.yahoo.vespa.hosted.controller.api.integration.configserver.PrepareResponse;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
@@ -402,8 +401,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public void restart(DeploymentId deployment, Optional<Hostname> hostname) {
- nodeRepository().requestRestart(deployment, hostname.map(Identifier::id).map(HostName::from));
+ public void restart(DeploymentId deployment, RestartFilter restartFilter) {
+ nodeRepository().requestRestart(deployment, restartFilter.getHostName());
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
index 1151fdd07f0..6a2feba1d47 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
@@ -2,7 +2,9 @@
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.ControllerTester;
+import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
import org.junit.Before;
import org.junit.Test;
@@ -27,19 +29,47 @@ public class ControllerMaintainerTest {
@Test
public void only_runs_in_permitted_systems() {
AtomicInteger executions = new AtomicInteger();
- maintainerIn(SystemName.cd, executions).run();
- maintainerIn(SystemName.main, executions).run();
+ new TestControllerMaintainer(tester.controller(), SystemName.cd, executions).run();
+ new TestControllerMaintainer(tester.controller(), SystemName.main, executions).run();
assertEquals(1, executions.get());
}
- private ControllerMaintainer maintainerIn(SystemName system, AtomicInteger executions) {
- return new ControllerMaintainer(tester.controller(), Duration.ofDays(1),
- "MockMaintainer", EnumSet.of(system)) {
- @Override
- protected void maintain() {
- executions.incrementAndGet();
- }
- };
+ @Test
+ public void records_metric() {
+ TestControllerMaintainer maintainer = new TestControllerMaintainer(tester.controller(), SystemName.main, new AtomicInteger());
+ maintainer.run();
+ assertEquals(0L, consecutiveFailuresMetric());
+ maintainer.success = false;
+ maintainer.run();
+ maintainer.run();
+ assertEquals(2L, consecutiveFailuresMetric());
+ maintainer.success = true;
+ maintainer.run();;
+ assertEquals(0, consecutiveFailuresMetric());
+ }
+
+ private long consecutiveFailuresMetric() {
+ MetricsMock metrics = (MetricsMock) tester.controller().metric();
+ return metrics.getMetric((context) -> "TestControllerMaintainer".equals(context.get("job")),
+ "maintenance.consecutiveFailures").get().longValue();
+ }
+
+ private static class TestControllerMaintainer extends ControllerMaintainer {
+
+ private final AtomicInteger executions;
+ private boolean success = true;
+
+ public TestControllerMaintainer(Controller controller, SystemName system, AtomicInteger executions) {
+ super(controller, Duration.ofDays(1), null, EnumSet.of(system));
+ this.executions = executions;
+ }
+
+ @Override
+ protected boolean maintain() {
+ executions.incrementAndGet();
+ return success;
+ }
+
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index fe33d728b7c..88eab642a60 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -91,7 +91,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
try {
@@ -122,7 +122,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
Supplier<Run> run = () -> jobs.last(id, systemTest).get();
jobs.start(id, systemTest, versions);
@@ -229,7 +229,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
RunId runId = new RunId(id, systemTest, 1);
jobs.start(id, systemTest, versions);
@@ -266,7 +266,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId instanceId = appId.defaultInstance();
JobId jobId = new JobId(instanceId, systemTest);
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
assertFalse(jobs.lastSuccess(jobId).isPresent());
for (int i = 0; i < jobs.historyLength(); i++) {
@@ -361,7 +361,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
tester.clock().advance(JobRunner.jobTimeout.plus(Duration.ofSeconds(1)));
@@ -378,7 +378,7 @@ public class JobRunnerTest {
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
- jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
for (RunStatus status : RunStatus.values()) {
if (status == success) continue; // Status not used for steps.
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
index d5c26408b23..98412b8147b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
@@ -275,11 +275,12 @@ public class SystemUpgraderTest {
}
@Test
- public void does_not_deploy_proxy_app_in_zones_without_proxy() {
+ public void does_not_deploy_proxy_app_in_zone_without_shared_routing() {
var applications = List.of(SystemApplication.configServerHost, SystemApplication.configServer,
SystemApplication.tenantHost);
tester.configServer().bootstrap(List.of(zone1.getId()), applications);
tester.configServer().disallowConvergenceCheck(SystemApplication.proxy.id());
+ tester.zoneRegistry().exclusiveRoutingIn(zone1);
var systemUpgrader = systemUpgrader(UpgradePolicy.create().upgrade(zone1));
// System begins upgrade
@@ -382,8 +383,7 @@ public class SystemUpgraderTest {
private SystemUpgrader systemUpgrader(UpgradePolicy upgradePolicy) {
tester.zoneRegistry().setUpgradePolicy(upgradePolicy);
- return new SystemUpgrader(tester.controller(), Duration.ofDays(1)
- );
+ return new SystemUpgrader(tester.controller(), Duration.ofDays(1));
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
index 7ca964e06dd..d67e9c3e432 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
@@ -6,7 +6,6 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
@@ -16,7 +15,6 @@ import org.junit.Test;
import java.time.Instant;
import java.util.Iterator;
-import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -63,16 +61,4 @@ public class RoutingPolicySerializerTest {
}
}
- // TODO(mpolden): Remove after January 2020
- @Test
- public void legacy_serialization() {
- var json = "{\"routingPolicies\":[{\"cluster\":\"default\",\"zone\":\"prod.us-north-1\",\"canonicalName\":\"lb-host\",\"dnsZone\":\"dnsZoneId\",\"rotations\":[\"default\"],\"active\":true}]}";
- var owner = ApplicationId.defaultId();
- var serialized = serializer.fromSlime(owner, SlimeUtils.jsonToSlime(json));
- var id = new RoutingPolicyId(owner, ClusterSpec.Id.from("default"), ZoneId.from("prod", "us-north-1"));
- var expected = Map.of(id, new RoutingPolicy(id, HostName.from("lb-host"), Optional.of("dnsZoneId"),
- Set.of(EndpointId.defaultId()), new Status(true, GlobalRouting.DEFAULT_STATUS)));
- assertEquals(expected, serialized);
- }
-
}
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 388ca65dc40..10682218353 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
@@ -70,11 +70,9 @@ import org.junit.Before;
import org.junit.Test;
import java.io.File;
-import java.math.BigDecimal;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
-import java.time.YearMonth;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
@@ -84,7 +82,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.TreeSet;
import java.util.function.Supplier;
import static com.yahoo.application.container.handler.Request.Method.DELETE;
@@ -975,6 +972,36 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
@Test
+ public void testRemovingAllDeployments() {
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
+ .region("us-west-1")
+ .region("us-east-3")
+ .region("eu-west-1")
+ .endpoint("eu", "default", "eu-west-1")
+ .endpoint("default", "default", "us-west-1", "us-east-3")
+ .build();
+
+ deploymentTester.controllerTester().createTenant("tenant1", ATHENZ_TENANT_DOMAIN.getName(), 432L);
+
+ // Create tenant and deploy
+ var app = deploymentTester.newDeploymentContext("tenant1", "application1", "instance1");
+ app.submit(applicationPackage).deploy();
+ tester.controller().jobController().deploy(app.instanceId(), JobType.devUsEast1, Optional.empty(), applicationPackage);
+
+ assertEquals(Set.of(ZoneId.from("prod.us-west-1"), ZoneId.from("prod.us-east-3"), ZoneId.from("prod.eu-west-1"), ZoneId.from("dev.us-east-1")),
+ app.instance().deployments().keySet());
+
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deployment", DELETE)
+ .userIdentity(USER_ID)
+ .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
+ "{\"message\":\"All deployments removed\"}");
+
+ assertEquals(Set.of(ZoneId.from("dev.us-east-1")), app.instance().deployments().keySet());
+ }
+
+ @Test
public void testErrorResponses() throws Exception {
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
index 928525a20d1..c74092c4ae8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
@@ -13,7 +13,7 @@
"routingMethod": "shared"
},
{
- "cluster": "",
+ "cluster": "foo",
"tls": true,
"url": "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/",
"scope": "global",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
index 4ffe809297d..7d2def6c479 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
@@ -16,7 +16,7 @@
"routingMethod": "shared"
},
{
- "cluster": "",
+ "cluster": "foo",
"tls": true,
"url": "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/",
"scope": "global",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesLegacyTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesLegacyTest.java
deleted file mode 100644
index d5a50d98c8d..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesLegacyTest.java
+++ /dev/null
@@ -1,711 +0,0 @@
-// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.routing;
-
-import com.google.common.collect.Sets;
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.application.api.ValidationId;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.AthenzDomain;
-import com.yahoo.config.provision.AthenzService;
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.zone.RoutingMethod;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.vespa.hosted.controller.Instance;
-import com.yahoo.vespa.hosted.controller.RoutingController;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.Endpoint;
-import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.SystemApplication;
-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.ServiceRegistryMock;
-import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher;
-import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
-import org.junit.Test;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author mortent
- * @author mpolden
- */
-// TODO(mpolden): Remove when weighted-dns-per-region flag is removed
-public class RoutingPoliciesLegacyTest {
-
- private final ZoneId zone1 = ZoneId.from("prod", "us-west-1");
- private final ZoneId zone2 = ZoneId.from("prod", "us-central-1");
- private final ZoneId zone3 = ZoneId.from("prod", "us-east-3");
-
- private final ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
- .region(zone2.region())
- .build();
-
- @Test
- public void global_routing_policies() {
- var tester = new RoutingPoliciesTester();
- var context1 = tester.newDeploymentContext("tenant1", "app1", "default");
- var context2 = tester.newDeploymentContext("tenant1", "app2", "default");
- int clustersPerZone = 2;
- int numberOfDeployments = 2;
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("r0", "c0")
- .endpoint("r1", "c0", "us-west-1")
- .endpoint("r2", "c1")
- .build();
- tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone1, zone2);
-
- // Creates alias records
- context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0, zone1);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 1, zone1, zone2);
- assertEquals("Routing policy count is equal to cluster count",
- numberOfDeployments * clustersPerZone,
- tester.policiesOf(context1.instance().id()).size());
-
- // Applications gains a new deployment
- ApplicationPackage applicationPackage2 = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .region(zone3.region())
- .endpoint("r0", "c0")
- .endpoint("r1", "c0", "us-west-1")
- .endpoint("r2", "c1")
- .build();
- numberOfDeployments++;
- tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone3);
- context1.submit(applicationPackage2).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
-
- // Endpoints are updated to contain cluster in new deployment
- tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0, zone1, zone2, zone3);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0, zone1);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 1, zone1, zone2, zone3);
-
- // Another application is deployed with a single cluster and global endpoint
- var endpoint4 = "r0.app2.tenant1.global.vespa.oath.cloud";
- tester.provisionLoadBalancers(1, context2.instanceId(), zone1, zone2);
- var applicationPackage3 = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("r0", "c0")
- .build();
- context2.submit(applicationPackage3).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context2.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
-
- // All endpoints for app1 are removed
- ApplicationPackage applicationPackage4 = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .region(zone3.region())
- .allow(ValidationId.globalEndpointChange)
- .build();
- context1.submit(applicationPackage4).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0);
- tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 0);
- var policies = tester.policiesOf(context1.instanceId());
- assertEquals(clustersPerZone * numberOfDeployments, policies.size());
- assertTrue("Rotation membership is removed from all policies",
- policies.stream().allMatch(policy -> policy.endpoints().isEmpty()));
- assertEquals("Rotations for " + context2.application() + " are not removed", 2, tester.aliasDataOf(endpoint4).size());
- }
-
- @Test
- public void zone_routing_policies() {
- zone_routing_policies(false);
- zone_routing_policies(true);
- }
-
- private void zone_routing_policies(boolean sharedRoutingLayer) {
- var tester = new RoutingPoliciesTester();
- var context1 = tester.newDeploymentContext("tenant1", "app1", "default");
- var context2 = tester.newDeploymentContext("tenant1", "app2", "default");
-
- // Deploy application
- int clustersPerZone = 2;
- tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), sharedRoutingLayer, zone1, zone2);
- context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
-
- // Deployment creates records and policies for all clusters in all zones
- Set<String> expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c0.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-central-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords, tester.recordNames());
- assertEquals(4, tester.policiesOf(context1.instanceId()).size());
-
- // Next deploy does nothing
- context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- assertEquals(expectedRecords, tester.recordNames());
- assertEquals(4, tester.policiesOf(context1.instanceId()).size());
-
- // Add 1 cluster in each zone and deploy
- tester.provisionLoadBalancers(clustersPerZone + 1, context1.instanceId(), sharedRoutingLayer, zone1, zone2);
- context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c2.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c0.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c2.app1.tenant1.us-central-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords, tester.recordNames());
- assertEquals(6, tester.policiesOf(context1.instanceId()).size());
-
- // Deploy another application
- tester.provisionLoadBalancers(clustersPerZone, context2.instanceId(), sharedRoutingLayer, zone1, zone2);
- context2.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c2.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c0.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c2.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c0.app2.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app2.tenant1.us-central-1.vespa.oath.cloud",
- "c0.app2.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app2.tenant1.us-west-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords.stream().sorted().collect(Collectors.toList()), tester.recordNames().stream().sorted().collect(Collectors.toList()));
- assertEquals(4, tester.policiesOf(context2.instanceId()).size());
-
- // Deploy removes cluster from app1
- tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), sharedRoutingLayer, zone1, zone2);
- context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c0.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c0.app2.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app2.tenant1.us-central-1.vespa.oath.cloud",
- "c0.app2.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app2.tenant1.us-west-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords, tester.recordNames());
-
- // Remove app2 completely
- tester.controllerTester().controller().applications().requireInstance(context2.instanceId()).deployments().keySet()
- .forEach(zone -> {
- tester.controllerTester().configServer().removeLoadBalancers(context2.instanceId(), zone);
- tester.controllerTester().controller().applications().deactivate(context2.instanceId(), zone);
- });
- context2.flushDnsUpdates();
- expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-west-1.vespa.oath.cloud",
- "c0.app1.tenant1.us-central-1.vespa.oath.cloud",
- "c1.app1.tenant1.us-central-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords, tester.recordNames());
- assertTrue("Removes stale routing policies " + context2.application(), tester.routingPolicies().get(context2.instanceId()).isEmpty());
- assertEquals("Keeps routing policies for " + context1.application(), 4, tester.routingPolicies().get(context1.instanceId()).size());
- }
-
- @Test
- public void global_routing_policies_in_rotationless_system() {
- var tester = new RoutingPoliciesTester(new DeploymentTester(new ControllerTester(new RotationsConfig.Builder().build())));
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
- tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
-
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region().value())
- .endpoint("r0", "c0")
- .build();
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
-
- var endpoint = "r0.app1.tenant1.global.vespa.oath.cloud";
- assertEquals(endpoint + " points to c0 in all regions",
- List.of("latency/lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint));
- assertTrue("No rotations assigned", context.application().instances().values().stream()
- .map(Instance::rotations)
- .allMatch(List::isEmpty));
- }
-
- @Test
- public void manual_deployment_creates_routing_policy() {
- // Empty application package is valid in manually deployed environments
- var tester = new RoutingPoliciesTester();
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
- var emptyApplicationPackage = new ApplicationPackageBuilder().build();
- var zone = ZoneId.from("dev", "us-east-1");
- var zoneApi = ZoneApiMock.from(zone.environment(), zone.region());
- tester.controllerTester().serviceRegistry().zoneRegistry()
- .setZones(zoneApi)
- .exclusiveRoutingIn(zoneApi);
-
- // Deploy to dev
- tester.controllerTester().controller().applications().deploy(context.instanceId(), zone, Optional.of(emptyApplicationPackage), DeployOptions.none());
- assertEquals("DeploymentSpec is not persisted", DeploymentSpec.empty, context.application().deploymentSpec());
- context.flushDnsUpdates();
-
- // Routing policy is created and DNS is updated
- assertEquals(1, tester.policiesOf(context.instanceId()).size());
- assertEquals(Set.of("app1.tenant1.us-east-1.dev.vespa.oath.cloud"), tester.recordNames());
- }
-
- @Test
- public void manual_deployment_creates_routing_policy_with_non_empty_spec() {
- // Initial deployment
- var tester = new RoutingPoliciesTester();
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
- context.submit(applicationPackage).deploy();
- var zone = ZoneId.from("dev", "us-east-1");
- var zoneApi = ZoneApiMock.from(zone.environment(), zone.region());
- tester.controllerTester().serviceRegistry().zoneRegistry()
- .setZones(zoneApi)
- .exclusiveRoutingIn(zoneApi);
- var prodRecords = Set.of("app1.tenant1.us-central-1.vespa.oath.cloud", "app1.tenant1.us-west-1.vespa.oath.cloud");
- assertEquals(prodRecords, tester.recordNames());
-
- // Deploy to dev under different instance
- var devInstance = context.application().id().instance("user");
- tester.controllerTester().controller().applications().deploy(devInstance, zone, Optional.of(applicationPackage), DeployOptions.none());
- assertEquals("DeploymentSpec is persisted", applicationPackage.deploymentSpec(), context.application().deploymentSpec());
- context.flushDnsUpdates();
-
- // Routing policy is created and DNS is updated
- assertEquals(1, tester.policiesOf(devInstance).size());
- assertEquals(Sets.union(prodRecords, Set.of("user.app1.tenant1.us-east-1.dev.vespa.oath.cloud")), tester.recordNames());
- }
-
- @Test
- public void reprovisioning_load_balancer_preserves_cname_record() {
- var tester = new RoutingPoliciesTester();
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
-
- // Initial load balancer is provisioned
- tester.provisionLoadBalancers(1, context.instanceId(), zone1);
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region())
- .build();
-
- // Application is deployed
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- var expectedRecords = Set.of(
- "c0.app1.tenant1.us-west-1.vespa.oath.cloud"
- );
- assertEquals(expectedRecords, tester.recordNames());
- assertEquals(1, tester.policiesOf(context.instanceId()).size());
-
- // Application is removed and the load balancer is deprovisioned
- tester.controllerTester().controller().applications().deactivate(context.instanceId(), zone1);
- tester.controllerTester().configServer().removeLoadBalancers(context.instanceId(), zone1);
-
- // Load balancer for the same application is provisioned again, but with a different hostname
- var newHostname = HostName.from("new-hostname");
- var loadBalancer = new LoadBalancer("LB-0-Z-" + zone1.value(),
- context.instanceId(),
- ClusterSpec.Id.from("c0"),
- newHostname,
- LoadBalancer.State.active,
- Optional.of("dns-zone-1"));
- tester.controllerTester().configServer().putLoadBalancers(zone1, List.of(loadBalancer));
-
- // Application redeployment preserves DNS record
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- assertEquals(expectedRecords, tester.recordNames());
- assertEquals(1, tester.policiesOf(context.instanceId()).size());
- assertEquals("CNAME points to current load blancer", newHostname.value() + ".",
- tester.cnameDataOf(expectedRecords.iterator().next()).get(0));
- }
-
- @Test
- public void set_global_endpoint_status() {
- var tester = new RoutingPoliciesTester();
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
-
- // Provision load balancers and deploy application
- tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
- .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
- .build();
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
-
- // Global DNS record is created
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
-
- // Global routing status is overridden in one zone
- var changedAt = tester.controllerTester().clock().instant();
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.out,
- GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
-
- // Inactive zone is removed from global DNS record
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone2);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone2);
-
- // Status details is stored in policy
- var policy1 = tester.routingPolicies().get(context.deploymentIdIn(zone1)).values().iterator().next();
- assertEquals(GlobalRouting.Status.out, policy1.status().globalRouting().status());
- assertEquals(GlobalRouting.Agent.tenant, policy1.status().globalRouting().agent());
- assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), policy1.status().globalRouting().changedAt());
-
- // Other zone remains in
- var policy2 = tester.routingPolicies().get(context.deploymentIdIn(zone2)).values().iterator().next();
- assertEquals(GlobalRouting.Status.in, policy2.status().globalRouting().status());
- assertEquals(GlobalRouting.Agent.system, policy2.status().globalRouting().agent());
- assertEquals(Instant.EPOCH, policy2.status().globalRouting().changedAt());
-
- // Next deployment does not affect status
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone2);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone2);
-
- // Deployment is set back in
- tester.controllerTester().clock().advance(Duration.ofHours(1));
- changedAt = tester.controllerTester().clock().instant();
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.in, GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
-
- policy1 = tester.routingPolicies().get(context.deploymentIdIn(zone1)).values().iterator().next();
- assertEquals(GlobalRouting.Status.in, policy1.status().globalRouting().status());
- assertEquals(GlobalRouting.Agent.tenant, policy1.status().globalRouting().agent());
- assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), policy1.status().globalRouting().changedAt());
-
- // Deployment is set out through a new deployment.xml
- var applicationPackage2 = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region(), false)
- .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
- .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
- .build();
- context.submit(applicationPackage2).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1);
-
- // ... back in
- var applicationPackage3 = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
- .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
- .build();
- context.submit(applicationPackage3).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
- tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
- }
-
- @Test
- public void set_zone_global_endpoint_status() {
- var tester = new RoutingPoliciesTester();
- var context1 = tester.newDeploymentContext("tenant1", "app1", "default");
- var context2 = tester.newDeploymentContext("tenant2", "app2", "default");
- var contexts = List.of(context1, context2);
-
- // Deploy applications
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("default", "c0", zone1.region().value(), zone2.region().value())
- .build();
- for (var context : contexts) {
- tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- tester.assertTargets(context.instanceId(), EndpointId.defaultId(), 0, zone1, zone2);
- }
-
- // Set zone out
- tester.routingPolicies().setGlobalRoutingStatus(zone2, GlobalRouting.Status.out);
- context1.flushDnsUpdates();
- tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
- tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1);
- for (var context : contexts) {
- var policies = tester.routingPolicies().get(context.instanceId());
- assertTrue("Global routing status for policy remains " + GlobalRouting.Status.in,
- policies.values().stream()
- .map(RoutingPolicy::status)
- .map(Status::globalRouting)
- .map(GlobalRouting::status)
- .allMatch(status -> status == GlobalRouting.Status.in));
- }
- var changedAt = tester.controllerTester().clock().instant();
- var zonePolicy = tester.controllerTester().controller().curator().readZoneRoutingPolicy(zone2);
- assertEquals(GlobalRouting.Status.out, zonePolicy.globalRouting().status());
- assertEquals(GlobalRouting.Agent.operator, zonePolicy.globalRouting().agent());
- assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), zonePolicy.globalRouting().changedAt());
-
- // Setting status per deployment does not affect status as entire zone is out
- tester.routingPolicies().setGlobalRoutingStatus(context1.deploymentIdIn(zone2), GlobalRouting.Status.in, GlobalRouting.Agent.tenant);
- context1.flushDnsUpdates();
- tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
- tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1);
-
- // Set single deployment out
- tester.routingPolicies().setGlobalRoutingStatus(context1.deploymentIdIn(zone2), GlobalRouting.Status.out, GlobalRouting.Agent.tenant);
- context1.flushDnsUpdates();
-
- // Set zone back in. Deployment set explicitly out, remains out, the rest are in
- tester.routingPolicies().setGlobalRoutingStatus(zone2, GlobalRouting.Status.in);
- context1.flushDnsUpdates();
- tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
- tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1, zone2);
- }
-
- @Test
- public void non_production_deployment_is_not_registered_in_global_endpoint() {
- var tester = new RoutingPoliciesTester(SystemName.Public);
-
- // Configure the system to use the same region for test, staging and prod
- var sharedRegion = RegionName.from("aws-us-east-1c");
- var prodZone = ZoneId.from(Environment.prod, sharedRegion);
- var stagingZone = ZoneId.from(Environment.staging, sharedRegion);
- var testZone = ZoneId.from(Environment.test, sharedRegion);
- var zones = List.of(ZoneApiMock.from(prodZone),
- ZoneApiMock.from(stagingZone),
- ZoneApiMock.from(testZone));
- tester.controllerTester().zoneRegistry()
- .setZones(zones)
- .setRoutingMethod(zones, RoutingMethod.exclusive);
- tester.controllerTester().configServer().bootstrap(List.of(prodZone, stagingZone, testZone),
- SystemApplication.all());
-
- var context = tester.tester.newDeploymentContext();
- var endpointId = EndpointId.of("r0");
- var applicationPackage = applicationPackageBuilder()
- .trustDefaultCertificate()
- .region(sharedRegion)
- .endpoint(endpointId.id(), "default")
- .build();
-
- // Application starts deployment
- context = context.submit(applicationPackage);
- for (var testJob : List.of(JobType.systemTest, JobType.stagingTest)) {
- context = context.runJob(testJob);
- // Since runJob implicitly tears down the deployment and immediately deletes DNS records associated with the
- // deployment, we consume only one DNS update at a time here
- do {
- context = context.flushDnsUpdates(1);
- tester.assertTargets(context.instanceId(), endpointId, 0);
- } while (!tester.recordNames().isEmpty());
- }
-
- // Deployment completes
- context.completeRollout();
- tester.assertTargets(context.instanceId(), endpointId, 0, prodZone);
- }
-
- @Test
- public void changing_global_routing_status_never_removes_all_members() {
- var tester = new RoutingPoliciesTester();
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
-
- // Provision load balancers and deploy application
- tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
- var applicationPackage = applicationPackageBuilder()
- .region(zone1.region())
- .region(zone2.region())
- .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
- .build();
- context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
-
- // Global DNS record is created, pointing to all configured zones
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
-
- // Global routing status is overridden for one deployment
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.out,
- GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone2);
-
- // Setting other deployment out implicitly sets all deployments in
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone2), GlobalRouting.Status.out,
- GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
-
- // One inactive deployment is put back in. Global DNS record now points to the only active deployment
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.in,
- GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1);
-
- // Setting zone (containing active deployment) out puts all deployments in
- tester.routingPolicies().setGlobalRoutingStatus(zone1, GlobalRouting.Status.out);
- context.flushDnsUpdates();
- assertEquals(GlobalRouting.Status.out, tester.routingPolicies().get(zone1).globalRouting().status());
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
-
- // Setting zone back in removes the currently inactive deployment
- tester.routingPolicies().setGlobalRoutingStatus(zone1, GlobalRouting.Status.in);
- context.flushDnsUpdates();
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1);
-
- // Inactive deployment is set in
- tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone2), GlobalRouting.Status.in,
- GlobalRouting.Agent.tenant);
- context.flushDnsUpdates();
- for (var policy : tester.routingPolicies().get(context.instanceId()).values()) {
- assertSame(GlobalRouting.Status.in, policy.status().globalRouting().status());
- }
- tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
- }
-
- @Test
- public void config_server_routing_policy() {
- var tester = new RoutingPoliciesTester();
- var app = SystemApplication.configServer.id();
- RecordName name = RecordName.from("cfg.prod.us-west-1.test.vip");
-
- tester.provisionLoadBalancers(1, app, zone1);
- tester.routingPolicies().refresh(app, DeploymentSpec.empty, zone1);
- new NameServiceDispatcher(tester.tester.controller(), Duration.ofDays(1), Integer.MAX_VALUE).run();
-
- List<Record> records = tester.controllerTester().nameService().findRecords(Record.Type.CNAME, name);
- assertEquals(1, records.size());
- assertEquals(RecordData.from("lb-0--hosted-vespa:zone-config-servers:default--prod.us-west-1."),
- records.get(0).data());
- }
-
- /** Returns an application package builder that satisfies requirements for a directly routed endpoint */
- private static ApplicationPackageBuilder applicationPackageBuilder() {
- return new ApplicationPackageBuilder()
- .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service"))
- .compileVersion(RoutingController.DIRECT_ROUTING_MIN_VERSION);
- }
-
- private static List<LoadBalancer> createLoadBalancers(ZoneId zone, ApplicationId application, boolean shared, int count) {
- List<LoadBalancer> loadBalancers = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- HostName lbHostname;
- if (shared) {
- lbHostname = HostName.from("shared-lb--" + zone.value());
- } else {
- lbHostname = HostName.from("lb-" + i + "--" + application.serializedForm() +
- "--" + zone.value());
- }
- loadBalancers.add(
- new LoadBalancer("LB-" + i + "-Z-" + zone.value(),
- application,
- ClusterSpec.Id.from("c" + i),
- lbHostname,
- LoadBalancer.State.active,
- Optional.of("dns-zone-1")));
- }
- return loadBalancers;
- }
-
- private static class RoutingPoliciesTester {
-
- private final DeploymentTester tester;
-
- public RoutingPoliciesTester() {
- this(SystemName.main);
- }
-
- public RoutingPoliciesTester(SystemName system) {
- this(new DeploymentTester(new ControllerTester(new ServiceRegistryMock(system))));
- }
-
- public RoutingPolicies routingPolicies() {
- return tester.controllerTester().controller().routing().policies();
- }
-
- public DeploymentContext newDeploymentContext(String tenant, String application, String instance) {
- return tester.newDeploymentContext(tenant, application, instance);
- }
-
- public ControllerTester controllerTester() {
- return tester.controllerTester();
- }
-
- public RoutingPoliciesTester(DeploymentTester tester) {
- this.tester = tester;
- // Make all zones directly routed
- tester.controllerTester().zoneRegistry().exclusiveRoutingIn(tester.controllerTester().zoneRegistry().zones().all().zones());
- }
-
- private void provisionLoadBalancers(int clustersPerZone, ApplicationId application, boolean shared, ZoneId... zones) {
- for (ZoneId zone : zones) {
- tester.configServer().removeLoadBalancers(application, zone);
- tester.configServer().putLoadBalancers(zone, createLoadBalancers(zone, application, shared, clustersPerZone));
- }
- }
-
- private void provisionLoadBalancers(int clustersPerZone, ApplicationId application, ZoneId... zones) {
- provisionLoadBalancers(clustersPerZone, application, false, zones);
- }
-
- private Collection<RoutingPolicy> policiesOf(ApplicationId instance) {
- return tester.controller().curator().readRoutingPolicies(instance).values();
- }
-
- private Set<String> recordNames() {
- return tester.controllerTester().nameService().records().stream()
- .map(Record::name)
- .map(RecordName::asString)
- .collect(Collectors.toSet());
- }
-
- private List<String> aliasDataOf(String name) {
- return tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from(name)).stream()
- .map(Record::data)
- .map(RecordData::asString)
- .collect(Collectors.toList());
- }
-
- private List<String> cnameDataOf(String name) {
- return tester.controllerTester().nameService().findRecords(Record.Type.CNAME, RecordName.from(name)).stream()
- .map(Record::data)
- .map(RecordData::asString)
- .collect(Collectors.toList());
- }
-
- private void assertTargets(ApplicationId application, EndpointId endpointId, int loadBalancerId, ZoneId ...zone) {
- var endpoint = tester.controller().routing().endpointsOf(application)
- .named(endpointId)
- .targets(List.of(zone))
- .primary()
- .map(Endpoint::dnsName)
- .orElse("<none>");
- var zoneTargets = Arrays.stream(zone)
- .map(z -> "latency/lb-" + loadBalancerId + "--" + application.serializedForm() + "--" +
- z.value() + "/dns-zone-1/" + z.value())
- .collect(Collectors.toSet());
- assertEquals("Global endpoint " + endpoint + " points to expected zones", zoneTargets,
- Set.copyOf(aliasDataOf(endpoint)));
- }
-
- }
-
-}
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 6679fc112a3..fab61eeaec3 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
@@ -16,8 +16,6 @@ import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.RoutingController;
@@ -749,7 +747,6 @@ public class RoutingPoliciesTest {
}
tester.controllerTester().configServer().bootstrap(tester.controllerTester().zoneRegistry().zones().all().ids(),
SystemApplication.all());
- ((InMemoryFlagSource) tester.controllerTester().controller().flagSource()).withBooleanFlag(Flags.WEIGHTED_DNS_PER_REGION.id(), true);
}
private void provisionLoadBalancers(int clustersPerZone, ApplicationId application, boolean shared, ZoneId... zones) {
@@ -788,16 +785,16 @@ public class RoutingPoliciesTest {
.collect(Collectors.toList());
}
- private void assertTargets(ApplicationId application, EndpointId endpointId, ClusterSpec.Id clusterId, int loadBalancerId, Map<ZoneId, Long> zoneWeights) {
+ private void assertTargets(ApplicationId application, EndpointId endpointId, ClusterSpec.Id cluster, int loadBalancerId, Map<ZoneId, Long> zoneWeights) {
Set<String> latencyTargets = new HashSet<>();
Map<String, List<ZoneId>> zonesByRegionEndpoint = new HashMap<>();
for (var zone : zoneWeights.keySet()) {
- Endpoint weighted = tester.controller().routing().endpointsOf(new DeploymentId(application, zone))
- .scope(Endpoint.Scope.weighted)
- .named(EndpointId.of(clusterId.value()))
- .asList()
- .get(0);
- zonesByRegionEndpoint.computeIfAbsent(weighted.dnsName(), (k) -> new ArrayList<>())
+ Endpoint regionEndpoint = tester.controller().routing().endpointsOf(new DeploymentId(application, zone))
+ .scope(Endpoint.Scope.region)
+ .cluster(cluster)
+ .asList()
+ .get(0);
+ zonesByRegionEndpoint.computeIfAbsent(regionEndpoint.dnsName(), (k) -> new ArrayList<>())
.add(zone);
}
zonesByRegionEndpoint.forEach((regionEndpoint, zonesInRegion) -> {
@@ -806,7 +803,7 @@ public class RoutingPoliciesTest {
application.serializedForm() + "--" + z.value() +
"/dns-zone-1/" + z.value() + "/" + zoneWeights.get(z))
.collect(Collectors.toSet());
- assertEquals("Weighted endpoint " + regionEndpoint + " points to load balancer",
+ assertEquals("Region endpoint " + regionEndpoint + " points to load balancer",
weightedTargets,
aliasDataOf(regionEndpoint));
ZoneId zone = zonesInRegion.get(0);
diff --git a/dist/vespa.spec b/dist/vespa.spec
index 59b7121e9d2..e49bd74e545 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -56,10 +56,12 @@ BuildRequires: cmake3
BuildRequires: llvm7.0-devel
BuildRequires: vespa-boost-devel >= 1.59.0-6
BuildRequires: vespa-gtest >= 1.8.1-1
-BuildRequires: vespa-protobuf-devel >= 3.7.0-4
+BuildRequires: vespa-icu-devel >= 65.1.0-1
+BuildRequires: vespa-lz4-devel >= 1.9.2-2
BuildRequires: vespa-onnxruntime-devel >= 1.3.0-1
BuildRequires: vespa-openssl-devel >= 1.1.1g-1
-BuildRequires: vespa-icu-devel >= 65.1.0-1
+BuildRequires: vespa-protobuf-devel >= 3.7.0-4
+BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%endif
%if 0%{?el8}
BuildRequires: cmake >= 3.11.4-3
@@ -67,14 +69,18 @@ BuildRequires: llvm-devel >= 9.0.1
BuildRequires: boost-devel >= 1.66
BuildRequires: openssl-devel
BuildRequires: vespa-gtest >= 1.8.1-1
-BuildRequires: vespa-protobuf-devel >= 3.7.0-4
+BuildRequires: vespa-lz4-devel >= 1.9.2-2
BuildRequires: vespa-onnxruntime-devel >= 1.3.0-1
+BuildRequires: vespa-protobuf-devel >= 3.7.0-4
+BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%endif
%if 0%{?fedora}
BuildRequires: cmake >= 3.9.1
BuildRequires: maven
-BuildRequires: vespa-onnxruntime-devel >= 1.3.0-1
BuildRequires: openssl-devel
+BuildRequires: vespa-lz4-devel >= 1.9.2-2
+BuildRequires: vespa-onnxruntime-devel >= 1.3.0-1
+BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%if 0%{?fc31}
BuildRequires: vespa-protobuf-devel >= 3.7.0-4
BuildRequires: llvm-devel >= 9.0.0
@@ -99,8 +105,6 @@ BuildRequires: gmock-devel
%endif
BuildRequires: xxhash-devel >= 0.7.3
BuildRequires: openblas-devel
-BuildRequires: lz4-devel
-BuildRequires: libzstd-devel
BuildRequires: zlib-devel
BuildRequires: re2-devel
%if ! 0%{?el7}
@@ -146,8 +150,6 @@ Requires: openblas
%else
Requires: openblas-serial
%endif
-Requires: lz4
-Requires: libzstd
Requires: zlib
Requires: re2
%if ! 0%{?el7}
@@ -158,12 +160,14 @@ Requires: gdb
Requires: net-tools
%if 0%{?el7}
Requires: llvm7.0
+Requires: vespa-icu >= 65.1.0-1
+Requires: vespa-lz4 >= 1.9.2-2
Requires: vespa-onnxruntime >= 1.3.0-1
Requires: vespa-openssl >= 1.1.1g-1
-Requires: vespa-icu >= 65.1.0-1
Requires: vespa-protobuf >= 3.7.0-4
Requires: vespa-telegraf >= 1.1.1-1
Requires: vespa-valgrind >= 3.16.0-1
+Requires: vespa-zstd >= 1.4.5-2
%define _vespa_llvm_version 7
%define _extra_link_directory /usr/lib64/llvm7.0/lib;%{_vespa_deps_prefix}/lib64
%define _extra_include_directory /usr/include/llvm7.0;%{_vespa_deps_prefix}/include;/usr/include/openblas
@@ -171,15 +175,19 @@ Requires: vespa-valgrind >= 3.16.0-1
%if 0%{?el8}
Requires: llvm-libs >= 9.0.1
%define _vespa_llvm_version 9
-Requires: vespa-protobuf >= 3.7.0-4
-Requires: vespa-onnxruntime >= 1.3.0-1
Requires: openssl-libs
+Requires: vespa-lz4 >= 1.9.2-2
+Requires: vespa-onnxruntime >= 1.3.0-1
+Requires: vespa-protobuf >= 3.7.0-4
+Requires: vespa-zstd >= 1.4.5-2
%define _extra_link_directory %{_vespa_deps_prefix}/lib64
%define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas
%endif
%if 0%{?fedora}
-Requires: vespa-onnxruntime >= 1.3.0-1
Requires: openssl-libs
+Requires: vespa-lz4 >= 1.9.2-2
+Requires: vespa-onnxruntime >= 1.3.0-1
+Requires: vespa-zstd >= 1.4.5-2
%if 0%{?fc31}
Requires: vespa-protobuf >= 3.7.0-4
Requires: llvm-libs >= 9.0.0
@@ -207,8 +215,8 @@ Requires: %{name}-malloc = %{version}-%{release}
Requires: %{name}-tools = %{version}-%{release}
# Ugly workaround because vespamalloc/src/vespamalloc/malloc/mmap.cpp uses the private
-# _dl_sym function.
-%global __requires_exclude ^libc\\.so\\.6\\(GLIBC_PRIVATE\\)\\(64bit\\)$
+# _dl_sym function. Exclude automated reqires for libraries in /opt/vespa-deps/lib64.
+%global __requires_exclude ^lib(c\\.so\\.6\\(GLIBC_PRIVATE\\)|(crypto|icui18n|icuuc|lz4|protobuf|ssl|zstd)\\.so\\.[0-9.]*\\((OPENSSL_1_1_0)?\\))\\(64bit\\)$
%description
@@ -233,13 +241,13 @@ Vespa - The open big data serving engine - base
Summary: Vespa - The open big data serving engine - base C++ libs
Requires: xxhash-libs >= 0.7.3
-Requires: lz4
-Requires: libzstd
%if 0%{?el7}
Requires: vespa-openssl >= 1.1.1g-1
%else
Requires: openssl-libs
%endif
+Requires: vespa-lz4 >= 1.9.2-2
+Requires: vespa-libzstd >= 1.4.5-2
%description base-libs
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 d5063a42605..cc3cb3adc85 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -71,7 +71,8 @@ public class Flags {
public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag(
"disabled-host-admin-tasks", List.of(), String.class,
- "List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask) that should be skipped",
+ "List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask), or some node-agent " +
+ "functionality (see NodeAgentTask), that should be skipped",
"Takes effect on next host admin tick",
HOSTNAME, NODE_TYPE);
@@ -264,12 +265,6 @@ public class Flags {
"Takes effect on redeploy",
ZONE_ID, APPLICATION_ID);
- public static final UnboundBooleanFlag ALLOW_DIRECT_ROUTING = defineFeatureFlag(
- "publish-direct-routing-endpoint", true,
- "Whether an application should receive a directly routed endpoint in its endpoint list",
- "Takes effect immediately",
- APPLICATION_ID);
-
public static final UnboundBooleanFlag NLB_PROXY_PROTOCOL = defineFeatureFlag(
"nlb-proxy-protocol", false,
"Configure NLB to use proxy protocol",
@@ -321,7 +316,7 @@ public class Flags {
);
public static final UnboundBooleanFlag WEIGHTED_DNS_PER_REGION = defineFeatureFlag(
- "weighted-dns-per-region", false,
+ "weighted-dns-per-region", true,
"Whether to create weighted DNS records per region in global endpoints",
"Takes effect on next deployment through controller",
APPLICATION_ID
@@ -345,6 +340,13 @@ public class Flags {
"Takes effect on next tick"
);
+ public static final UnboundBooleanFlag USE_CONFIG_SERVER_LOCK = defineFeatureFlag(
+ "use-config-server-lock",
+ false,
+ "Whether the node-repository should take the same application lock as the config server when making changes to nodes",
+ "Takes effect on config server restart"
+ );
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
diff --git a/fnet/src/tests/examples/examples_test.cpp b/fnet/src/tests/examples/examples_test.cpp
index c704c58abc9..8f13aca0898 100644
--- a/fnet/src/tests/examples/examples_test.cpp
+++ b/fnet/src/tests/examples/examples_test.cpp
@@ -1,6 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/util/thread.h>
#include <atomic>
@@ -10,9 +10,9 @@
static const int PORT0 = 18570;
static const int PORT1 = 18571;
-using vespalib::SlaveProc;
+using vespalib::ChildProcess;
-bool runProc(SlaveProc &proc, std::atomic<bool> &done) {
+bool runProc(ChildProcess &proc, std::atomic<bool> &done) {
char buf[4096];
proc.close(); // close stdin
while (proc.running() && !done) {
@@ -38,7 +38,7 @@ bool runProc(const std::string &cmd) {
vespalib::Thread::sleep(500);
}
std::atomic<bool> done(false);
- SlaveProc proc(cmd.c_str());
+ ChildProcess proc(cmd.c_str());
ok = runProc(proc, done);
}
return ok;
@@ -47,60 +47,60 @@ bool runProc(const std::string &cmd) {
TEST("usage") {
std::atomic<bool> done(false);
{
- SlaveProc proc("exec ../../examples/proxy/fnet_proxy_app");
+ ChildProcess proc("exec ../../examples/proxy/fnet_proxy_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/ping/fnet_pingserver_app");
+ ChildProcess proc("exec ../../examples/ping/fnet_pingserver_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/ping/fnet_pingclient_app");
+ ChildProcess proc("exec ../../examples/ping/fnet_pingclient_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_client_app");
+ ChildProcess proc("exec ../../examples/frt/rpc/fnet_rpc_client_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_server_app");
+ ChildProcess proc("exec ../../examples/frt/rpc/fnet_rpc_server_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/fnet_echo_client_app");
+ ChildProcess proc("exec ../../examples/frt/rpc/fnet_echo_client_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/vespa-rpc-info");
+ ChildProcess proc("exec ../../examples/frt/rpc/vespa-rpc-info");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/vespa-rpc-invoke-bin");
+ ChildProcess proc("exec ../../examples/frt/rpc/vespa-rpc-invoke-bin");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app");
+ ChildProcess proc("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_callback_client_app");
+ ChildProcess proc("exec ../../examples/frt/rpc/fnet_rpc_callback_client_app");
EXPECT_FALSE(runProc(proc, done));
}
{
- SlaveProc proc("exec ../../examples/frt/rpc/vespa-rpc-proxy");
+ ChildProcess proc("exec ../../examples/frt/rpc/vespa-rpc-proxy");
EXPECT_FALSE(runProc(proc, done));
}
}
TEST("timeout") {
std::string out;
- EXPECT_TRUE(SlaveProc::run("exec ../../examples/timeout/fnet_timeout_app", out));
+ EXPECT_TRUE(ChildProcess::run("exec ../../examples/timeout/fnet_timeout_app", out));
fprintf(stderr, "%s\n", out.c_str());
}
TEST_MT_F("ping", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -114,7 +114,7 @@ TEST_MT_F("ping", 2, std::atomic<bool>()) {
TEST_MT_F("ping times out", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -128,12 +128,12 @@ TEST_MT_F("ping times out", 2, std::atomic<bool>()) {
TEST_MT_F("ping with proxy", 3, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
} else if (thread_id == 1) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d",
PORT1, PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -147,7 +147,7 @@ TEST_MT_F("ping with proxy", 3, std::atomic<bool>()) {
TEST_MT_F("rpc client server", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -161,7 +161,7 @@ TEST_MT_F("rpc client server", 2, std::atomic<bool>()) {
TEST_MT_F("rpc echo client", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -175,7 +175,7 @@ TEST_MT_F("rpc echo client", 2, std::atomic<bool>()) {
TEST_MT_F("rpc info", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -191,7 +191,7 @@ TEST_MT_F("rpc info", 2, std::atomic<bool>()) {
TEST_MT_F("rpc invoke", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -206,7 +206,7 @@ TEST_MT_F("rpc invoke", 2, std::atomic<bool>()) {
TEST_MT_F("rpc callback client server", 2, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
@@ -220,12 +220,12 @@ TEST_MT_F("rpc callback client server", 2, std::atomic<bool>()) {
TEST_MT_F("rpc callback client server with proxy", 3, std::atomic<bool>()) {
if (thread_id == 0) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
} else if (thread_id == 1) {
- SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/vespa-rpc-proxy tcp/%d tcp/localhost:%d",
+ ChildProcess proc(vespalib::make_string("exec ../../examples/frt/rpc/vespa-rpc-proxy tcp/%d tcp/localhost:%d",
PORT1, PORT0).c_str());
TEST_BARRIER();
EXPECT_TRUE(runProc(proc, f1));
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java b/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
index b1aceb81bc6..12168663205 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
@@ -113,4 +113,11 @@ public interface OsgiFramework {
*/
void stop() throws BundleException;
+ /**
+ * Returns true if this is a Felix based framework and not e.g. a test framework.
+ */
+ default boolean isFelixFramework() {
+ return false;
+ }
+
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
index c14e513fb98..bd189f8b898 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
@@ -169,6 +169,11 @@ public class FelixFramework implements OsgiFramework {
collisionHook.allowDuplicateBundles(bundles);
}
+ @Override
+ public boolean isFelixFramework() {
+ return true;
+ }
+
private void installBundle(String bundleLocation, Set<String> mask, List<Bundle> out) throws BundleException {
bundleLocation = BundleLocationResolver.resolve(bundleLocation);
if (mask.contains(bundleLocation)) {
diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml
index bd8c77bc9cc..b140e66f28a 100644
--- a/jdisc_http_service/pom.xml
+++ b/jdisc_http_service/pom.xml
@@ -140,6 +140,7 @@
<artifactId>bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
+ <buildLegacyVespaPlatformBundle>true</buildLegacyVespaPlatformBundle>
<discPreInstallBundle>
javax.servlet-api-3.1.0.jar,
jetty-continuation-${jetty.version}.jar,
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java
index f5f1eb5791d..f8a6f47f946 100644
--- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java
@@ -5,10 +5,9 @@ package ai.vespa.metricsproxy.http.application;
import ai.vespa.metricsproxy.core.MetricsConsumers;
import ai.vespa.metricsproxy.http.TextResponse;
import ai.vespa.metricsproxy.metric.model.ConsumerId;
+import ai.vespa.metricsproxy.metric.model.DimensionId;
import ai.vespa.metricsproxy.metric.model.MetricsPacket;
import ai.vespa.metricsproxy.metric.model.json.GenericJsonModel;
-import ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil;
-import ai.vespa.metricsproxy.metric.model.prometheus.PrometheusUtil;
import com.google.inject.Inject;
import com.yahoo.container.handler.metrics.ErrorResponse;
import com.yahoo.container.handler.metrics.HttpHandlerBase;
@@ -25,7 +24,6 @@ import java.util.stream.Collectors;
import static ai.vespa.metricsproxy.http.ValuesFetcher.getConsumerOrDefault;
import static ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil.toGenericApplicationModel;
-import static ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil.toGenericJsonModel;
import static ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil.toMetricsPackets;
import static ai.vespa.metricsproxy.metric.model.prometheus.PrometheusUtil.toPrometheusModel;
import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR;
@@ -84,7 +82,9 @@ public class ApplicationMetricsHandler extends HttpHandlerBase {
List<GenericJsonModel> genericNodes = toGenericApplicationModel(metricsByNode).nodes;
List<MetricsPacket> metricsForAllNodes = genericNodes.stream()
- .flatMap(element -> toMetricsPackets(element).stream()
+ .flatMap(element -> toMetricsPackets(element)
+ .stream()
+ .map(builder -> builder.putDimension(DimensionId.toDimensionId("hostname"), element.hostname))
.map(MetricsPacket.Builder::build))
.collect(Collectors.toList());
return new TextResponse(200, toPrometheusModel(metricsForAllNodes).serialize());
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandlerTest.java
index cc6b6b36057..0fa6fea7d11 100644
--- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandlerTest.java
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandlerTest.java
@@ -11,6 +11,8 @@ import ai.vespa.metricsproxy.metric.model.json.GenericService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.yahoo.container.jdisc.RequestHandlerTestDriver;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
@@ -62,6 +64,8 @@ public class ApplicationMetricsHandlerTest {
private static final String MOCK_METRICS_PATH = "/node0";
+ private static final Pattern PROMETHEUS_REGEX_FORMAT = Pattern.compile("[a-z_]+([{]([A-Za-z_]+=\"[A-Za-z.\\-\\/0.9_]+\",)*[}])?( [0-9E]+(\\.[0-9E]+)?){2}");
+
private int port;
private static RequestHandlerTestDriver testDriver;
@@ -112,13 +116,20 @@ public class ApplicationMetricsHandlerTest {
@Ignore
@Test
- public void visually_inspect_values_response() throws Exception {
+ public void visually_inspect_values_response_metrics() throws Exception {
String response = testDriver.sendRequest(METRICS_VALUES_URI).readAll();
ObjectMapper mapper = createObjectMapper();
var jsonModel = mapper.readValue(response, GenericApplicationModel.class);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonModel));
}
+ @Ignore
+ @Test
+ public void visually_inspect_values_response_prometheus() throws Exception {
+ String response = testDriver.sendRequest(PROMETHEUS_VALUES_URI).readAll();
+ System.out.println(response);
+ }
+
@Test
public void response_contains_node() {
GenericApplicationModel jsonModel = getResponseAsJsonModel(DEFAULT_PUBLIC_CONSUMER_ID.id);
@@ -132,6 +143,23 @@ public class ApplicationMetricsHandlerTest {
}
@Test
+ public void prometheus_response_contains_hostname() {
+ String response = testDriver.sendRequest(PROMETHEUS_VALUES_URI).readAll();
+ Arrays.stream(response.split("\n"))
+ .filter(line -> line.contains("{"))
+ .forEach(line -> assertTrue(line.contains("hostname")));
+ }
+
+ @Test
+ public void prometheus_response_obeys_format() {
+ String response = testDriver.sendRequest(PROMETHEUS_VALUES_URI).readAll();
+ Arrays.stream(response.split("\n"))
+ .filter(line -> !line.startsWith("#"))
+ .filter(line -> !line.contains("{"))
+ .forEach(line -> assertTrue(PROMETHEUS_REGEX_FORMAT.matcher(line).find()));
+ }
+
+ @Test
public void response_contains_services_with_metrics() {
GenericApplicationModel jsonModel = getResponseAsJsonModel(DEFAULT_PUBLIC_CONSUMER_ID.id);
diff --git a/metrics/src/vespa/metrics/metricmanager.cpp b/metrics/src/vespa/metrics/metricmanager.cpp
index 7ac912416de..84e92c2c316 100644
--- a/metrics/src/vespa/metrics/metricmanager.cpp
+++ b/metrics/src/vespa/metrics/metricmanager.cpp
@@ -94,6 +94,9 @@ MetricManager::~MetricManager()
void
MetricManager::stop()
{
+ if (!running()) {
+ return; // Let stop() be idempotent.
+ }
Runnable::stop();
{
vespalib::MonitorGuard sync(_waiter);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
index 30ca2e0d218..a5efec1dcb7 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
@@ -5,7 +5,6 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.NodeType;
-import java.util.logging.Level;
import com.yahoo.vespa.hosted.dockerapi.Container;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
@@ -16,8 +15,9 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.disk.DiskCleanupRule;
import com.yahoo.vespa.hosted.node.admin.maintenance.disk.LinearCleanupRule;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask;
import com.yahoo.vespa.hosted.node.admin.task.util.file.DiskSize;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder;
import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
import com.yahoo.vespa.hosted.node.admin.task.util.process.Terminal;
@@ -37,8 +37,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
+import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.node.admin.maintenance.disk.DiskCleanupRule.Priority;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -107,6 +107,8 @@ public class StorageMaintainer {
}
public boolean cleanDiskIfFull(NodeAgentContext context) {
+ if (context.isDisabled(NodeAgentTask.DiskCleanup)) return false;
+
double totalBytes = context.node().diskSize().bytes();
// Delete enough bytes to get below 70% disk usage, but only if we are already using more than 80% disk
long bytesToRemove = diskUsageFor(context)
@@ -148,6 +150,7 @@ public class StorageMaintainer {
/** Checks if container has any new coredumps, reports and archives them if so */
public void handleCoreDumpsForContainer(NodeAgentContext context, Optional<Container> container) {
+ if (context.isDisabled(NodeAgentTask.CoreDumps)) return;
coredumpHandler.converge(context, () -> getCoredumpNodeAttributes(context, container));
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
index 360cea8a60d..fe6b29402b5 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
@@ -2,9 +2,9 @@
package com.yahoo.vespa.hosted.node.admin.maintenance.acl;
import com.google.common.net.InetAddresses;
-import java.util.logging.Level;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask;
import com.yahoo.vespa.hosted.node.admin.task.util.file.Editor;
import com.yahoo.vespa.hosted.node.admin.task.util.file.LineEditor;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPAddresses;
@@ -18,6 +18,7 @@ import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
+import java.util.logging.Level;
import java.util.logging.Logger;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -51,6 +52,8 @@ public class AclMaintainer {
// ip(6)tables operate while having the xtables lock, run with synchronized to prevent multiple NodeAgents
// invoking ip(6)tables concurrently.
public synchronized void converge(NodeAgentContext context) {
+ if (context.isDisabled(NodeAgentTask.AclMaintainer)) return;
+
// Apply acl to the filter table
editFlushOnError(context, IPVersion.IPv4, "filter", FilterTableLineEditor.from(context.acl(), IPVersion.IPv4));
editFlushOnError(context, IPVersion.IPv6, "filter", FilterTableLineEditor.from(context.acl(), IPVersion.IPv6));
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
index 3320851a36c..d6c08a820cd 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.identity;
-import java.util.logging.Level;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyStoreType;
import com.yahoo.security.KeyUtils;
@@ -24,6 +23,7 @@ import com.yahoo.vespa.athenz.utils.SiaUtils;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask;
import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder;
import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
@@ -46,6 +46,7 @@ import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -108,6 +109,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
}
public boolean converge(NodeAgentContext context) {
+ if (context.isDisabled(NodeAgentTask.CredentialsMaintainer)) return false;
+
try {
context.log(logger, Level.FINE, "Checking certificate");
Path containerSiaDirectory = context.pathOnHostFromPathInNode(CONTAINER_SIA_DIRECTORY);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
index d589000c07e..872b8a8096b 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
@@ -44,6 +44,10 @@ public interface NodeAgentContext extends TaskContext {
String vespaUserOnHost();
+ default boolean isDisabled(NodeAgentTask task) {
+ return false;
+ };
+
/**
* The vcpu value in NodeSpec is multiplied by the speedup factor per cpu core compared to a historical baseline
* for a particular cpu generation of the host (see flavors.def cpuSpeedup).
@@ -52,7 +56,7 @@ public interface NodeAgentContext extends TaskContext {
*/
double unscaledVcpu();
- /** The file system used by the NodeAgentContext. All paths must have the same provider. */
+ /** The file system used by the NodeAgentContext. All paths must have the same provider. */
FileSystem fileSystem();
/**
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
index 9f0c8d47d64..c7c0675c30e 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
@@ -7,6 +7,10 @@ import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.FlagSource;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.Acl;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
@@ -18,6 +22,7 @@ import java.nio.file.Path;
import java.nio.file.ProviderMismatchException;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -40,10 +45,11 @@ public class NodeAgentContextImpl implements NodeAgentContext {
private final String vespaUser;
private final String vespaUserOnHost;
private final double cpuSpeedup;
+ private final Set<NodeAgentTask> disabledNodeAgentTasks;
public NodeAgentContextImpl(NodeSpec node, Acl acl, AthenzIdentity identity,
DockerNetworking dockerNetworking, ZoneApi zone,
- FileSystem fileSystem,
+ FileSystem fileSystem, FlagSource flagSource,
Path pathToContainerStorage, Path pathToVespaHome,
String vespaUser, String vespaUserOnHost, double cpuSpeedup) {
if (cpuSpeedup <= 0)
@@ -55,13 +61,15 @@ public class NodeAgentContextImpl implements NodeAgentContext {
this.identity = Objects.requireNonNull(identity);
this.dockerNetworking = Objects.requireNonNull(dockerNetworking);
this.zone = Objects.requireNonNull(zone);
- this.fileSystem = fileSystem;
+ this.fileSystem = Objects.requireNonNull(fileSystem);
this.pathToNodeRootOnHost = requireValidPath(pathToContainerStorage).resolve(containerName.asString());
this.pathToVespaHome = requireValidPath(pathToVespaHome);
this.logPrefix = containerName.asString() + ": ";
this.vespaUser = vespaUser;
this.vespaUserOnHost = vespaUserOnHost;
this.cpuSpeedup = cpuSpeedup;
+ this.disabledNodeAgentTasks = NodeAgentTask.fromString(
+ Flags.DISABLED_HOST_ADMIN_TASKS.bindTo(flagSource).with(FetchVector.Dimension.HOSTNAME, node.hostname()).value());
}
@Override
@@ -105,6 +113,11 @@ public class NodeAgentContextImpl implements NodeAgentContext {
}
@Override
+ public boolean isDisabled(NodeAgentTask task) {
+ return disabledNodeAgentTasks.contains(task);
+ }
+
+ @Override
public double unscaledVcpu() {
return node.vcpu() / cpuSpeedup;
}
@@ -212,6 +225,7 @@ public class NodeAgentContextImpl implements NodeAgentContext {
private String vespaUser;
private String vespaUserOnHost;
private FileSystem fileSystem = FileSystems.getDefault();
+ private FlagSource flagSource;
private double cpuSpeedUp = 1;
public Builder(NodeSpec node) {
@@ -268,6 +282,11 @@ public class NodeAgentContextImpl implements NodeAgentContext {
return this;
}
+ public Builder flagSource(FlagSource flagSource) {
+ this.flagSource = flagSource;
+ return this;
+ }
+
public Builder cpuSpeedUp(double cpuSpeedUp) {
this.cpuSpeedUp = cpuSpeedUp;
return this;
@@ -301,6 +320,7 @@ public class NodeAgentContextImpl implements NodeAgentContext {
}
}),
fileSystem,
+ Optional.ofNullable(flagSource).orElseGet(InMemoryFlagSource::new),
fileSystem.getPath("/home/docker/container-storage"),
fileSystem.getPath("/opt/vespa"),
Optional.ofNullable(vespaUser).orElse("vespa"),
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java
new file mode 100644
index 00000000000..d57c680e190
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java
@@ -0,0 +1,30 @@
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public enum NodeAgentTask {
+
+ // The full task name is prefixed with 'node>', e.g. 'node>DiskCleanup'
+ DiskCleanup,
+ CoreDumps,
+ CredentialsMaintainer,
+ AclMaintainer;
+
+ private static final Map<String, NodeAgentTask> tasksByName = Arrays.stream(NodeAgentTask.values())
+ .collect(Collectors.toUnmodifiableMap(NodeAgentTask::taskName, n -> n));
+
+ private final String taskName;
+ NodeAgentTask() {
+ this.taskName = "node>" + name();
+ }
+
+ public String taskName() { return taskName; }
+
+ public static Set<NodeAgentTask> fromString(List<String> tasks) {
+ return tasks.stream().filter(tasksByName::containsKey).map(tasksByName::get).collect(Collectors.toUnmodifiableSet());
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java
index 9bcbce849af..b7e0a2a1d97 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java
@@ -2,14 +2,19 @@
package com.yahoo.vespa.hosted.node.admin.nodeagent;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.Test;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* @author freva
@@ -84,6 +89,23 @@ public class NodeAgentContextImplTest {
assertRewrite("docker.tld/vespa/hosted:1.2.3", "/opt/vespa/log", "/opt/vespa/log");
}
+ @Test
+ public void disabledTasksTest() {
+ NodeAgentContext context1 = createContextWithDisabledTasks();
+ assertFalse(context1.isDisabled(NodeAgentTask.DiskCleanup));
+ assertFalse(context1.isDisabled(NodeAgentTask.CoreDumps));
+
+ NodeAgentContext context2 = createContextWithDisabledTasks("root>UpgradeTask", "DiskCleanup", "node>CoreDumps");
+ assertFalse(context2.isDisabled(NodeAgentTask.DiskCleanup));
+ assertTrue(context2.isDisabled(NodeAgentTask.CoreDumps));
+ }
+
+ private static NodeAgentContext createContextWithDisabledTasks(String... tasks) {
+ InMemoryFlagSource flagSource = new InMemoryFlagSource();
+ flagSource.withListFlag(Flags.DISABLED_HOST_ADMIN_TASKS.id(), List.of(tasks), String.class);
+ return new NodeAgentContextImpl.Builder("node123").flagSource(flagSource).build();
+ }
+
private static void assertRewrite(String dockerImage, String path, String expected) {
NodeAgentContext context = new NodeAgentContextImpl.Builder("node123")
.nodeSpecBuilder(ns -> ns.wantedDockerImage(DockerImage.fromString(dockerImage)))
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 be4f551ca29..e146583ae04 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
@@ -94,15 +94,6 @@ public final class Node {
throw new IllegalArgumentException("Only hosts can be reserved to a tenant");
}
- /** Returns the IP addresses of this node */
- // TODO: Remove and make callers access this through ipConfig()
- public Set<String> ipAddresses() { return ipConfig.primary(); }
-
- /** Returns the IP address pool available on this node. These IP addresses are available for use by containers
- * running on this node */
- // TODO: Remove and make callers access this through ipConfig()
- public IP.Pool ipAddressPool() { return ipConfig.pool(); }
-
/** Returns the IP config of this node */
public IP.Config ipConfig() { return ipConfig; }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
index 99b6c48c90b..7bf76b7c2c2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
@@ -18,6 +18,7 @@ import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.provision.Node.State;
import com.yahoo.vespa.hosted.provision.applications.Applications;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
@@ -46,6 +47,7 @@ import com.yahoo.vespa.hosted.provision.restapi.NotFoundException;
import java.time.Clock;
import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -58,6 +60,8 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -92,6 +96,8 @@ import java.util.stream.Stream;
// Nodes might have an application assigned in dirty.
public class NodeRepository extends AbstractComponent {
+ private static final Logger log = Logger.getLogger(NodeRepository.class.getName());
+
private final CuratorDatabaseClient db;
private final Clock clock;
private final Zone zone;
@@ -106,6 +112,7 @@ public class NodeRepository extends AbstractComponent {
private final Applications applications;
private final boolean canProvisionHosts;
private final int spareCount;
+ private final boolean useConfigServerLock;
/**
* Creates a node repository from a zookeeper provider.
@@ -146,7 +153,9 @@ public class NodeRepository extends AbstractComponent {
boolean useCuratorClientCache,
boolean canProvisionHosts,
int spareCount) {
- this.db = new CuratorDatabaseClient(flavors, curator, clock, zone, useCuratorClientCache);
+ // Flag is read once here as it shouldn't not change at runtime
+ this.useConfigServerLock = Flags.USE_CONFIG_SERVER_LOCK.bindTo(flagSource).value();
+ this.db = new CuratorDatabaseClient(flavors, curator, clock, zone, useCuratorClientCache, useConfigServerLock);
this.zone = zone;
this.clock = clock;
this.flavors = flavors;
@@ -160,13 +169,33 @@ public class NodeRepository extends AbstractComponent {
this.applications = new Applications(db);
this.canProvisionHosts = canProvisionHosts;
this.spareCount = spareCount;
-
- // read and write all nodes to make sure they are stored in the latest version of the serialized format
- for (State state : State.values())
- // TODO(mpolden): Add per-node locking. In its current state this may collide with other callers making
- // node state changes. Example: A redeployment on another config server which moves a node
- // to another state while this is constructed.
- db.writeTo(state, db.readNodes(state), Agent.system, Optional.empty());
+ rewriteNodes();
+ }
+
+ /** Read and write all nodes to make sure they are stored in the latest version of the serialized format */
+ private void rewriteNodes() {
+ Instant start = clock.instant();
+ int nodesWritten = 0;
+ if (useConfigServerLock) {
+ for (var state : State.values()) {
+ for (var node : db.readNodes(state)) {
+ try (var lock = lock(node)) {
+ var currentNode = db.readNode(node.hostname());
+ if (currentNode.isEmpty()) continue; // Node disappeared while running this loop
+ db.writeTo(currentNode.get().state(), currentNode.get(), Agent.system, Optional.empty());
+ nodesWritten++;
+ }
+ }
+ }
+ } else {
+ for (State state : State.values()) {
+ List<Node> nodes = db.readNodes(state);
+ db.writeTo(state, nodes, Agent.system, Optional.empty());
+ nodesWritten += nodes.size();
+ }
+ }
+ Instant end = clock.instant();
+ log.log(Level.INFO, String.format("Rewrote %d nodes in %s", nodesWritten, Duration.between(start, end)));
}
/** Returns the curator database client used by this */
@@ -829,10 +858,14 @@ public class NodeRepository extends AbstractComponent {
public Zone zone() { return zone; }
/** Create a lock which provides exclusive rights to making changes to the given application */
- public Mutex lock(ApplicationId application) { return db.legacyLock(application); }
+ public Mutex lock(ApplicationId application) {
+ return useConfigServerLock ? db.lock(application) : db.legacyLock(application);
+ }
/** Create a lock with a timeout which provides exclusive rights to making changes to the given application */
- public Mutex lock(ApplicationId application, Duration timeout) { return db.legacyLock(application, timeout); }
+ public Mutex lock(ApplicationId application, Duration timeout) {
+ return useConfigServerLock ? db.lock(application, timeout) : db.legacyLock(application, timeout);
+ }
/** Create a lock which provides exclusive rights to modifying unallocated nodes */
public Mutex lockUnallocated() { return db.lockInactive(); }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerId.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerId.java
index 1431f21de47..3630cf4c1e4 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerId.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerId.java
@@ -50,6 +50,11 @@ public class LoadBalancerId {
return Objects.hash(application, cluster);
}
+ @Override
+ public String toString() {
+ return "load balancer " + serializedForm;
+ }
+
/** Create an instance from a serialized value on the form tenant:application:instance:cluster-id */
public static LoadBalancerId fromSerializedForm(String value) {
int lastSeparator = value.lastIndexOf(":");
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 bc4381573c6..559dbe63cba 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
@@ -43,7 +43,7 @@ public class SharedLoadBalancerService implements LoadBalancerService {
var firstProxyNode = proxyNodes.get(0);
var networkNames = proxyNodes.stream()
- .flatMap(node -> node.ipAddresses().stream())
+ .flatMap(node -> node.ipConfig().primary().stream())
.map(SharedLoadBalancerService::withPrefixLength)
.collect(Collectors.toSet());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java
index a762f718ab7..9980335bab0 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java
@@ -35,14 +35,15 @@ public abstract class ApplicationMaintainer extends NodeRepositoryMaintainer {
new DaemonThreadFactory("node repo application maintainer"));
protected ApplicationMaintainer(Deployer deployer, Metric metric, NodeRepository nodeRepository, Duration interval) {
- super(nodeRepository, interval);
+ super(nodeRepository, interval, metric);
this.deployer = deployer;
this.metric = metric;
}
@Override
- protected final void maintain() {
+ protected final boolean maintain() {
applicationsNeedingMaintenance().forEach(this::deploy);
+ return true;
}
/** Returns the number of deployments that are pending execution */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
index c32b7854d4e..e2b98d8d000 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
@@ -14,7 +14,6 @@ import com.yahoo.vespa.hosted.provision.applications.Applications;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb;
-import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
import java.time.Duration;
import java.util.List;
@@ -38,17 +37,19 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer {
Deployer deployer,
Metric metric,
Duration interval) {
- super(nodeRepository, interval);
+ super(nodeRepository, interval, metric);
this.autoscaler = new Autoscaler(metricsDb, nodeRepository);
this.metric = metric;
this.deployer = deployer;
}
@Override
- protected void maintain() {
- if ( ! nodeRepository().zone().environment().isProduction()) return;
+ protected boolean maintain() {
+ boolean success = true;
+ if ( ! nodeRepository().zone().environment().isProduction()) return success;
activeNodesByApplication().forEach((applicationId, nodes) -> autoscale(applicationId, nodes));
+ return success;
}
private void autoscale(ApplicationId application, List<Node> applicationNodes) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
index f583728f9b8..92abae46f9a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
@@ -142,12 +142,12 @@ public class CapacityChecker {
for (var host : hosts) {
NodeResources hostResources = host.flavor().resources();
int occupiedIps = 0;
- Set<String> ipPool = host.ipAddressPool().asSet();
+ Set<String> ipPool = host.ipConfig().pool().asSet();
for (var child : nodeChildren.get(host)) {
hostResources = hostResources.subtract(child.resources().justNumbers());
- occupiedIps += child.ipAddresses().stream().filter(ipPool::contains).count();
+ occupiedIps += child.ipConfig().primary().stream().filter(ipPool::contains).count();
}
- availableResources.put(host, new AllocationResources(hostResources, host.ipAddressPool().asSet().size() - occupiedIps));
+ availableResources.put(host, new AllocationResources(hostResources, host.ipConfig().pool().asSet().size() - occupiedIps));
}
return availableResources;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java
index f428e276df8..eb5973f11a9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -25,8 +26,8 @@ public class DirtyExpirer extends Expirer {
private final NodeRepository nodeRepository;
- DirtyExpirer(NodeRepository nodeRepository, Clock clock, Duration dirtyTimeout) {
- super(Node.State.dirty, History.Event.Type.deallocated, nodeRepository, clock, dirtyTimeout);
+ DirtyExpirer(NodeRepository nodeRepository, Clock clock, Duration dirtyTimeout, Metric metric) {
+ super(Node.State.dirty, History.Event.Type.deallocated, nodeRepository, clock, dirtyTimeout, metric);
this.nodeRepository = nodeRepository;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
index 0a32970e056..b9005a028ff 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.OutOfCapacityException;
+import com.yahoo.jdisc.Metric;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
@@ -49,19 +50,21 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer {
DynamicProvisioningMaintainer(NodeRepository nodeRepository,
Duration interval,
HostProvisioner hostProvisioner,
- FlagSource flagSource) {
- super(nodeRepository, interval);
+ FlagSource flagSource,
+ Metric metric) {
+ super(nodeRepository, interval, metric);
this.hostProvisioner = hostProvisioner;
this.targetCapacityFlag = Flags.TARGET_CAPACITY.bindTo(flagSource);
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
try (Mutex lock = nodeRepository().lockUnallocated()) {
NodeList nodes = nodeRepository().list();
resumeProvisioning(nodes, lock);
convergeToCapacity(nodes);
}
+ return true;
}
/** Resume provisioning of already provisioned hosts and their children */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java
index dc5155312e7..43f5210b233 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.History;
@@ -32,8 +33,8 @@ public abstract class Expirer extends NodeRepositoryMaintainer {
private final Duration expiryTime;
Expirer(Node.State fromState, History.Event.Type eventType, NodeRepository nodeRepository,
- Clock clock, Duration expiryTime) {
- super(nodeRepository, min(Duration.ofMinutes(10), expiryTime));
+ Clock clock, Duration expiryTime, Metric metric) {
+ super(nodeRepository, min(Duration.ofMinutes(10), expiryTime), metric);
this.fromState = fromState;
this.eventType = eventType;
this.clock = clock;
@@ -41,7 +42,7 @@ public abstract class Expirer extends NodeRepositoryMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
List<Node> expired = new ArrayList<>();
for (Node node : nodeRepository().getNodes(fromState)) {
if (isExpired(node))
@@ -50,6 +51,7 @@ public abstract class Expirer extends NodeRepositoryMaintainer {
if ( ! expired.isEmpty())
log.info(fromState + " expirer found " + expired.size() + " expired nodes: " + expired);
expire(expired);
+ return true;
}
protected boolean isExpired(Node node) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
index d65b4ce4248..2afbb0e6476 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
@@ -3,9 +3,9 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -22,24 +22,21 @@ import java.util.stream.Collectors;
/**
* This moves expired failed nodes:
- * <ul>
- * <li>To parked: If the node has known hardware failure, Docker hosts are moved to parked only when all of their
- * children are already in parked
- * <li>To dirty: If the node has failed less than 5 times OR the environment is dev, test or perf.
- * Those environments have no protection against users running bogus applications, so
- * we cannot use the node failure count to conclude the node has a failure.
- * <li>Otherwise the node will remain in failed
- * </ul>
+ *
+ * - To parked: If the node has known hardware failure, Docker hosts are moved to parked only when all of their
+ * children are already in parked.
+ * - To dirty: If the node is a host and has failed less than 5 times, or always if the node is a child.
+ * - Otherwise the node will remain in failed.
+ *
* Failed content nodes are given a long expiry time to enable us to manually moved them back to
* active to recover data in cases where the node was failed accidentally.
- * <p>
+ *
* Failed container (Vespa, not Docker) nodes are expired early as there's no data to potentially recover.
- * </p>
- * <p>
+ *
* The purpose of the automatic recycling to dirty + fail count is that nodes which were moved
* to failed due to some undetected hardware failure will end up being failed again.
* When that has happened enough they will not be recycled.
- * <p>
+ *
* Nodes with detected hardware issues will not be recycled.
*
* @author bratseth
@@ -56,8 +53,8 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
private final Duration defaultExpiry; // Grace period to allow recovery of data
private final Duration containerExpiry; // Stateless nodes, no data to recover
- FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, Duration interval) {
- super(nodeRepository, interval);
+ FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, Duration interval, Metric metric) {
+ super(nodeRepository, interval, metric);
this.nodeRepository = nodeRepository;
this.zone = zone;
this.clock = clock;
@@ -74,7 +71,7 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
List<Node> remainingNodes = new ArrayList<>(nodeRepository.list()
.state(Node.State.failed)
.nodeType(NodeType.tenant, NodeType.host)
@@ -86,6 +83,7 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
node.history().hasEventBefore(History.Event.Type.failed, clock.instant().minus(containerExpiry)));
recycleIf(remainingNodes, node ->
node.history().hasEventBefore(History.Event.Type.failed, clock.instant().minus(defaultExpiry)));
+ return true;
}
/** Recycle the nodes matching condition, and remove those nodes from the nodes list. */
@@ -123,8 +121,7 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
/** Returns whether the current node fail count should be used as an indicator of hardware issue */
private boolean failCountIndicatesHardwareIssue(Node node) {
- if (node.flavor().getType() == Flavor.Type.DOCKER_CONTAINER) return false;
- return (zone.environment() == Environment.prod || zone.environment() == Environment.staging) &&
- node.status().failCount() >= maxAllowedFailures;
+ return node.type().isHost() && node.status().failCount() >= maxAllowedFailures;
}
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
index 3cb7cc218a7..389fc0ee907 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -36,8 +37,8 @@ public class InactiveExpirer extends Expirer {
private final NodeRepository nodeRepository;
- InactiveExpirer(NodeRepository nodeRepository, Clock clock, Duration inactiveTimeout) {
- super(Node.State.inactive, History.Event.Type.deactivated, nodeRepository, clock, inactiveTimeout);
+ InactiveExpirer(NodeRepository nodeRepository, Clock clock, Duration inactiveTimeout, Metric metric) {
+ super(Node.State.inactive, History.Event.Type.deactivated, nodeRepository, clock, inactiveTimeout, metric);
this.nodeRepository = nodeRepository;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
index b933e549357..e317333135c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
@@ -2,10 +2,11 @@
package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.InfraDeployer;
-import java.util.logging.Level;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.time.Duration;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -20,8 +21,8 @@ public class InfrastructureProvisioner extends NodeRepositoryMaintainer {
private final InfraDeployer infraDeployer;
- InfrastructureProvisioner(NodeRepository nodeRepository, InfraDeployer infraDeployer, Duration interval) {
- super(nodeRepository, interval);
+ InfrastructureProvisioner(NodeRepository nodeRepository, InfraDeployer infraDeployer, Duration interval, Metric metric) {
+ super(nodeRepository, interval, metric);
this.infraDeployer = infraDeployer;
}
@@ -38,7 +39,9 @@ public class InfrastructureProvisioner extends NodeRepositoryMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
infraDeployer.activateAllSupportedInfraApplications(false);
+ return true;
}
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
index 6edd57de1c1..90cf3ba8f54 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
@@ -39,17 +40,16 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
private final LoadBalancerService service;
private final CuratorDatabaseClient db;
- LoadBalancerExpirer(NodeRepository nodeRepository, Duration interval, LoadBalancerService service) {
- super(nodeRepository, interval);
+ LoadBalancerExpirer(NodeRepository nodeRepository, Duration interval, LoadBalancerService service, Metric metric) {
+ super(nodeRepository, interval, metric);
this.service = Objects.requireNonNull(service, "service must be non-null");
this.db = nodeRepository.database();
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
expireReserved();
- removeInactive();
- pruneReals();
+ return removeInactive() & pruneReals();
}
/** Move reserved load balancer that have expired to inactive */
@@ -63,7 +63,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
}
/** Deprovision inactive load balancers that have expired */
- private void removeInactive() {
+ private boolean removeInactive() {
var failed = new ArrayList<LoadBalancerId>();
var lastException = new AtomicReference<Exception>();
var now = nodeRepository().clock().instant();
@@ -88,10 +88,11 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
interval()),
lastException.get());
}
+ return lastException.get() == null;
}
/** Remove reals from inactive load balancers */
- private void pruneReals() {
+ private boolean pruneReals() {
var failed = new ArrayList<LoadBalancerId>();
var lastException = new AtomicReference<Exception>();
withLoadBalancersIn(State.inactive, lb -> {
@@ -109,13 +110,14 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
});
if (!failed.isEmpty()) {
log.log(Level.WARNING, String.format("Failed to remove reals from %d load balancers: %s, retrying in %s",
- failed.size(),
- failed.stream()
- .map(LoadBalancerId::serializedForm)
- .collect(Collectors.joining(", ")),
- interval()),
+ failed.size(),
+ failed.stream()
+ .map(LoadBalancerId::serializedForm)
+ .collect(Collectors.joining(", ")),
+ interval()),
lastException.get());
}
+ return lastException.get() == null;
}
/** Apply operation to all load balancers that exist in given state, while holding lock */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
index c631de5f17b..e0d7dc5f19e 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
@@ -50,7 +50,7 @@ public class MetricsReporter extends NodeRepositoryMaintainer {
Supplier<Integer> pendingRedeploymentsSupplier,
Duration interval,
Clock clock) {
- super(nodeRepository, interval);
+ super(nodeRepository, interval, metric);
this.metric = metric;
this.orchestrator = orchestrator;
this.serviceMonitor = serviceMonitor;
@@ -59,7 +59,7 @@ public class MetricsReporter extends NodeRepositoryMaintainer {
}
@Override
- public void maintain() {
+ public boolean maintain() {
NodeList nodes = nodeRepository().list();
ServiceModel serviceModel = serviceMonitor.getServiceModelSnapshot();
@@ -68,6 +68,7 @@ public class MetricsReporter extends NodeRepositoryMaintainer {
updateMaintenanceMetrics();
updateDockerMetrics(nodes);
updateTenantUsageMetrics(nodes);
+ return true;
}
private void updateMaintenanceMetrics() {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
index 9c1892a1920..a2a189769bf 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
@@ -78,7 +78,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
Duration downTimeLimit, Clock clock, Orchestrator orchestrator,
ThrottlePolicy throttlePolicy, Metric metric) {
// check ping status every five minutes, but at least twice as often as the down time limit
- super(nodeRepository, min(downTimeLimit.dividedBy(2), Duration.ofMinutes(5)));
+ super(nodeRepository, min(downTimeLimit.dividedBy(2), Duration.ofMinutes(5)), metric);
this.deployer = deployer;
this.hostLivenessTracker = hostLivenessTracker;
this.serviceMonitor = serviceMonitor;
@@ -91,7 +91,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
int throttledHostFailures = 0;
int throttledNodeFailures = 0;
@@ -131,9 +131,11 @@ public class NodeFailer extends NodeRepositoryMaintainer {
failActive(node, reason);
}
- metric.set(throttlingActiveMetric, Math.min( 1, throttledHostFailures + throttledNodeFailures), null);
+ int throttlingActive = Math.min(1, throttledHostFailures + throttledNodeFailures);
+ metric.set(throttlingActiveMetric, throttlingActive, null);
metric.set(throttledHostFailuresMetric, throttledHostFailures, null);
metric.set(throttledNodeFailuresMetric, throttledNodeFailures, null);
+ return throttlingActive == 0;
}
private void updateNodeLivenessEventsForReadyNodes(Mutex lock) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
index eb2b46dd53e..222ee631968 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
@@ -2,8 +2,9 @@
package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.provision.autoscale.NodeMetrics;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.autoscale.NodeMetrics;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb;
import com.yahoo.yolean.Exceptions;
@@ -26,14 +27,15 @@ public class NodeMetricsDbMaintainer extends NodeRepositoryMaintainer {
public NodeMetricsDbMaintainer(NodeRepository nodeRepository,
NodeMetrics nodeMetrics,
NodeMetricsDb nodeMetricsDb,
- Duration interval) {
- super(nodeRepository, interval);
+ Duration interval,
+ Metric metric) {
+ super(nodeRepository, interval, metric);
this.nodeMetrics = nodeMetrics;
this.nodeMetricsDb = nodeMetricsDb;
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
int warnings = 0;
for (ApplicationId application : activeNodesByApplication().keySet()) {
try {
@@ -46,6 +48,7 @@ public class NodeMetricsDbMaintainer extends NodeRepositoryMaintainer {
}
}
nodeMetricsDb.gc(nodeRepository().clock());
+ return warnings == 0;
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooter.java
index c78ed72ff42..f64f27b1219 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooter.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooter.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.Flavor;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.IntFlag;
@@ -32,15 +33,15 @@ public class NodeRebooter extends NodeRepositoryMaintainer {
private final Clock clock;
private final Random random;
- NodeRebooter(NodeRepository nodeRepository, Clock clock, FlagSource flagSource) {
- super(nodeRepository, Duration.ofMinutes(25));
+ NodeRebooter(NodeRepository nodeRepository, Clock clock, FlagSource flagSource, Metric metric) {
+ super(nodeRepository, Duration.ofMinutes(25), metric);
this.rebootIntervalInDays = Flags.REBOOT_INTERVAL_IN_DAYS.bindTo(flagSource);
this.clock = clock;
this.random = new Random(clock.millis()); // seed with clock for test determinism
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
// Reboot candidates: Nodes in long-term states, where we know we can safely orchestrate a reboot
List<Node> nodesToReboot = nodeRepository().getNodes(Node.State.active, Node.State.ready).stream()
.filter(node -> node.flavor().getType() != Flavor.Type.DOCKER_CONTAINER)
@@ -49,6 +50,7 @@ public class NodeRebooter extends NodeRepositoryMaintainer {
if (!nodesToReboot.isEmpty())
nodeRepository().reboot(NodeListFilter.from(nodesToReboot));
+ return true;
}
private boolean shouldReboot(Node node) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
index 8368569cda0..5f87cf9fd9b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
@@ -1,9 +1,11 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.concurrent.maintenance.JobMetrics;
import com.yahoo.concurrent.maintenance.Maintainer;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -21,8 +23,9 @@ public abstract class NodeRepositoryMaintainer extends Maintainer {
private final NodeRepository nodeRepository;
- public NodeRepositoryMaintainer(NodeRepository nodeRepository, Duration interval) {
- super(null, interval, nodeRepository.clock().instant(), nodeRepository.jobControl(), nodeRepository.database().cluster());
+ public NodeRepositoryMaintainer(NodeRepository nodeRepository, Duration interval, Metric metric) {
+ super(null, interval, nodeRepository.clock().instant(), nodeRepository.jobControl(), jobMetrics(metric),
+ nodeRepository.database().cluster());
this.nodeRepository = nodeRepository;
}
@@ -41,4 +44,10 @@ public abstract class NodeRepositoryMaintainer extends Maintainer {
.collect(Collectors.groupingBy(node -> node.allocation().get().owner()));
}
+ private static JobMetrics jobMetrics(Metric metric) {
+ return new JobMetrics((job, consecutiveFailures) -> {
+ metric.set("maintenance.consecutiveFailures", consecutiveFailures, metric.createContext(Map.of("job", job)));
+ });
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index 4323622df8b..a5482281ef1 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -75,25 +75,25 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
nodeFailer = new NodeFailer(deployer, hostLivenessTracker, serviceMonitor, nodeRepository, defaults.failGrace, clock, orchestrator, throttlePolicyFromEnv().orElse(defaults.throttlePolicy), metric);
periodicApplicationMaintainer = new PeriodicApplicationMaintainer(deployer, metric, nodeRepository, defaults.redeployMaintainerInterval, defaults.periodicRedeployInterval);
operatorChangeApplicationMaintainer = new OperatorChangeApplicationMaintainer(deployer, metric, nodeRepository, defaults.operatorChangeRedeployInterval);
- reservationExpirer = new ReservationExpirer(nodeRepository, clock, defaults.reservationExpiry);
+ reservationExpirer = new ReservationExpirer(nodeRepository, clock, defaults.reservationExpiry, metric);
retiredExpirer = new RetiredExpirer(nodeRepository, orchestrator, deployer, metric, clock, defaults.retiredInterval, defaults.retiredExpiry);
- inactiveExpirer = new InactiveExpirer(nodeRepository, clock, defaults.inactiveExpiry);
- failedExpirer = new FailedExpirer(nodeRepository, zone, clock, defaults.failedExpirerInterval);
- dirtyExpirer = new DirtyExpirer(nodeRepository, clock, defaults.dirtyExpiry);
- provisionedExpirer = new ProvisionedExpirer(nodeRepository, clock, defaults.provisionedExpiry);
- nodeRebooter = new NodeRebooter(nodeRepository, clock, flagSource);
+ inactiveExpirer = new InactiveExpirer(nodeRepository, clock, defaults.inactiveExpiry, metric);
+ failedExpirer = new FailedExpirer(nodeRepository, zone, clock, defaults.failedExpirerInterval, metric);
+ dirtyExpirer = new DirtyExpirer(nodeRepository, clock, defaults.dirtyExpiry, metric);
+ provisionedExpirer = new ProvisionedExpirer(nodeRepository, clock, defaults.provisionedExpiry, metric);
+ nodeRebooter = new NodeRebooter(nodeRepository, clock, flagSource, metric);
metricsReporter = new MetricsReporter(nodeRepository, metric, orchestrator, serviceMonitor, periodicApplicationMaintainer::pendingDeployments, defaults.metricsInterval, clock);
- infrastructureProvisioner = new InfrastructureProvisioner(nodeRepository, infraDeployer, defaults.infrastructureProvisionInterval);
+ infrastructureProvisioner = new InfrastructureProvisioner(nodeRepository, infraDeployer, defaults.infrastructureProvisionInterval, metric);
loadBalancerExpirer = provisionServiceProvider.getLoadBalancerService(nodeRepository).map(lbService ->
- new LoadBalancerExpirer(nodeRepository, defaults.loadBalancerExpirerInterval, lbService));
+ new LoadBalancerExpirer(nodeRepository, defaults.loadBalancerExpirerInterval, lbService, metric));
dynamicProvisioningMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner ->
- new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, flagSource));
+ new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, flagSource, metric));
spareCapacityMaintainer = new SpareCapacityMaintainer(deployer, nodeRepository, metric, defaults.spareCapacityMaintenanceInterval);
- osUpgradeActivator = new OsUpgradeActivator(nodeRepository, defaults.osUpgradeActivatorInterval);
+ osUpgradeActivator = new OsUpgradeActivator(nodeRepository, defaults.osUpgradeActivatorInterval, metric);
rebalancer = new Rebalancer(deployer, nodeRepository, metric, clock, defaults.rebalancerInterval);
- nodeMetricsDbMaintainer = new NodeMetricsDbMaintainer(nodeRepository, nodeMetrics, nodeMetricsDb, defaults.nodeMetricsCollectionInterval);
+ nodeMetricsDbMaintainer = new NodeMetricsDbMaintainer(nodeRepository, nodeMetrics, nodeMetricsDb, defaults.nodeMetricsCollectionInterval, metric);
autoscalingMaintainer = new AutoscalingMaintainer(nodeRepository, nodeMetricsDb, deployer, metric, defaults.autoscalingInterval);
- scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, nodeMetricsDb, defaults.scalingSuggestionsInterval);
+ scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, nodeMetricsDb, defaults.scalingSuggestionsInterval, metric);
// The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now
infrastructureProvisioner.maintainButThrowOnException();
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivator.java
index 11afbd785e8..be1190ccff4 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivator.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -17,17 +18,18 @@ import java.time.Duration;
*/
public class OsUpgradeActivator extends NodeRepositoryMaintainer {
- public OsUpgradeActivator(NodeRepository nodeRepository, Duration interval) {
- super(nodeRepository, interval);
+ public OsUpgradeActivator(NodeRepository nodeRepository, Duration interval, Metric metric) {
+ super(nodeRepository, interval, metric);
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
for (var nodeType : NodeType.values()) {
if (!nodeType.isHost()) continue;
var active = canUpgradeOsOf(nodeType);
nodeRepository().osVersions().resumeUpgradeOf(nodeType, active);
}
+ return true;
}
/** Returns whether to allow OS upgrade of nodes of given type */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java
index e1407f2a41d..d38bff091b0 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -19,8 +20,8 @@ public class ProvisionedExpirer extends Expirer {
private final NodeRepository nodeRepository;
- ProvisionedExpirer(NodeRepository nodeRepository, Clock clock, Duration dirtyTimeout) {
- super(Node.State.provisioned, History.Event.Type.provisioned, nodeRepository, clock, dirtyTimeout);
+ ProvisionedExpirer(NodeRepository nodeRepository, Clock clock, Duration dirtyTimeout, Metric metric) {
+ super(Node.State.provisioned, History.Event.Type.provisioned, nodeRepository, clock, dirtyTimeout, metric);
this.nodeRepository = nodeRepository;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
index 3df20fa9d08..9b9c7df5d0d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
@@ -31,22 +31,24 @@ public class Rebalancer extends NodeRepositoryMaintainer {
Metric metric,
Clock clock,
Duration interval) {
- super(nodeRepository, interval);
+ super(nodeRepository, interval, metric);
this.deployer = deployer;
this.metric = metric;
this.clock = clock;
}
@Override
- protected void maintain() {
- if ( ! nodeRepository().zone().getCloud().allowHostSharing()) return; // Rebalancing not necessary
- if (nodeRepository().zone().environment().isTest()) return; // Short lived deployments; no need to rebalance
+ protected boolean maintain() {
+ boolean success = true;
+ if ( ! nodeRepository().zone().getCloud().allowHostSharing()) return success; // Rebalancing not necessary
+ if (nodeRepository().zone().environment().isTest()) return success; // Short lived deployments; no need to rebalance
// Work with an unlocked snapshot as this can take a long time and full consistency is not needed
NodeList allNodes = nodeRepository().list();
updateSkewMetric(allNodes);
- if ( ! zoneIsStable(allNodes)) return;
+ if ( ! zoneIsStable(allNodes)) return success;
findBestMove(allNodes).execute(true, Agent.Rebalancer, deployer, metric, nodeRepository());
+ return success;
}
/** We do this here rather than in MetricsReporter because it is expensive and frequent updates are unnecessary */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
index 03d466dbf09..27f77dd08a3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -22,8 +23,8 @@ public class ReservationExpirer extends Expirer {
private final NodeRepository nodeRepository;
- public ReservationExpirer(NodeRepository nodeRepository, Clock clock, Duration reservationPeriod) {
- super(Node.State.reserved, History.Event.Type.reserved, nodeRepository, clock, reservationPeriod);
+ public ReservationExpirer(NodeRepository nodeRepository, Clock clock, Duration reservationPeriod, Metric metric) {
+ super(Node.State.reserved, History.Event.Type.reserved, nodeRepository, clock, reservationPeriod, metric);
this.nodeRepository = nodeRepository;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java
index a8566e24743..5b7f90102ba 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java
@@ -39,7 +39,7 @@ public class RetiredExpirer extends NodeRepositoryMaintainer {
Clock clock,
Duration maintenanceInterval,
Duration retiredExpiry) {
- super(nodeRepository, maintenanceInterval);
+ super(nodeRepository, maintenanceInterval, metric);
this.deployer = deployer;
this.metric = metric;
this.orchestrator = orchestrator;
@@ -48,7 +48,7 @@ public class RetiredExpirer extends NodeRepositoryMaintainer {
}
@Override
- protected void maintain() {
+ protected boolean maintain() {
List<Node> activeNodes = nodeRepository().getNodes(Node.State.active);
Map<ApplicationId, List<Node>> retiredNodesByApplication = activeNodes.stream()
@@ -69,11 +69,12 @@ public class RetiredExpirer extends NodeRepositoryMaintainer {
nodeRepository().setRemovable(application, nodesToRemove);
boolean success = deployment.activate();
- if ( ! success) return;
+ if ( ! success) return success;
String nodeList = nodesToRemove.stream().map(Node::hostname).collect(Collectors.joining(", "));
log.info("Redeployed " + application + " to deactivate retired nodes: " + nodeList);
}
}
+ return true;
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
index b68e8eacbaa..b0c52d10f7d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.jdisc.Metric;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -12,7 +13,6 @@ import com.yahoo.vespa.hosted.provision.applications.Applications;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb;
-import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
import java.time.Duration;
import java.util.List;
@@ -31,16 +31,19 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer {
public ScalingSuggestionsMaintainer(NodeRepository nodeRepository,
NodeMetricsDb metricsDb,
- Duration interval) {
- super(nodeRepository, interval);
+ Duration interval,
+ Metric metric) {
+ super(nodeRepository, interval, metric);
this.autoscaler = new Autoscaler(metricsDb, nodeRepository);
}
@Override
- protected void maintain() {
- if ( ! nodeRepository().zone().environment().isProduction()) return;
+ protected boolean maintain() {
+ boolean success = true;
+ if ( ! nodeRepository().zone().environment().isProduction()) return success;
activeNodesByApplication().forEach((applicationId, nodes) -> suggest(applicationId, nodes));
+ return success;
}
private void suggest(ApplicationId application, List<Node> applicationNodes) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java
index 90c3a277080..20258e7947b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java
@@ -56,15 +56,16 @@ public class SpareCapacityMaintainer extends NodeRepositoryMaintainer {
Metric metric,
Duration interval,
int maxIterations) {
- super(nodeRepository, interval);
+ super(nodeRepository, interval, metric);
this.deployer = deployer;
this.metric = metric;
this.maxIterations = maxIterations;
}
@Override
- protected void maintain() {
- if ( ! nodeRepository().zone().getCloud().allowHostSharing()) return;
+ protected boolean maintain() {
+ boolean success = true;
+ if ( ! nodeRepository().zone().getCloud().allowHostSharing()) return success;
CapacityChecker capacityChecker = new CapacityChecker(nodeRepository());
@@ -89,6 +90,7 @@ public class SpareCapacityMaintainer extends NodeRepositoryMaintainer {
}
metric.set("spareHostCapacity", spareHostCapacity, null);
}
+ return success;
}
private boolean execute(List<Move> mitigation) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
index 5e2f0bd4761..cc62ae67e84 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
@@ -77,13 +77,15 @@ public class CuratorDatabaseClient {
private final Clock clock;
private final Zone zone;
private final CuratorCounter provisionIndexCounter;
+ private final boolean logStackTracesOnLockTimeout;
- public CuratorDatabaseClient(NodeFlavors flavors, Curator curator, Clock clock, Zone zone, boolean useCache) {
+ public CuratorDatabaseClient(NodeFlavors flavors, Curator curator, Clock clock, Zone zone, boolean useCache, boolean logStackTracesOnLockTimeout) {
this.nodeSerializer = new NodeSerializer(flavors);
this.zone = zone;
this.db = new CuratorDatabase(curator, root, useCache);
this.clock = clock;
this.provisionIndexCounter = new CuratorCounter(curator, root.append("provisionIndexCounter").getAbsolute());
+ this.logStackTracesOnLockTimeout = logStackTracesOnLockTimeout;
initZK();
}
@@ -394,6 +396,22 @@ public class CuratorDatabaseClient {
try {
return db.lock(lockPath(application), timeout);
} catch (UncheckedTimeoutException e) {
+ if (logStackTracesOnLockTimeout) {
+ log.log(Level.WARNING, "Logging stack trace from all threads due to lock timeout");
+ Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
+ for (Map.Entry<Thread, StackTraceElement[]> kv : stackTraces.entrySet()) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Thread '")
+ .append(kv.getKey().getName())
+ .append("'\n");
+ for (var stackTraceElement : kv.getValue()) {
+ sb.append("\tat ")
+ .append(stackTraceElement)
+ .append("\n");
+ }
+ log.log(Level.WARNING, sb.toString());
+ }
+ }
throw new ApplicationLockException(e);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java
index fd16e61417f..e8bd598626b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
-import com.yahoo.vespa.hosted.provision.LockedNodeList;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
@@ -75,7 +74,7 @@ public class HostCapacity {
* Number of free (not allocated) IP addresses assigned to the dockerhost.
*/
int freeIPs(Node dockerHost) {
- return dockerHost.ipAddressPool().findUnused(allNodes).size();
+ return dockerHost.ipConfig().pool().findUnused(allNodes).size();
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
index e710d70f20f..6403bbe2b0c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
@@ -165,33 +165,33 @@ public class LoadBalancerProvisioner {
if (loadBalancer.isEmpty() && activate) return; // Nothing to activate as this load balancer was never prepared
var force = loadBalancer.isPresent() && loadBalancer.get().state() != LoadBalancer.State.active;
- var instance = provisionInstance(application, clusterId, nodes, force);
+ var instance = provisionInstance(id, nodes, force);
LoadBalancer newLoadBalancer;
if (loadBalancer.isEmpty()) {
newLoadBalancer = new LoadBalancer(id, instance, LoadBalancer.State.reserved, now);
} else {
var newState = activate ? LoadBalancer.State.active : loadBalancer.get().state();
newLoadBalancer = loadBalancer.get().with(instance).with(newState, now);
+ if (loadBalancer.get().state() != newLoadBalancer.state()) {
+ log.log(logLevel(), "Moving " + newLoadBalancer.id() + " to state " + newLoadBalancer.state());
+ }
}
db.writeLoadBalancer(newLoadBalancer);
}
- private LoadBalancerInstance provisionInstance(ApplicationId application, ClusterSpec.Id cluster, List<Node> nodes,
- boolean force) {
+ private LoadBalancerInstance provisionInstance(LoadBalancerId id, List<Node> nodes, boolean force) {
var reals = new LinkedHashSet<Real>();
for (var node : nodes) {
for (var ip : reachableIpAddresses(node)) {
reals.add(new Real(HostName.from(node.hostname()), ip));
}
}
- log.log(Level.FINE, "Creating load balancer for " + cluster + " in " + application.toShortString() +
- ", targeting: " + reals);
+ log.log(logLevel(), "Creating " + id + ", targeting: " + reals);
try {
- return service.create(new LoadBalancerSpec(application, cluster, reals), force);
+ return service.create(new LoadBalancerSpec(id.application(), id.cluster(), reals), force);
} catch (Exception e) {
- throw new LoadBalancerServiceException("Failed to (re)configure load balancer for " + cluster + " in " +
- application + ", targeting: " + reals + ". The operation will be " +
- "retried on next deployment", e);
+ throw new LoadBalancerServiceException("Failed to (re)configure " + id + ", targeting: " +
+ reals + ". The operation will be retried on next deployment", e);
}
}
@@ -216,7 +216,7 @@ public class LoadBalancerProvisioner {
/** Find IP addresses reachable by the load balancer service */
private Set<String> reachableIpAddresses(Node node) {
- Set<String> reachable = new LinkedHashSet<>(node.ipAddresses());
+ Set<String> reachable = new LinkedHashSet<>(node.ipConfig().primary());
// Remove addresses unreachable by the load balancer service
switch (service.protocol()) {
case ipv4:
@@ -233,4 +233,8 @@ public class LoadBalancerProvisioner {
return cluster.combinedId().orElse(cluster.id());
}
+ private Level logLevel() {
+ return nodeRepository.zone().system().isCd() ? Level.INFO : Level.FINE;
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodeAclResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodeAclResponse.java
index a7577392fe2..07e93111b6f 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodeAclResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodeAclResponse.java
@@ -55,7 +55,7 @@ public class NodeAclResponse extends HttpResponse {
}
private void toSlime(NodeAcl nodeAcl, Cursor array) {
- nodeAcl.trustedNodes().forEach(node -> node.ipAddresses().forEach(ipAddress -> {
+ nodeAcl.trustedNodes().forEach(node -> node.ipConfig().primary().forEach(ipAddress -> {
Cursor object = array.addObject();
object.setString("hostname", node.hostname());
object.setString("type", node.type().name());
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 58e838018c6..3efd6e417cb 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
@@ -180,8 +180,8 @@ class NodesResponse extends HttpResponse {
object.setBool("wantToRetire", node.status().wantToRetire());
object.setBool("wantToDeprovision", node.status().wantToDeprovision());
toSlime(node.history(), object.setArray("history"));
- ipAddressesToSlime(node.ipAddresses(), object.setArray("ipAddresses"));
- ipAddressesToSlime(node.ipAddressPool().asSet(), object.setArray("additionalIpAddresses"));
+ ipAddressesToSlime(node.ipConfig().primary(), object.setArray("ipAddresses"));
+ ipAddressesToSlime(node.ipConfig().pool().asSet(), object.setArray("additionalIpAddresses"));
node.reports().toSlime(object, "reports");
node.modelName().ifPresent(modelName -> object.setString("modelName", modelName));
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
index ba859655ab7..45da1f1d3ee 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
@@ -65,7 +65,7 @@ public class DynamicProvisioningMaintainerTest {
Node host4 = tester.nodeRepository.getNode("host4").orElseThrow();
Node host41 = tester.nodeRepository.getNode("host4-1").orElseThrow();
assertTrue("No IP addresses assigned",
- Stream.of(host3, host4, host41).map(Node::ipAddresses).allMatch(Set::isEmpty));
+ Stream.of(host3, host4, host41).map(node -> node.ipConfig().primary()).allMatch(Set::isEmpty));
Node host3new = host3.with(host3.ipConfig().with(Set.of("::3:0")));
Node host4new = host4.with(host4.ipConfig().with(Set.of("::4:0")));
@@ -83,7 +83,7 @@ public class DynamicProvisioningMaintainerTest {
tester.hostProvisioner.with(Behaviour.failProvisioning);
Node host4 = tester.addNode("host4", Optional.empty(), NodeType.host, Node.State.provisioned);
Node host41 = tester.addNode("host4-1", Optional.of("host4"), NodeType.tenant, Node.State.reserved, DynamicProvisioningTester.tenantApp);
- assertTrue("No IP addresses assigned", Stream.of(host4, host41).map(Node::ipAddresses).allMatch(Set::isEmpty));
+ assertTrue("No IP addresses assigned", Stream.of(host4, host41).map(node -> node.ipConfig().primary()).allMatch(Set::isEmpty));
tester.maintainer.maintain();
assertEquals(Set.of("host4", "host4-1"),
@@ -233,7 +233,8 @@ public class DynamicProvisioningMaintainerTest {
this.maintainer = new DynamicProvisioningMaintainer(nodeRepository,
Duration.ofDays(1),
hostProvisioner,
- flagSource);
+ flagSource,
+ new TestMetric());
}
private DynamicProvisioningTester addInitialNodes() {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
index ed6f31984a5..f8e21ebbfce 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
@@ -263,7 +263,7 @@ public class FailedExpirerTest {
false,
0);
this.provisioner = new NodeRepositoryProvisioner(nodeRepository, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
- this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30));
+ this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30), new TestMetric());
}
public ManualClock clock() {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
index 89e43f80479..3d17cbf0217 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
@@ -64,7 +64,7 @@ public class InactiveAndFailedExpirerTest {
// Inactive times out
tester.advanceTime(Duration.ofMinutes(14));
- new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
assertEquals(0, tester.nodeRepository().getNodes(Node.State.inactive).size());
List<Node> dirty = tester.nodeRepository().getNodes(Node.State.dirty);
assertEquals(2, dirty.size());
@@ -79,7 +79,7 @@ public class InactiveAndFailedExpirerTest {
// Dirty times out for the other one
tester.advanceTime(Duration.ofMinutes(14));
- new DirtyExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new DirtyExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
assertEquals(0, tester.nodeRepository().getNodes(NodeType.tenant, Node.State.dirty).size());
List<Node> failed = tester.nodeRepository().getNodes(NodeType.tenant, Node.State.failed);
assertEquals(1, failed.size());
@@ -107,7 +107,7 @@ public class InactiveAndFailedExpirerTest {
// Inactive times out and node is moved to dirty
tester.advanceTime(Duration.ofMinutes(14));
- new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
List<Node> dirty = tester.nodeRepository().getNodes(Node.State.dirty);
assertEquals(2, dirty.size());
@@ -158,7 +158,7 @@ public class InactiveAndFailedExpirerTest {
// Inactive times out and one node is moved to parked
tester.advanceTime(Duration.ofMinutes(11)); // Trigger InactiveExpirer
- new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
assertEquals(1, tester.nodeRepository().getNodes(Node.State.parked).size());
}
@@ -180,7 +180,7 @@ public class InactiveAndFailedExpirerTest {
assertEquals(1, inactiveNodes.size());
// See that nodes are moved to dirty immediately.
- new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
assertEquals(0, tester.nodeRepository().getNodes(Node.State.inactive).size());
List<Node> dirty = tester.nodeRepository().getNodes(Node.State.dirty);
assertEquals(1, dirty.size());
@@ -207,7 +207,7 @@ public class InactiveAndFailedExpirerTest {
.map(node -> node.withWantToRetire(true, true, Agent.system, tester.clock().instant()))
.collect(Collectors.toList()), () -> {});
tester.advanceTime(Duration.ofMinutes(11));
- new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run();
+ new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run();
assertEquals(2, tester.nodeRepository().getNodes(Node.State.parked).size());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
index a5e96369591..6c22f798fe0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
@@ -38,7 +38,8 @@ public class LoadBalancerExpirerTest {
public void expire_inactive() {
LoadBalancerExpirer expirer = new LoadBalancerExpirer(tester.nodeRepository(),
Duration.ofDays(1),
- tester.loadBalancerService());
+ tester.loadBalancerService(),
+ new TestMetric());
Supplier<Map<LoadBalancerId, LoadBalancer>> loadBalancers = () -> tester.nodeRepository().database().readLoadBalancers((ignored) -> true);
// Deploy two applications with a total of three load balancers
@@ -103,7 +104,8 @@ public class LoadBalancerExpirerTest {
public void expire_reserved() {
LoadBalancerExpirer expirer = new LoadBalancerExpirer(tester.nodeRepository(),
Duration.ofDays(1),
- tester.loadBalancerService());
+ tester.loadBalancerService(),
+ new TestMetric());
Supplier<Map<LoadBalancerId, LoadBalancer>> loadBalancers = () -> tester.nodeRepository().database().readLoadBalancers((ignored) -> true);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooterTest.java
index bae6de5a095..3ff81070516 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooterTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRebooterTest.java
@@ -26,7 +26,7 @@ public class NodeRebooterTest {
var flagSource = new InMemoryFlagSource().withIntFlag(Flags.REBOOT_INTERVAL_IN_DAYS.id(), (int) rebootInterval.toDays());
var tester = new MaintenanceTester();
tester.createReadyHostNodes(15);
- NodeRebooter rebooter = new NodeRebooter(tester.nodeRepository, tester.clock, flagSource);
+ NodeRebooter rebooter = new NodeRebooter(tester.nodeRepository, tester.clock, flagSource, new TestMetric());
assertReadyHosts(15, tester, 0L);
@@ -69,7 +69,7 @@ public class NodeRebooterTest {
var flagSource = new InMemoryFlagSource().withIntFlag(Flags.REBOOT_INTERVAL_IN_DAYS.id(), (int) rebootInterval.toDays());
var tester = new MaintenanceTester();
tester.createReadyHostNodes(2);
- NodeRebooter rebooter = new NodeRebooter(tester.nodeRepository, tester.clock, flagSource);
+ NodeRebooter rebooter = new NodeRebooter(tester.nodeRepository, tester.clock, flagSource, new TestMetric());
assertReadyHosts(2, tester, 0L);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java
index 65c7bf13b42..218812f9a3d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java
@@ -34,7 +34,7 @@ public class OsUpgradeActivatorTest {
@Test
public void activates_upgrade() {
var osVersions = tester.nodeRepository().osVersions();
- var osUpgradeActivator = new OsUpgradeActivator(tester.nodeRepository(), Duration.ofDays(1));
+ var osUpgradeActivator = new OsUpgradeActivator(tester.nodeRepository(), Duration.ofDays(1), new TestMetric());
var version0 = Version.fromString("7.0");
// Create infrastructure nodes
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
index 6ca154f5f17..bd92c2a9aa2 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
@@ -75,7 +75,7 @@ public class ReservationExpirerTest {
// Reservation times out
clock.advance(Duration.ofMinutes(14)); // Reserved but not used time out
- new ReservationExpirer(nodeRepository, clock, Duration.ofMinutes(10)).run();
+ new ReservationExpirer(nodeRepository, clock, Duration.ofMinutes(10), new TestMetric()).run();
// Assert nothing is reserved
assertEquals(0, nodeRepository.getNodes(NodeType.tenant, Node.State.reserved).size());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
index b7f21eb3114..be5c7f423c7 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
@@ -25,7 +25,6 @@ import java.time.Duration;
import java.util.List;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
/**
* Tests the scaling suggestions maintainer integration.
@@ -66,7 +65,8 @@ public class ScalingSuggestionsMaintainerTest {
ScalingSuggestionsMaintainer maintainer = new ScalingSuggestionsMaintainer(tester.nodeRepository(),
nodeMetricsDb,
- Duration.ofMinutes(1));
+ Duration.ofMinutes(1),
+ new TestMetric());
maintainer.maintain();
assertEquals("14 nodes with [vcpu: 6.9, memory: 5.1 Gb, disk 15.0 Gb, bandwidth: 0.1 Gbps, storage type: remote]",
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClientTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClientTest.java
index 880f5af5653..b3d567c8d58 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClientTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClientTest.java
@@ -26,7 +26,7 @@ public class CuratorDatabaseClientTest {
private final Curator curator = new MockCurator();
private final CuratorDatabaseClient zkClient = new CuratorDatabaseClient(
- FlavorConfigBuilder.createDummies("default"), curator, Clock.systemUTC(), Zone.defaultZone(), true);
+ FlavorConfigBuilder.createDummies("default"), curator, Clock.systemUTC(), Zone.defaultZone(), true, false);
@Test
public void can_read_stored_host_information() throws Exception {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
index f3fe1fc4915..5e4bfc2a7bc 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
@@ -240,7 +240,7 @@ public class NodeSerializerTest {
public void serializes_multiple_ip_addresses() {
byte[] nodeWithMultipleIps = createNodeJson("node4.yahoo.tld", "127.0.0.4", "::4");
Node deserializedNode = nodeSerializer.fromJson(State.provisioned, nodeWithMultipleIps);
- assertEquals(ImmutableSet.of("127.0.0.4", "::4"), deserializedNode.ipAddresses());
+ assertEquals(ImmutableSet.of("127.0.0.4", "::4"), deserializedNode.ipConfig().primary());
}
@Test
@@ -250,12 +250,12 @@ public class NodeSerializerTest {
// Test round-trip with IP address pool
node = node.with(node.ipConfig().with(IP.Pool.of(Set.of("::1", "::2", "::3"))));
Node copy = nodeSerializer.fromJson(node.state(), nodeSerializer.toJson(node));
- assertEquals(node.ipAddressPool().asSet(), copy.ipAddressPool().asSet());
+ assertEquals(node.ipConfig().pool().asSet(), copy.ipConfig().pool().asSet());
// Test round-trip without IP address pool (handle empty pool)
node = createNode();
copy = nodeSerializer.fromJson(node.state(), nodeSerializer.toJson(node));
- assertEquals(node.ipAddressPool().asSet(), copy.ipAddressPool().asSet());
+ assertEquals(node.ipConfig().pool().asSet(), copy.ipConfig().pool().asSet());
}
@Test
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
index f27775db570..d165f865432 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
@@ -201,9 +201,9 @@ public class AclProvisioningTest {
assertEquals(3, nodeAcls.get(0).trustedNodes().size());
Iterator<Node> trustedNodes = nodeAcls.get(0).trustedNodes().iterator();
- assertEquals(Set.of("127.0.1.1"), trustedNodes.next().ipAddresses());
- assertEquals(Set.of("127.0.1.2"), trustedNodes.next().ipAddresses());
- assertEquals(Set.of("127.0.1.3"), trustedNodes.next().ipAddresses());
+ assertEquals(Set.of("127.0.1.1"), trustedNodes.next().ipConfig().primary());
+ assertEquals(Set.of("127.0.1.2"), trustedNodes.next().ipConfig().primary());
+ assertEquals(Set.of("127.0.1.3"), trustedNodes.next().ipConfig().primary());
}
private List<Node> deploy(int nodeCount) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
index 100cf5704eb..29e371dd937 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
@@ -318,8 +318,8 @@ public class DynamicDockerAllocationTest {
tester.activate(application, hosts);
List<Node> activeNodes = tester.nodeRepository().getNodes(application);
- assertEquals(ImmutableSet.of("127.0.127.13", "::13"), activeNodes.get(0).ipAddresses());
- assertEquals(ImmutableSet.of("127.0.127.2", "::2"), activeNodes.get(1).ipAddresses());
+ assertEquals(ImmutableSet.of("127.0.127.13", "::13"), activeNodes.get(0).ipConfig().primary());
+ assertEquals(ImmutableSet.of("127.0.127.2", "::2"), activeNodes.get(1).ipConfig().primary());
}
@Test
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
index 3eb379b0914..845eeba972c 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
@@ -22,6 +22,7 @@ import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.maintenance.ReservationExpirer;
+import com.yahoo.vespa.hosted.provision.maintenance.TestMetric;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.History;
import org.junit.Test;
@@ -791,7 +792,7 @@ public class ProvisioningTest {
// Over 10 minutes pass since first reservation. First set of reserved nodes are not expired
tester.clock().advance(Duration.ofMinutes(8).plus(Duration.ofSeconds(1)));
ReservationExpirer expirer = new ReservationExpirer(tester.nodeRepository(), tester.clock(),
- Duration.ofMinutes(10));
+ Duration.ofMinutes(10), new TestMetric());
expirer.run();
assertEquals("Nodes remain reserved", 4,
tester.getNodes(application, Node.State.reserved).size());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
index b1ecd03aa13..81bf999a184 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
@@ -9,6 +9,7 @@ import com.yahoo.text.Utf8;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.maintenance.OsUpgradeActivator;
+import com.yahoo.vespa.hosted.provision.maintenance.TestMetric;
import com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository;
import com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock;
import org.junit.After;
@@ -763,7 +764,7 @@ public class NodesV2ApiTest {
// Activate target
var nodeRepository = (NodeRepository)tester.container().components().getComponent(MockNodeRepository.class.getName());
- var osUpgradeActivator = new OsUpgradeActivator(nodeRepository, Duration.ofDays(1));
+ var osUpgradeActivator = new OsUpgradeActivator(nodeRepository, Duration.ofDays(1), new TestMetric());
osUpgradeActivator.run();
// Other node type does not return wanted OS version
diff --git a/parent/pom.xml b/parent/pom.xml
index a77750b9132..ce40eb464fc 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -255,7 +255,6 @@
<artifactId>bundle-plugin</artifactId>
<version>${project.version}</version>
<configuration>
- <buildVespaPlatformBundle>true</buildVespaPlatformBundle>
<configGenVersion>${project.version}</configGenVersion>
<useCommonAssemblyIds>true</useCommonAssemblyIds>
</configuration>
diff --git a/searchcore/src/apps/proton/proton.cpp b/searchcore/src/apps/proton/proton.cpp
index 8709b0d01b1..beff0f363fe 100644
--- a/searchcore/src/apps/proton/proton.cpp
+++ b/searchcore/src/apps/proton/proton.cpp
@@ -252,6 +252,9 @@ App::Main()
}
}
}
+ // Ensure metric manager and state server are shut down before we start tearing
+ // down any service layer components that they may end up transitively using.
+ protonUP->shutdown_config_fetching_and_state_exposing_components_once();
if (spiProton) {
spiProton->getNode().requestShutdown("controlled shutdown");
spiProton->shutdown();
diff --git a/searchcore/src/apps/vespa-dump-feed/vespa-dump-feed.cpp b/searchcore/src/apps/vespa-dump-feed/vespa-dump-feed.cpp
index 7488fc633fd..f034ccdd6d1 100644
--- a/searchcore/src/apps/vespa-dump-feed/vespa-dump-feed.cpp
+++ b/searchcore/src/apps/vespa-dump-feed/vespa-dump-feed.cpp
@@ -13,7 +13,7 @@
#include <vespa/messagebus/network/rpcnetworkparams.h>
#include <vespa/vespalib/io/fileutil.h>
#include <vespa/vespalib/util/signalhandler.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/config/common/exceptions.h>
@@ -207,7 +207,7 @@ App::Main()
route.c_str(), feedFile.c_str()));
fprintf(stderr, "running feed command: %s\n", feedCmd.c_str());
std::string feederOutput;
- bool feedingOk = vespalib::SlaveProc::run(feedCmd.c_str(), feederOutput);
+ bool feedingOk = vespalib::ChildProcess::run(feedCmd.c_str(), feederOutput);
if (!feedingOk) {
fprintf(stderr, "error: feed command failed\n");
fprintf(stderr, "feed command output:\n-----\n%s\n-----\n", feederOutput.c_str());
diff --git a/searchcore/src/tests/proton/flushengine/flushengine_test.cpp b/searchcore/src/tests/proton/flushengine/flushengine_test.cpp
index b5bcd65cd33..dbfbbd16820 100644
--- a/searchcore/src/tests/proton/flushengine/flushengine_test.cpp
+++ b/searchcore/src/tests/proton/flushengine/flushengine_test.cpp
@@ -162,7 +162,7 @@ public:
return wrappedTargets;
}
- // Called once by flush engine slave thread for each task done
+ // Called once by flush engine thread for each task done
void taskDone()
{
std::lock_guard<std::mutex> guard(_lock);
diff --git a/searchcore/src/tests/proton/matching/query_test.cpp b/searchcore/src/tests/proton/matching/query_test.cpp
index 24e1e886351..6fbd43eabbe 100644
--- a/searchcore/src/tests/proton/matching/query_test.cpp
+++ b/searchcore/src/tests/proton/matching/query_test.cpp
@@ -730,7 +730,8 @@ void checkQueryAddsLocation(Test &test, const string &loc_string) {
SearchIterator::UP search = query.createSearch(*md);
test.ASSERT_TRUE(search.get());
if (!test.EXPECT_NOT_EQUAL(string::npos, search->asString().find(loc_string))) {
- fprintf(stderr, "search (missing loc_string): %s", search->asString().c_str());
+ fprintf(stderr, "search (missing loc_string '%s'): %s",
+ loc_string.c_str(), search->asString().c_str());
}
}
diff --git a/searchcore/src/tests/proton/matching/termdataextractor_test.cpp b/searchcore/src/tests/proton/matching/termdataextractor_test.cpp
index 2570e64dbe2..36c34e38a04 100644
--- a/searchcore/src/tests/proton/matching/termdataextractor_test.cpp
+++ b/searchcore/src/tests/proton/matching/termdataextractor_test.cpp
@@ -83,7 +83,7 @@ Node::UP getQuery(const ViewResolver &resolver)
query_builder.addStringTerm("bar", field, id[3], Weight(0));
}
- query_builder.addLocationTerm(Location(Point(10, 10), 3, 0),
+ query_builder.addLocationTerm(Location(Point{10, 10}, 3, 0),
field, id[7], Weight(0));
Node::UP node = query_builder.build();
diff --git a/searchcore/src/tests/proton/proton_configurer/CMakeLists.txt b/searchcore/src/tests/proton/proton_configurer/CMakeLists.txt
index edc4060d067..83958954c29 100644
--- a/searchcore/src/tests/proton/proton_configurer/CMakeLists.txt
+++ b/searchcore/src/tests/proton/proton_configurer/CMakeLists.txt
@@ -5,5 +5,6 @@ vespa_add_executable(searchcore_proton_configurer_test_app TEST
DEPENDS
searchcore_server
searchcore_fconfig
+ GTest::GTest
)
vespa_add_test(NAME searchcore_proton_configurer_test_app COMMAND searchcore_proton_configurer_test_app)
diff --git a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
index c26b008f769..83706d966ae 100644
--- a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
+++ b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
@@ -19,7 +19,7 @@
#include <vespa/searchcore/proton/server/i_proton_disk_layout.h>
#include <vespa/searchsummary/config/config-juniperrc.h>
#include <vespa/searchcore/config/config-ranking-constants.h>
-#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/gtest/gtest.h>
#include <vespa/searchcommon/common/schemaconfigurer.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/vespalib/test/insertion_operators.h>
@@ -119,12 +119,12 @@ struct ConfigFixture {
_generation(1),
_cachedConfigSnapshot()
{
- addDocType("_alwaysthere_");
+ addDocType("_alwaysthere_", "default");
}
~ConfigFixture() { }
- DBConfigFixture *addDocType(const std::string & name) {
+ DBConfigFixture *addDocType(const std::string & name, const std::string& bucket_space) {
DocumenttypesConfigBuilder::Documenttype dt;
dt.bodystruct = -1270491200;
dt.headerstruct = 306916075;
@@ -140,7 +140,7 @@ struct ConfigFixture {
BucketspacesConfigBuilder::Documenttype bsdt;
bsdt.name = name;
- bsdt.bucketspace = "default";
+ bsdt.bucketspace = bucket_space;
_bucketspacesBuilder.documenttype.push_back(bsdt);
DBConfigFixture::UP fixture = std::make_unique<DBConfigFixture>();
@@ -169,6 +169,12 @@ struct ConfigFixture {
}
}
_dbConfig.erase(name);
+ for (auto it(_bucketspacesBuilder.documenttype.begin()), mt(_bucketspacesBuilder.documenttype.end()); it != mt; ++it) {
+ if (it->name == name) {
+ _bucketspacesBuilder.documenttype.erase(it);
+ break;
+ }
+ }
}
BootstrapConfig::SP getBootstrapConfig(int64_t generation) const {
@@ -212,17 +218,21 @@ struct MyProtonConfigurerOwner;
struct MyDocumentDBConfigOwner : public DocumentDBConfigOwner
{
vespalib::string _name;
+ document::BucketSpace _bucket_space;
MyProtonConfigurerOwner &_owner;
MyDocumentDBConfigOwner(const vespalib::string &name,
+ document::BucketSpace bucket_space,
MyProtonConfigurerOwner &owner)
: DocumentDBConfigOwner(),
_name(name),
+ _bucket_space(bucket_space),
_owner(owner)
{
}
~MyDocumentDBConfigOwner() { }
void reconfigure(const DocumentDBConfig::SP & config) override;
+ document::BucketSpace getBucketSpace() const override { return _bucket_space; }
};
struct MyLog
@@ -266,8 +276,8 @@ struct MyProtonConfigurerOwner : public IProtonConfigurerOwner,
(void) configId;
(void) bootstrapConfig;
(void) initializeThreads;
- ASSERT_TRUE(_dbs.find(docTypeName) == _dbs.end());
- auto db = std::make_shared<MyDocumentDBConfigOwner>(docTypeName.getName(), *this);
+ EXPECT_TRUE(_dbs.find(docTypeName) == _dbs.end());
+ auto db = std::make_shared<MyDocumentDBConfigOwner>(docTypeName.getName(), bucketSpace, *this);
_dbs.insert(std::make_pair(docTypeName, db));
std::ostringstream os;
os << "add db " << docTypeName.getName() << " " << documentDBConfig->getGeneration();
@@ -285,6 +295,7 @@ struct MyProtonConfigurerOwner : public IProtonConfigurerOwner,
std::ostringstream os;
os << "apply config " << bootstrapConfig->getGeneration();
_log.push_back(os.str());
+
}
void reconfigureDocumentDB(const vespalib::string &name, const DocumentDBConfig::SP &config)
{
@@ -329,14 +340,15 @@ struct MyProtonDiskLayout : public IProtonDiskLayout
}
};
-struct Fixture
+class ProtonConfigurerTest : public ::testing::Test
{
MyProtonConfigurerOwner _owner;
ConfigFixture _config;
std::unique_ptr<IProtonDiskLayout> _diskLayout;
ProtonConfigurer _configurer;
- Fixture()
+protected:
+ ProtonConfigurerTest()
: _owner(),
_config("test"),
_diskLayout(),
@@ -344,13 +356,13 @@ struct Fixture
{
_diskLayout = std::make_unique<MyProtonDiskLayout>(_owner);
}
- ~Fixture() { }
+ ~ProtonConfigurerTest() override;
void assertLog(const std::vector<vespalib::string> &expLog) {
- EXPECT_EQUAL(expLog, _owner._log);
+ EXPECT_EQ(expLog, _owner._log);
}
void sync() { _owner.sync(); }
- void addDocType(const vespalib::string &name) { _config.addDocType(name); }
+ void addDocType(const vespalib::string &name, const std::string& bucket_space = "default") { _config.addDocType(name, bucket_space); }
void removeDocType(const vespalib::string &name) { _config.removeDocType(name); }
void applyConfig() {
_configurer.reconfigure(_config.getConfigSnapshot());
@@ -375,91 +387,105 @@ struct Fixture
}
};
-TEST_F("require that nothing is applied before initial config", Fixture())
+ProtonConfigurerTest::~ProtonConfigurerTest() = default;
+
+TEST_F(ProtonConfigurerTest, require_that_nothing_is_applied_before_initial_config)
{
- f.applyConfig();
- TEST_DO(f1.assertLog({}));
+ applyConfig();
+ assertLog({});
}
-TEST_F("require that initial config is applied", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_initial_config_is_applied)
{
- f.applyInitialConfig();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"}));
+ applyInitialConfig();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"});
}
-TEST_F("require that new config is blocked", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_new_config_is_blocked)
{
- f.applyInitialConfig();
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"}));
+ applyInitialConfig();
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"});
}
-TEST_F("require that new config can be unblocked", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_new_config_can_be_unblocked)
{
- f.applyInitialConfig();
- f.reconfigure();
- f.allowReconfig();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3", "reconf db _alwaysthere_ 3"}));
+ applyInitialConfig();
+ reconfigure();
+ allowReconfig();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3", "reconf db _alwaysthere_ 3"});
}
-TEST_F("require that initial config is not reapplied due to config unblock", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_initial_config_is_not_reapplied_due_to_config_unblock)
{
- f.applyInitialConfig();
- f.allowReconfig();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"}));
+ applyInitialConfig();
+ allowReconfig();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2"});
}
-TEST_F("require that we can add document db", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_we_can_add_document_db)
{
- f.applyInitialConfig();
- f.allowReconfig();
- f.addDocType("foobar");
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3","reconf db _alwaysthere_ 3", "add db foobar 3"}));
+ applyInitialConfig();
+ allowReconfig();
+ addDocType("foobar");
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3","reconf db _alwaysthere_ 3", "add db foobar 3"});
}
-TEST_F("require that we can remove document db", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_we_can_remove_document_db)
{
- f.addDocType("foobar");
- f.applyInitialConfig();
- f.allowReconfig();
- f.removeDocType("foobar");
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "remove db foobar", "remove dbdir foobar"}));
+ addDocType("foobar");
+ applyInitialConfig();
+ allowReconfig();
+ removeDocType("foobar");
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "remove db foobar", "remove dbdir foobar"});
}
-TEST_F("require that document db adds and reconfigs are intermingled", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_document_db_adds_and_reconfigs_are_intermingled)
{
- f.addDocType("foobar");
- f.applyInitialConfig();
- f.allowReconfig();
- f.addDocType("abar");
- f.removeDocType("foobar");
- f.addDocType("foobar");
- f.addDocType("zbar");
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "add db abar 3", "reconf db foobar 3", "add db zbar 3"}));
+ addDocType("foobar");
+ applyInitialConfig();
+ allowReconfig();
+ addDocType("abar");
+ removeDocType("foobar");
+ addDocType("foobar");
+ addDocType("zbar");
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "add db abar 3", "reconf db foobar 3", "add db zbar 3"});
}
-TEST_F("require that document db removes are applied at end", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_document_db_removes_are_applied_at_end)
{
- f.addDocType("abar");
- f.addDocType("foobar");
- f.applyInitialConfig();
- f.allowReconfig();
- f.removeDocType("abar");
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_,abar,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db abar 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "reconf db foobar 3", "remove db abar", "remove dbdir abar"}));
+ addDocType("abar");
+ addDocType("foobar");
+ applyInitialConfig();
+ allowReconfig();
+ removeDocType("abar");
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_,abar,foobar", "apply config 2", "add db _alwaysthere_ 2", "add db abar 2", "add db foobar 2", "apply config 3","reconf db _alwaysthere_ 3", "reconf db foobar 3", "remove db abar", "remove dbdir abar"});
}
-TEST_F("require that new configs can be blocked again", Fixture())
+TEST_F(ProtonConfigurerTest, require_that_new_configs_can_be_blocked_again)
{
- f.applyInitialConfig();
- f.reconfigure();
- f.allowReconfig();
- f.disableReconfig();
- f.reconfigure();
- TEST_DO(f1.assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3", "reconf db _alwaysthere_ 3"}));
+ applyInitialConfig();
+ reconfigure();
+ allowReconfig();
+ disableReconfig();
+ reconfigure();
+ assertLog({"initial dbs _alwaysthere_", "apply config 2", "add db _alwaysthere_ 2", "apply config 3", "reconf db _alwaysthere_ 3"});
}
-TEST_MAIN() { TEST_RUN_ALL(); }
+TEST_F(ProtonConfigurerTest, require_that_bucket_space_for_document_type_change_exits)
+{
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+ addDocType("globaldoc", "default");
+ applyInitialConfig();
+ removeDocType("globaldoc");
+ addDocType("globaldoc", "global");
+ allowReconfig();
+ EXPECT_EXIT(reconfigure(), ::testing::ExitedWithCode(1), "Bucket space for document type globaldoc changed from default to global");
+}
+
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
index fd7069c618a..2f5ee2f22cb 100644
--- a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
+++ b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
@@ -1,6 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/searchcommon/common/schema.h>
#include <vespa/searchlib/fef/indexproperties.h>
@@ -154,7 +154,7 @@ struct Model {
}
bool verify() {
generate();
- return vespalib::SlaveProc::run(vespalib::make_string("%s dir:%s", prog, gen_dir.c_str()).c_str());
+ return vespalib::ChildProcess::run(vespalib::make_string("%s dir:%s", prog, gen_dir.c_str()).c_str());
}
void verify_valid(std::initializer_list<std::string> features) {
for (const std::string &f: features) {
@@ -207,12 +207,12 @@ struct ShadowModel : Model {
};
TEST_F("print usage", Model()) {
- EXPECT_TRUE(!vespalib::SlaveProc::run(vespalib::make_string("%s", prog).c_str()));
+ EXPECT_TRUE(!vespalib::ChildProcess::run(vespalib::make_string("%s", prog).c_str()));
}
TEST_F("setup output directory", Model()) {
- ASSERT_TRUE(vespalib::SlaveProc::run(vespalib::make_string("rm -rf %s", gen_dir.c_str()).c_str()));
- ASSERT_TRUE(vespalib::SlaveProc::run(vespalib::make_string("mkdir %s", gen_dir.c_str()).c_str()));
+ ASSERT_TRUE(vespalib::ChildProcess::run(vespalib::make_string("rm -rf %s", gen_dir.c_str()).c_str()));
+ ASSERT_TRUE(vespalib::ChildProcess::run(vespalib::make_string("mkdir %s", gen_dir.c_str()).c_str()));
}
//-----------------------------------------------------------------------------
@@ -317,7 +317,7 @@ TEST_F("require that imported attribute field can be used by rank feature", Simp
//-----------------------------------------------------------------------------
TEST_F("cleanup files", Model()) {
- ASSERT_TRUE(vespalib::SlaveProc::run(vespalib::make_string("rm -rf %s", gen_dir.c_str()).c_str()));
+ ASSERT_TRUE(vespalib::ChildProcess::run(vespalib::make_string("rm -rf %s", gen_dir.c_str()).c_str()));
}
TEST_MAIN_WITH_PROCESS_PROXY() { TEST_RUN_ALL(); }
diff --git a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
index 0869fc175a7..0dbffe2402a 100644
--- a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
+++ b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.cpp
@@ -181,38 +181,6 @@ DocsumContext::FillRankFeatures(search::docsummary::GetDocsumsState * state, sea
state->_rankFeatures = _matcher->getRankFeatures(_request, _searchCtx, _attrCtx, _sessionMgr);
}
-namespace {
-Location *getLocation(const string &loc_str, search::IAttributeManager &attrMgr)
-{
- LOG(debug, "Filling document locations from location string: %s", loc_str.c_str());
-
- Location *loc = new Location;
- string location;
- string::size_type pos = loc_str.find(':');
- if (pos != string::npos) {
- string view = loc_str.substr(0, pos);
- AttributeGuard::UP vec = attrMgr.getAttribute(view);
- if (!vec->valid()) {
- view = PositionDataType::getZCurveFieldName(view);
- vec = attrMgr.getAttribute(view);
- }
- loc->setVecGuard(std::move(vec));
- location = loc_str.substr(pos + 1);
- } else {
- LOG(warning, "Location string lacks attribute vector specification. loc='%s'", loc_str.c_str());
- location = loc_str;
- }
- loc->parse(location);
- return loc;
-}
-} // namespace
-
-void
-DocsumContext::ParseLocation(search::docsummary::GetDocsumsState *state)
-{
- state->_parsedLocation.reset(getLocation(_request.location, _attrMgr));
-}
-
std::unique_ptr<MatchingElements>
DocsumContext::fill_matching_elements(const MatchingElementsFields &fields)
{
diff --git a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.h b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.h
index 1624048828f..d1b656915d9 100644
--- a/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.h
+++ b/searchcore/src/vespa/searchcore/proton/docsummary/docsumcontext.h
@@ -52,7 +52,6 @@ public:
// Implements GetDocsumsStateCallback
void FillSummaryFeatures(search::docsummary::GetDocsumsState * state, search::docsummary::IDocsumEnvironment * env) override;
void FillRankFeatures(search::docsummary::GetDocsumsState * state, search::docsummary::IDocsumEnvironment * env) override;
- void ParseLocation(search::docsummary::GetDocsumsState * state) override;
std::unique_ptr<search::MatchingElements> fill_matching_elements(const search::MatchingElementsFields &fields) override;
};
diff --git a/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt
index 558914805d1..ffbab597118 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt
+++ b/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt
@@ -27,6 +27,7 @@ vespa_add_library(searchcore_matching STATIC
querynodes.cpp
ranking_constants.cpp
requestcontext.cpp
+ resolveviewvisitor.cpp
result_processor.cpp
same_element_builder.cpp
sameelementmodifier.cpp
diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.cpp b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
index 62a59ab7680..65959e6e6de 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/query.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
@@ -8,10 +8,9 @@
#include "sameelementmodifier.h"
#include "unpacking_iterators_optimizer.h"
#include <vespa/document/datatype/positiondatatype.h>
-#include <vespa/searchlib/common/location.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
+#include <vespa/searchlib/common/geo_location_parser.h>
#include <vespa/searchlib/parsequery/stackdumpiterator.h>
-#include <vespa/searchlib/query/tree/point.h>
-#include <vespa/searchlib/query/tree/rectangle.h>
#include <vespa/searchlib/queryeval/intermediate_blueprints.h>
#include <vespa/log/log.h>
@@ -20,11 +19,13 @@ LOG_SETUP(".proton.matching.query");
using document::PositionDataType;
using search::SimpleQueryStackDumpIterator;
+using search::common::GeoLocation;
+using search::common::GeoLocationParser;
+using search::common::GeoLocationSpec;
using search::fef::IIndexEnvironment;
using search::fef::ITermData;
using search::fef::MatchData;
using search::fef::MatchDataLayout;
-using search::fef::Location;
using search::query::Node;
using search::query::QueryTreeCreator;
using search::query::Weight;
@@ -58,37 +59,73 @@ inject(Node::UP query, Node::UP to_inject) {
return query;
}
-void
-addLocationNode(const string &location_str, Node::UP &query_tree, Location &fef_location) {
- if (location_str.empty()) {
- return;
- }
- string::size_type pos = location_str.find(':');
- if (pos == string::npos) {
- LOG(warning, "Location string lacks attribute vector specification. loc='%s'", location_str.c_str());
- return;
+std::vector<ProtonLocationTerm *>
+find_location_terms(Node *tree) {
+ std::vector<ProtonLocationTerm *> retval;
+ std::vector<Node *> nodes;
+ nodes.push_back(tree);
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ if (auto loc = dynamic_cast<ProtonLocationTerm *>(nodes[i])) {
+ retval.push_back(loc);
+ }
+ if (auto parent = dynamic_cast<const search::query::Intermediate *>(nodes[i])) {
+ for (Node * child : parent->getChildren()) {
+ nodes.push_back(child);
+ }
+ }
}
- const string view = PositionDataType::getZCurveFieldName(location_str.substr(0, pos));
- const string loc = location_str.substr(pos + 1);
+ return retval;
+}
- search::common::Location locationSpec;
- if (!locationSpec.parse(loc)) {
- LOG(warning, "Location parse error (location: '%s'): %s", location_str.c_str(), locationSpec.getParseError());
- return;
+GeoLocationSpec parse_location_string(string str) {
+ GeoLocationSpec empty;
+ if (str.empty()) {
+ return empty;
}
+ GeoLocationParser parser;
+ if (parser.parseOldFormatWithField(str)) {
+ auto attr_name = PositionDataType::getZCurveFieldName(parser.getFieldName());
+ return GeoLocationSpec{attr_name, parser.getGeoLocation()};
+ } else {
+ LOG(warning, "Location parse error (location: '%s'): %s", str.c_str(), parser.getParseError());
+ }
+ return empty;
+}
+
+GeoLocationSpec process_location_term(ProtonLocationTerm &pterm) {
+ auto old_view = pterm.getView();
+ auto new_view = PositionDataType::getZCurveFieldName(old_view);
+ pterm.setView(new_view);
+ const GeoLocation &loc = pterm.getTerm();
+ return GeoLocationSpec{new_view, loc};
+}
+
+void exchange_location_nodes(const string &location_str,
+ Node::UP &query_tree,
+ std::vector<GeoLocationSpec> &fef_locations)
+{
+ std::vector<GeoLocationSpec> locationSpecs;
- int32_t id = -1;
- Weight weight(100);
-
- if (locationSpec.getRankOnDistance()) {
- query_tree = inject(std::move(query_tree), std::make_unique<ProtonLocationTerm>(loc, view, id, weight));
- fef_location.setAttribute(view);
- fef_location.setXPosition(locationSpec.getX());
- fef_location.setYPosition(locationSpec.getY());
- fef_location.setXAspect(locationSpec.getXAspect());
- fef_location.setValid(true);
- } else if (locationSpec.getPruneOnDistance()) {
- query_tree = inject(std::move(query_tree), std::make_unique<ProtonLocationTerm>(loc, view, id, weight));
+ auto parsed = parse_location_string(location_str);
+ if (parsed.location.valid()) {
+ locationSpecs.push_back(parsed);
+ }
+ for (ProtonLocationTerm * pterm : find_location_terms(query_tree.get())) {
+ auto spec = process_location_term(*pterm);
+ if (spec.location.valid()) {
+ locationSpecs.push_back(spec);
+ }
+ }
+ for (const GeoLocationSpec &spec : locationSpecs) {
+ if (spec.location.has_point) {
+ fef_locations.push_back(spec);
+ }
+ }
+ if (parsed.location.can_limit()) {
+ int32_t id = -1;
+ Weight weight(100);
+ query_tree = inject(std::move(query_tree),
+ std::make_unique<ProtonLocationTerm>(parsed.location, parsed.field_name, id, weight));
}
}
@@ -127,7 +164,7 @@ Query::buildTree(vespalib::stringref stack, const string &location,
if (_query_tree) {
SameElementModifier prefixSameElementSubIndexes;
_query_tree->accept(prefixSameElementSubIndexes);
- addLocationNode(location, _query_tree, _location);
+ exchange_location_nodes(location, _query_tree, _locations);
_query_tree = UnpackingIteratorsOptimizer::optimize(std::move(_query_tree),
bool(_whiteListBlueprint), split_unpacking_iterators, delay_unpacking_iterators);
ResolveViewVisitor resolve_visitor(resolver, indexEnv);
@@ -146,10 +183,12 @@ Query::extractTerms(vector<const ITermData *> &terms)
}
void
-Query::extractLocations(vector<const Location *> &locations)
+Query::extractLocations(vector<const GeoLocationSpec *> &locations)
{
locations.clear();
- locations.push_back(&_location);
+ for (const auto & loc : _locations) {
+ locations.push_back(&loc);
+ }
}
void
diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.h b/searchcore/src/vespa/searchcore/proton/matching/query.h
index 60f40e24d1e..952b6260da1 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/query.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/query.h
@@ -2,7 +2,7 @@
#pragma once
-#include <vespa/searchlib/fef/location.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
#include <vespa/searchlib/fef/itermdata.h>
#include <vespa/searchlib/fef/matchdatalayout.h>
#include <vespa/searchlib/fef/iindexenvironment.h>
@@ -18,13 +18,16 @@ class ISearchContext;
class Query
{
private:
- using Blueprint=search::queryeval::Blueprint;
+ using Blueprint = search::queryeval::Blueprint;
search::query::Node::UP _query_tree;
Blueprint::UP _blueprint;
- search::fef::Location _location;
Blueprint::UP _whiteListBlueprint;
+ std::vector<search::common::GeoLocationSpec> _locations;
public:
+ /** Convenience typedef. */
+ using GeoLocationSpecPtrs = std::vector<const search::common::GeoLocationSpec *>;
+
Query();
~Query();
/**
@@ -65,7 +68,7 @@ public:
*
* @param locs where to collect locations
**/
- void extractLocations(std::vector<const search::fef::Location *> &locs);
+ void extractLocations(GeoLocationSpecPtrs &locs);
/**
* Reserve room for terms in the query in the given match data
diff --git a/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.cpp b/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.cpp
index fe0f6aaff91..448ce14dd51 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.cpp
@@ -5,7 +5,6 @@
using search::attribute::IAttributeContext;
using search::fef::IIndexEnvironment;
-using search::fef::Location;
using search::fef::Properties;
namespace proton::matching {
@@ -17,7 +16,7 @@ QueryEnvironment::QueryEnvironment(const IIndexEnvironment &indexEnv,
: _indexEnv(indexEnv),
_attrContext(attrContext),
_properties(properties),
- _locations(1),
+ _locations(),
_terms(),
_field_length_inspector(field_length_inspector)
{
@@ -44,12 +43,6 @@ QueryEnvironment::getTerm(uint32_t idx) const
return _terms[idx];
}
-const search::fef::Location &
-QueryEnvironment::getLocation() const
-{
- return *_locations[0];
-}
-
const IAttributeContext &
QueryEnvironment::getAttributeContext() const
{
diff --git a/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.h b/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.h
index 575694ae079..6daf488297d 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/queryenvironment.h
@@ -4,7 +4,6 @@
#include <vespa/searchlib/fef/iqueryenvironment.h>
#include <vespa/searchlib/fef/properties.h>
-#include <vespa/searchlib/fef/location.h>
namespace search::index { class IFieldLengthInspector; }
@@ -19,7 +18,7 @@ private:
const search::fef::IIndexEnvironment &_indexEnv;
const search::attribute::IAttributeContext &_attrContext;
search::fef::Properties _properties;
- std::vector<const search::fef::Location *> _locations;
+ GeoLocationSpecPtrs _locations;
std::vector<const search::fef::ITermData *> _terms;
const search::index::IFieldLengthInspector &_field_length_inspector;
@@ -56,7 +55,7 @@ public:
*
* @return modifiable list of location data pointers
**/
- std::vector<const search::fef::Location *> &locations() {
+ GeoLocationSpecPtrs &locations() {
return _locations;
}
@@ -70,7 +69,9 @@ public:
const search::fef::ITermData *getTerm(uint32_t idx) const override;
// inherited from search::fef::IQueryEnvironment
- const search::fef::Location & getLocation() const override;
+ GeoLocationSpecPtrs getAllLocations() const override {
+ return _locations;
+ }
// inherited from search::fef::IQueryEnvironment
const search::attribute::IAttributeContext & getAttributeContext() const override;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.cpp b/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.cpp
new file mode 100644
index 00000000000..b12b48465d9
--- /dev/null
+++ b/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.cpp
@@ -0,0 +1,26 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "resolveviewvisitor.h"
+#include <vespa/document/datatype/positiondatatype.h>
+#include <vespa/log/log.h>
+
+LOG_SETUP(".proton.matching.resolveviewvisitor");
+
+namespace proton::matching {
+
+void
+ResolveViewVisitor::visit(ProtonLocationTerm &n) {
+ // if injected by query.cpp, this should work:
+ n.resolve(_resolver, _indexEnv);
+ if (n.numFields() == 0) {
+ // if received from QRS, this is needed:
+ auto oldView = n.getView();
+ auto newView = document::PositionDataType::getZCurveFieldName(oldView);
+ n.setView(newView);
+ n.resolve(_resolver, _indexEnv);
+ LOG(debug, "ProtonLocationTerm found %zu field after view change %s -> %s",
+ n.numFields(), oldView.c_str(), newView.c_str());
+ }
+}
+
+} // namespace
diff --git a/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.h b/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.h
index 4a12e6adda9..f8c1a007c28 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/resolveviewvisitor.h
@@ -22,6 +22,8 @@ public:
template <class TermNode>
void visitTerm(TermNode &n) { n.resolve(_resolver, _indexEnv); }
+ void visit(ProtonLocationTerm &n) override;
+
void visit(ProtonNodeTypes::Equiv &n) override {
visitChildren(n);
n.resolveFromChildren(n.getChildren());
diff --git a/searchcore/src/vespa/searchcore/proton/server/i_document_db_config_owner.h b/searchcore/src/vespa/searchcore/proton/server/i_document_db_config_owner.h
index 10a47e7c6e4..feb33e11721 100644
--- a/searchcore/src/vespa/searchcore/proton/server/i_document_db_config_owner.h
+++ b/searchcore/src/vespa/searchcore/proton/server/i_document_db_config_owner.h
@@ -4,6 +4,8 @@
#include <memory>
+namespace document { class BucketSpace; }
+
namespace proton {
class DocumentDBConfig;
@@ -15,6 +17,7 @@ class IDocumentDBConfigOwner
{
public:
virtual ~IDocumentDBConfigOwner() { }
+ virtual document::BucketSpace getBucketSpace() const = 0;
virtual void reconfigure(const std::shared_ptr<DocumentDBConfig> & config) = 0;
};
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.cpp b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
index 1e579739d85..b297dd860ea 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
@@ -223,6 +223,7 @@ Proton::Proton(const config::ConfigUri & configUri,
_initStarted(false),
_initComplete(false),
_initDocumentDbsInSequence(false),
+ _has_shut_down_config_and_state_components(false),
_documentDBReferenceRegistry(std::make_shared<DocumentDBReferenceRegistry>()),
_nodeUpLock(),
_nodeUp()
@@ -400,16 +401,8 @@ Proton::~Proton()
if ( ! _initComplete ) {
LOG(warning, "Initialization of proton was halted. Shutdown sequence has been initiated.");
}
- _protonConfigFetcher.close();
- _protonConfigurer.setAllowReconfig(false);
+ shutdown_config_fetching_and_state_exposing_components_once();
_executor.sync();
- _customComponentRootToken.reset();
- _customComponentBindToken.reset();
- _stateServer.reset();
- if (_metricsEngine) {
- _metricsEngine->removeMetricsHook(_metricsHook);
- _metricsEngine->stop();
- }
if (_matchEngine) {
_matchEngine->close();
}
@@ -463,6 +456,25 @@ Proton::~Proton()
}
void
+Proton::shutdown_config_fetching_and_state_exposing_components_once() noexcept
+{
+ if (_has_shut_down_config_and_state_components) {
+ return;
+ }
+ _protonConfigFetcher.close();
+ _protonConfigurer.setAllowReconfig(false);
+ _executor.sync();
+ _customComponentRootToken.reset();
+ _customComponentBindToken.reset();
+ _stateServer.reset();
+ if (_metricsEngine) {
+ _metricsEngine->removeMetricsHook(_metricsHook);
+ _metricsEngine->stop();
+ }
+ _has_shut_down_config_and_state_components = true;
+}
+
+void
Proton::closeDocumentDBs(vespalib::ThreadStackExecutorBase & executor) {
// Need to extract names first as _documentDBMap is modified while removing.
std::vector<DocTypeName> docTypes;
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.h b/searchcore/src/vespa/searchcore/proton/server/proton.h
index d5c1a8b7b78..6b561ad5deb 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.h
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.h
@@ -124,6 +124,7 @@ private:
bool _initStarted;
bool _initComplete;
bool _initDocumentDbsInSequence;
+ bool _has_shut_down_config_and_state_components;
std::shared_ptr<IDocumentDBReferenceRegistry> _documentDBReferenceRegistry;
std::mutex _nodeUpLock;
std::set<BucketSpace> _nodeUp; // bucketspaces where node is up
@@ -169,6 +170,15 @@ public:
*/
BootstrapConfig::SP init();
+ /**
+ * Shuts down metric manager and state server functionality to avoid
+ * calls to these during service layer component tear-down.
+ *
+ * Explicitly noexcept to avoid consistency issues between this and the
+ * destructor if something throws during shutdown.
+ */
+ void shutdown_config_fetching_and_state_exposing_components_once() noexcept;
+
// 2nd phase init: setup data structures.
void init(const BootstrapConfig::SP & configSnapshot);
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
index 45e3c978dd9..a7dada3047c 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
@@ -15,6 +15,9 @@
#include <vespa/vespalib/stllike/asciistream.h>
#include <future>
+#include <vespa/log/log.h>
+LOG_SETUP(".proton.server.proton_configurer");
+
using vespalib::makeLambdaTask;
using vespa::config::search::core::ProtonConfig;
@@ -177,6 +180,13 @@ ProtonConfigurer::configureDocumentDB(const ProtonConfigSnapshot &configSnapshot
} else {
auto documentDB = dbitr->second.first.lock();
assert(documentDB);
+ auto old_bucket_space = documentDB->getBucketSpace();
+ if (bucketSpace != old_bucket_space) {
+ vespalib::string old_bucket_space_name = document::FixedBucketSpaces::to_string(old_bucket_space);
+ vespalib::string bucket_space_name = document::FixedBucketSpaces::to_string(bucketSpace);
+ LOG(fatal, "Bucket space for document type %s changed from %s to %s. This triggers undefined behavior on a running system. Restarting process immediately to fix it.", docTypeName.getName().c_str(), old_bucket_space_name.c_str(), bucket_space_name.c_str());
+ std::_Exit(1);
+ }
documentDB->reconfigure(documentDBConfig);
}
}
diff --git a/searchlib/src/tests/attribute/document_weight_iterator/document_weight_iterator_test.cpp b/searchlib/src/tests/attribute/document_weight_iterator/document_weight_iterator_test.cpp
index a64704a08e9..cf1506a9118 100644
--- a/searchlib/src/tests/attribute/document_weight_iterator/document_weight_iterator_test.cpp
+++ b/searchlib/src/tests/attribute/document_weight_iterator/document_weight_iterator_test.cpp
@@ -17,7 +17,6 @@
#include <vespa/searchlib/attribute/singlestringattribute.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
#include <vespa/searchlib/query/tree/location.h>
-#include <vespa/searchlib/query/tree/point.h>
#include <vespa/searchlib/query/tree/simplequery.h>
#include <vespa/searchlib/queryeval/document_weight_search_iterator.h>
#include <vespa/searchlib/test/searchiteratorverifier.h>
diff --git a/searchlib/src/tests/attribute/searchable/attribute_searchable_adapter_test.cpp b/searchlib/src/tests/attribute/searchable/attribute_searchable_adapter_test.cpp
index 2eafeab20bd..87aea2e3e8c 100644
--- a/searchlib/src/tests/attribute/searchable/attribute_searchable_adapter_test.cpp
+++ b/searchlib/src/tests/attribute/searchable/attribute_searchable_adapter_test.cpp
@@ -339,29 +339,43 @@ TEST("requireThatPrefixTermsWork") {
TEST("requireThatLocationTermsWork") {
// 0xcc is z-curve for (10, 10).
MyAttributeManager attribute_manager = makeAttributeManager(int64_t(0xcc));
-
- SimpleLocationTerm node(Location(Point(10, 10), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(100, 100), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(!search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(13, 13), 4, 0), field, 0, Weight(0));
- EXPECT_TRUE(!search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(10, 13), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(search(node, attribute_manager));
+ {
+ SimpleLocationTerm node(Location(Point{10, 10}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(search(node, attribute_manager));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{100, 100}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!search(node, attribute_manager));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{13, 13}, 4, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!search(node, attribute_manager));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{10, 13}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(search(node, attribute_manager));
+ }
}
TEST("requireThatOptimizedLocationTermsWork") {
// 0xcc is z-curve for (10, 10).
MyAttributeManager attribute_manager = makeFastSearchLongAttributeManager(int64_t(0xcc));
-
- SimpleLocationTerm node(Location(Point(10, 10), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(search(node, attribute_manager, true));
- node = SimpleLocationTerm(Location(Point(100, 100), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(!search(node, attribute_manager, true));
- node = SimpleLocationTerm(Location(Point(13, 13), 4, 0), field, 0, Weight(0));
- EXPECT_TRUE(!search(node, attribute_manager, true));
- node = SimpleLocationTerm(Location(Point(10, 13), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(search(node, attribute_manager, true));
+ {
+ SimpleLocationTerm node(Location(Point{10, 10}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(search(node, attribute_manager, true));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{100, 100}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!search(node, attribute_manager, true));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{13, 13}, 4, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!search(node, attribute_manager, true));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{10, 13}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(search(node, attribute_manager, true));
+ }
}
TEST("require that optimized location search works with wrapped bounding box (no hits)") {
diff --git a/searchlib/src/tests/attribute/searchable/attributeblueprint_test.cpp b/searchlib/src/tests/attribute/searchable/attributeblueprint_test.cpp
index 5e633dcc97d..3098232b443 100644
--- a/searchlib/src/tests/attribute/searchable/attributeblueprint_test.cpp
+++ b/searchlib/src/tests/attribute/searchable/attributeblueprint_test.cpp
@@ -254,15 +254,22 @@ TEST(AttributeBlueprintTest, require_that_location_terms_work)
{
// 0xcc is z-curve for (10, 10).
auto attribute_manager = makeAttributeManager(int64_t(0xcc));
-
- SimpleLocationTerm node(Location(Point(10, 10), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(do_search(node, attribute_manager, false));
- node = SimpleLocationTerm(Location(Point(100, 100), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(!do_search(node, attribute_manager, false));
- node = SimpleLocationTerm(Location(Point(13, 13), 4, 0), field, 0, Weight(0));
- EXPECT_TRUE(!do_search(node, attribute_manager, false));
- node = SimpleLocationTerm(Location(Point(10, 13), 3, 0), field, 0, Weight(0));
- EXPECT_TRUE(do_search(node, attribute_manager, false));
+ {
+ SimpleLocationTerm node(Location(Point{10, 10}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(do_search(node, attribute_manager, false));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{100, 100}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!do_search(node, attribute_manager, false));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{13, 13}, 4, 0), field, 0, Weight(0));
+ EXPECT_TRUE(!do_search(node, attribute_manager, false));
+ }
+ {
+ SimpleLocationTerm node(Location(Point{10, 13}, 3, 0), field, 0, Weight(0));
+ EXPECT_TRUE(do_search(node, attribute_manager, false));
+ }
}
TEST(AttributeBlueprintTest, require_that_fast_search_location_terms_work)
@@ -270,14 +277,14 @@ TEST(AttributeBlueprintTest, require_that_fast_search_location_terms_work)
// 0xcc is z-curve for (10, 10).
auto attribute_manager = makeFastSearchLongAttribute(int64_t(0xcc));
- SimpleLocationTerm node(Location(Point(10, 10), 3, 0), field, 0, Weight(0));
+ SimpleLocationTerm node(Location(Point{10, 10}, 3, 0), field, 0, Weight(0));
#if 0
EXPECT_TRUE(search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(100, 100), 3, 0),field, 0, Weight(0));
+ node = SimpleLocationTerm(Location(Point{100, 100}, 3, 0),field, 0, Weight(0));
EXPECT_TRUE(!search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(13, 13), 4, 0),field, 0, Weight(0));
+ node = SimpleLocationTerm(Location(Point{13, 13}, 4, 0),field, 0, Weight(0));
EXPECT_TRUE(!search(node, attribute_manager));
- node = SimpleLocationTerm(Location(Point(10, 13), 3, 0),field, 0, Weight(0));
+ node = SimpleLocationTerm(Location(Point{10, 13}, 3, 0),field, 0, Weight(0));
EXPECT_TRUE(search(node, attribute_manager));
#endif
}
diff --git a/searchlib/src/tests/common/location/CMakeLists.txt b/searchlib/src/tests/common/location/CMakeLists.txt
index 64a894096d5..ea0d96529e1 100644
--- a/searchlib/src/tests/common/location/CMakeLists.txt
+++ b/searchlib/src/tests/common/location/CMakeLists.txt
@@ -1,8 +1,9 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(searchlib_location_test_app TEST
+vespa_add_executable(searchlib_geo_location_test_app TEST
SOURCES
- location_test.cpp
+ geo_location_test.cpp
DEPENDS
searchlib
+ GTest::GTest
)
-vespa_add_test(NAME searchlib_location_test_app COMMAND searchlib_location_test_app)
+vespa_add_test(NAME searchlib_geo_location_test_app COMMAND searchlib_geo_location_test_app)
diff --git a/searchlib/src/tests/common/location/geo_location_test.cpp b/searchlib/src/tests/common/location/geo_location_test.cpp
new file mode 100644
index 00000000000..31b844d0fc8
--- /dev/null
+++ b/searchlib/src/tests/common/location/geo_location_test.cpp
@@ -0,0 +1,394 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <stdio.h>
+#include <vespa/searchlib/common/geo_location.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
+#include <vespa/searchlib/common/geo_location_parser.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using search::common::GeoLocation;
+using search::common::GeoLocationParser;
+
+using Box = search::common::GeoLocation::Box;
+using Point = search::common::GeoLocation::Point;
+using Range = search::common::GeoLocation::Range;
+using Aspect = search::common::GeoLocation::Aspect;
+
+constexpr int32_t plus_inf = std::numeric_limits<int32_t>::max();
+constexpr int32_t minus_inf = std::numeric_limits<int32_t>::min();
+constexpr uint32_t u32_inf = std::numeric_limits<uint32_t>::max();
+
+bool is_parseable(const char *str) {
+ GeoLocationParser parser;
+ return parser.parseOldFormat(str);
+}
+
+GeoLocation parse(const char *str) {
+ GeoLocationParser parser;
+ EXPECT_TRUE(parser.parseOldFormat(str));
+ return parser.getGeoLocation();
+}
+
+TEST(GeoLocationParserTest, malformed_bounding_boxes_are_not_parseable) {
+ EXPECT_TRUE(is_parseable("[2,10,20,30,40]"));
+ EXPECT_FALSE(is_parseable("[2,10,20,30,40][2,10,20,30,40]"));
+ EXPECT_FALSE(is_parseable("[1,10,20,30,40]"));
+ EXPECT_FALSE(is_parseable("[3,10,20,30,40]"));
+ EXPECT_FALSE(is_parseable("[2, 10, 20, 30, 40]"));
+ EXPECT_FALSE(is_parseable("[2,10,20,30,40"));
+ EXPECT_FALSE(is_parseable("[2,10,20,30]"));
+ EXPECT_FALSE(is_parseable("[10,20,30,40]"));
+}
+
+TEST(GeoLocationParserTest, malformed_circles_are_not_parseable) {
+ EXPECT_TRUE(is_parseable("(2,10,20,5,0,0,0)"));
+ EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0)(2,10,20,5,0,0,0)"));
+ EXPECT_FALSE(is_parseable("(1,10,20,5,0,0,0)"));
+ EXPECT_FALSE(is_parseable("(3,10,20,5,0,0,0)"));
+ EXPECT_FALSE(is_parseable("(2, 10, 20, 5, 0, 0, 0)"));
+ EXPECT_FALSE(is_parseable("(2,10,20,5)"));
+ EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0"));
+ EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0,1000"));
+ EXPECT_FALSE(is_parseable("(10,20,5)"));
+}
+
+TEST(GeoLocationParserTest, bounding_boxes_can_be_parsed) {
+ auto loc = parse("[2,10,20,30,40]");
+ EXPECT_EQ(false, loc.has_point);
+ EXPECT_EQ(true, loc.bounding_box.active());
+ EXPECT_EQ(0u, loc.x_aspect.multiplier);
+ EXPECT_EQ(0, loc.point.x);
+ EXPECT_EQ(0, loc.point.y);
+ EXPECT_EQ(std::numeric_limits<uint32_t>::max(), loc.radius);
+ EXPECT_EQ(10, loc.bounding_box.x.low);
+ EXPECT_EQ(20, loc.bounding_box.y.low);
+ EXPECT_EQ(30, loc.bounding_box.x.high);
+ EXPECT_EQ(40, loc.bounding_box.y.high);
+}
+
+TEST(GeoLocationParserTest, circles_can_be_parsed) {
+ auto loc = parse("(2,10,20,5,0,0,0)");
+ EXPECT_EQ(true, loc.has_point);
+ EXPECT_EQ(true, loc.bounding_box.active());
+ EXPECT_EQ(0u, loc.x_aspect.multiplier);
+ EXPECT_EQ(10, loc.point.x);
+ EXPECT_EQ(20, loc.point.y);
+ EXPECT_EQ(5u, loc.radius);
+ EXPECT_EQ(5, loc.bounding_box.x.low);
+ EXPECT_EQ(15, loc.bounding_box.y.low);
+ EXPECT_EQ(15, loc.bounding_box.x.high);
+ EXPECT_EQ(25, loc.bounding_box.y.high);
+}
+
+TEST(GeoLocationParserTest, circles_can_have_aspect_ratio) {
+ auto loc = parse("(2,10,20,5,0,0,0,2147483648)");
+ EXPECT_EQ(true, loc.has_point);
+ EXPECT_EQ(true, loc.bounding_box.active());
+ EXPECT_EQ(2147483648u, loc.x_aspect.multiplier);
+ EXPECT_EQ(10, loc.point.x);
+ EXPECT_EQ(20, loc.point.y);
+ EXPECT_EQ(5u, loc.radius);
+ EXPECT_EQ(-1, loc.bounding_box.x.low);
+ EXPECT_EQ(15, loc.bounding_box.y.low);
+ EXPECT_EQ(21, loc.bounding_box.x.high);
+ EXPECT_EQ(25, loc.bounding_box.y.high);
+}
+
+TEST(GeoLocationParserTest, bounding_box_can_be_specified_after_circle) {
+ auto loc = parse("(2,10,20,5,0,0,0)[2,10,20,30,40]");
+ EXPECT_EQ(true, loc.has_point);
+ EXPECT_EQ(true, loc.bounding_box.active());
+ EXPECT_EQ(0u, loc.x_aspect.multiplier);
+ EXPECT_EQ(10, loc.point.x);
+ EXPECT_EQ(20, loc.point.y);
+ EXPECT_EQ(5u, loc.radius);
+ EXPECT_EQ(10, loc.bounding_box.x.low);
+ EXPECT_EQ(20, loc.bounding_box.y.low);
+ EXPECT_EQ(15, loc.bounding_box.x.high);
+ EXPECT_EQ(25, loc.bounding_box.y.high);
+}
+
+TEST(GeoLocationParserTest, circles_can_be_specified_after_bounding_box) {
+ auto loc = parse("[2,10,20,30,40](2,10,20,5,0,0,0)");
+ EXPECT_EQ(true, loc.has_point);
+ EXPECT_EQ(true, loc.bounding_box.active());
+ EXPECT_EQ(0u, loc.x_aspect.multiplier);
+ EXPECT_EQ(10, loc.point.x);
+ EXPECT_EQ(20, loc.point.y);
+ EXPECT_EQ(5u, loc.radius);
+ EXPECT_EQ(10, loc.bounding_box.x.low);
+ EXPECT_EQ(20, loc.bounding_box.y.low);
+ EXPECT_EQ(15, loc.bounding_box.x.high);
+ EXPECT_EQ(25, loc.bounding_box.y.high);
+}
+
+TEST(GeoLocationParserTest, santa_search_gives_non_wrapped_bounding_box) {
+ auto loc = parse("(2,122163600,89998536,290112,4,2000,0,109704)");
+ EXPECT_GE(loc.bounding_box.x.high, loc.bounding_box.x.low);
+ EXPECT_GE(loc.bounding_box.y.high, loc.bounding_box.y.low);
+}
+
+TEST(GeoLocationParserTest, near_boundary_search_gives_non_wrapped_bounding_box) {
+ auto loc1 = parse("(2,2000000000,2000000000,3000000000,0,1,0)");
+ EXPECT_GE(loc1.bounding_box.x.high, loc1.bounding_box.x.low);
+ EXPECT_GE(loc1.bounding_box.y.high, loc1.bounding_box.y.low);
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), loc1.bounding_box.y.high);
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), loc1.bounding_box.y.high);
+
+ auto loc2 = parse("(2,-2000000000,-2000000000,3000000000,0,1,0)");
+ EXPECT_GE(loc2.bounding_box.x.high, loc2.bounding_box.x.low);
+ EXPECT_GE(loc2.bounding_box.y.high, loc2.bounding_box.y.low);
+ EXPECT_EQ(std::numeric_limits<int32_t>::min(), loc2.bounding_box.x.low);
+ EXPECT_EQ(std::numeric_limits<int32_t>::min(), loc2.bounding_box.y.low);
+}
+
+void check_box(const GeoLocation &location, Box expected)
+{
+ int32_t lx = expected.x.low;
+ int32_t hx = expected.x.high;
+ int32_t ly = expected.y.low;
+ int32_t hy = expected.y.high;
+ EXPECT_TRUE(location.inside_limit(Point{lx,ly}));
+ EXPECT_TRUE(location.inside_limit(Point{lx,hy}));
+ EXPECT_TRUE(location.inside_limit(Point{hx,ly}));
+ EXPECT_TRUE(location.inside_limit(Point{hx,hy}));
+
+ EXPECT_FALSE(location.inside_limit(Point{lx,ly-1}));
+ EXPECT_FALSE(location.inside_limit(Point{lx,hy+1}));
+ EXPECT_FALSE(location.inside_limit(Point{lx-1,ly}));
+ EXPECT_FALSE(location.inside_limit(Point{lx-1,hy}));
+ EXPECT_FALSE(location.inside_limit(Point{hx,ly-1}));
+ EXPECT_FALSE(location.inside_limit(Point{hx,hy+1}));
+ EXPECT_FALSE(location.inside_limit(Point{hx+1,ly}));
+ EXPECT_FALSE(location.inside_limit(Point{hx+1,hy}));
+
+ EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, invalid_location) {
+ GeoLocation invalid;
+ EXPECT_FALSE(invalid.valid());
+ EXPECT_FALSE(invalid.has_radius());
+ EXPECT_FALSE(invalid.can_limit());
+ EXPECT_FALSE(invalid.has_point);
+ EXPECT_FALSE(invalid.bounding_box.active());
+ EXPECT_FALSE(invalid.x_aspect.active());
+
+ EXPECT_EQ(invalid.sq_distance_to(Point{0,0}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{999999,999999}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{-999999,-999999}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{plus_inf,plus_inf}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{minus_inf,minus_inf}), 0);
+
+ EXPECT_TRUE(invalid.inside_limit(Point{0,0}));
+ EXPECT_TRUE(invalid.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(invalid.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(invalid.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(invalid.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_location) {
+ GeoLocation location(Point{300,-400});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_FALSE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_FALSE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(location.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_and_radius) {
+ GeoLocation location(Point{300,-400}, 500);
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{-200,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{800,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{300,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{300,100}));
+ EXPECT_TRUE(location.inside_limit(Point{300,-900}));
+
+ check_box(location, Box{Range{0,600},{-800,0}});
+}
+
+TEST(GeoLocationTest, point_and_aspect) {
+ GeoLocation location(Point{600,400}, Aspect{0.5});
+ // same: GeoLocation location(Point{600,400}, Aspect{1ul << 31});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_FALSE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_FALSE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+ EXPECT_EQ(location.x_aspect.multiplier, 1ul << 31);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{1200,800}), 500*500);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(location.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_radius_and_aspect) {
+ GeoLocation location(Point{1200,400}, 500, Aspect{0.25});
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+ EXPECT_EQ(location.x_aspect.multiplier, 1ul << 30);
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{1200,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{1240,400}), 100);
+
+ EXPECT_TRUE(location.inside_limit(Point{1200,400}));
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{2400,0}));
+ EXPECT_TRUE(location.inside_limit(Point{2400,800}));
+ EXPECT_TRUE(location.inside_limit(Point{0,800}));
+ // note: must be 4 outside since 3*0.25 may be truncated to 0
+ EXPECT_FALSE(location.inside_limit(Point{-4,0}));
+ EXPECT_FALSE(location.inside_limit(Point{-4,800}));
+ EXPECT_FALSE(location.inside_limit(Point{2404,0}));
+ EXPECT_FALSE(location.inside_limit(Point{2404,800}));
+ EXPECT_FALSE(location.inside_limit(Point{2400,-1}));
+ EXPECT_FALSE(location.inside_limit(Point{2400,801}));
+ EXPECT_FALSE(location.inside_limit(Point{0,-1}));
+ EXPECT_FALSE(location.inside_limit(Point{0,801}));
+ EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, box_location) {
+ Box mybox{Range{300,350},Range{400,450}};
+ GeoLocation location(mybox);
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_FALSE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ // currently does not measure distance outside box:
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{350,450}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{450,550}), 0);
+
+ EXPECT_TRUE(location.inside_limit(Point{333,444}));
+ EXPECT_FALSE(location.inside_limit(Point{0,0}));
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_and_point) {
+ Box mybox{Range{287,343},Range{366,401}};
+ GeoLocation location(mybox, Point{300,400});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23);
+
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_point_and_aspect) {
+ Box mybox{Range{-1000,350},Range{-1000,600}};
+ GeoLocation location(mybox, Point{600,400}, Aspect{0.5});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7);
+ EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7);
+
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_point_and_radius) {
+ Box mybox{Range{-1000,350},Range{-1000,600}};
+ GeoLocation location(mybox, Point{300,400}, 500);
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23);
+
+ EXPECT_EQ(location.bounding_box.x.low, -200);
+ EXPECT_EQ(location.bounding_box.y.low, -100);
+ EXPECT_EQ(location.bounding_box.x.high, 350);
+ EXPECT_EQ(location.bounding_box.y.high, 600);
+}
+
+TEST(GeoLocationTest, box_point_radius_and_aspect) {
+ Box mybox{Range{-1000,650},Range{-1000,700}};
+ GeoLocation location(mybox, Point{600,400}, 500, Aspect{0.5});
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7);
+ EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7);
+
+ EXPECT_GE(location.bounding_box.x.low, -402);
+ EXPECT_LE(location.bounding_box.x.low, -400);
+ EXPECT_EQ(location.bounding_box.y.low, -100);
+ EXPECT_EQ(location.bounding_box.x.high, 650);
+ EXPECT_EQ(location.bounding_box.y.high, 700);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/tests/common/location/location_test.cpp b/searchlib/src/tests/common/location/location_test.cpp
deleted file mode 100644
index d781e5b7275..00000000000
--- a/searchlib/src/tests/common/location/location_test.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/searchlib/common/location.h>
-#include <vespa/searchlib/attribute/attributeguard.h>
-
-
-using search::common::Location;
-
-bool is_parseable(const char *str) {
- Location loc;
- return loc.parse(str);
-}
-
-Location parse(const char *str) {
- Location loc;
- if (!EXPECT_TRUE(loc.parse(str))) {
- fprintf(stderr, " parse error: %s\n", loc.getParseError());
- }
- return loc;
-}
-
-TEST("require that malformed bounding boxes are not parseable") {
- EXPECT_TRUE(is_parseable("[2,10,20,30,40]"));
- EXPECT_FALSE(is_parseable("[2,10,20,30,40][2,10,20,30,40]"));
- EXPECT_FALSE(is_parseable("[1,10,20,30,40]"));
- EXPECT_FALSE(is_parseable("[3,10,20,30,40]"));
- EXPECT_FALSE(is_parseable("[2, 10, 20, 30, 40]"));
- EXPECT_FALSE(is_parseable("[2,10,20,30,40"));
- EXPECT_FALSE(is_parseable("[2,10,20,30]"));
- EXPECT_FALSE(is_parseable("[10,20,30,40]"));
-}
-
-TEST("require that malformed circles are not parseable") {
- EXPECT_TRUE(is_parseable("(2,10,20,5,0,0,0)"));
- EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0)(2,10,20,5,0,0,0)"));
- EXPECT_FALSE(is_parseable("(1,10,20,5,0,0,0)"));
- EXPECT_FALSE(is_parseable("(3,10,20,5,0,0,0)"));
- EXPECT_FALSE(is_parseable("(2, 10, 20, 5, 0, 0, 0)"));
- EXPECT_FALSE(is_parseable("(2,10,20,5)"));
- EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0"));
- EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0,1000"));
- EXPECT_FALSE(is_parseable("(10,20,5)"));
-}
-
-TEST("require that bounding boxes can be parsed") {
- Location loc = parse("[2,10,20,30,40]");
- EXPECT_EQUAL(false, loc.getRankOnDistance());
- EXPECT_EQUAL(true, loc.getPruneOnDistance());
- EXPECT_EQUAL(0u, loc.getXAspect());
- EXPECT_EQUAL(0, loc.getX());
- EXPECT_EQUAL(0, loc.getY());
- EXPECT_EQUAL(std::numeric_limits<uint32_t>::max(), loc.getRadius());
- EXPECT_EQUAL(10, loc.getMinX());
- EXPECT_EQUAL(20, loc.getMinY());
- EXPECT_EQUAL(30, loc.getMaxX());
- EXPECT_EQUAL(40, loc.getMaxY());
-}
-
-TEST("require that circles can be parsed") {
- Location loc = parse("(2,10,20,5,0,0,0)");
- EXPECT_EQUAL(true, loc.getRankOnDistance());
- EXPECT_EQUAL(true, loc.getPruneOnDistance());
- EXPECT_EQUAL(0u, loc.getXAspect());
- EXPECT_EQUAL(10, loc.getX());
- EXPECT_EQUAL(20, loc.getY());
- EXPECT_EQUAL(5u, loc.getRadius());
- EXPECT_EQUAL(5, loc.getMinX());
- EXPECT_EQUAL(15, loc.getMinY());
- EXPECT_EQUAL(15, loc.getMaxX());
- EXPECT_EQUAL(25, loc.getMaxY());
-}
-
-TEST("require that circles can have aspect ratio") {
- Location loc = parse("(2,10,20,5,0,0,0,2147483648)");
- EXPECT_EQUAL(true, loc.getRankOnDistance());
- EXPECT_EQUAL(true, loc.getPruneOnDistance());
- EXPECT_EQUAL(2147483648u, loc.getXAspect());
- EXPECT_EQUAL(10, loc.getX());
- EXPECT_EQUAL(20, loc.getY());
- EXPECT_EQUAL(5u, loc.getRadius());
- EXPECT_EQUAL(-1, loc.getMinX());
- EXPECT_EQUAL(15, loc.getMinY());
- EXPECT_EQUAL(21, loc.getMaxX());
- EXPECT_EQUAL(25, loc.getMaxY());
-}
-
-TEST("require that bounding box can be specified after circle") {
- Location loc = parse("(2,10,20,5,0,0,0)[2,10,20,30,40]");
- EXPECT_EQUAL(true, loc.getRankOnDistance());
- EXPECT_EQUAL(true, loc.getPruneOnDistance());
- EXPECT_EQUAL(0u, loc.getXAspect());
- EXPECT_EQUAL(10, loc.getX());
- EXPECT_EQUAL(20, loc.getY());
- EXPECT_EQUAL(5u, loc.getRadius());
- EXPECT_EQUAL(10, loc.getMinX());
- EXPECT_EQUAL(20, loc.getMinY());
- EXPECT_EQUAL(15, loc.getMaxX());
- EXPECT_EQUAL(25, loc.getMaxY());
-}
-
-TEST("require that circles can be specified after bounding box") {
- Location loc = parse("[2,10,20,30,40](2,10,20,5,0,0,0)");
- EXPECT_EQUAL(true, loc.getRankOnDistance());
- EXPECT_EQUAL(true, loc.getPruneOnDistance());
- EXPECT_EQUAL(0u, loc.getXAspect());
- EXPECT_EQUAL(10, loc.getX());
- EXPECT_EQUAL(20, loc.getY());
- EXPECT_EQUAL(5u, loc.getRadius());
- EXPECT_EQUAL(10, loc.getMinX());
- EXPECT_EQUAL(20, loc.getMinY());
- EXPECT_EQUAL(15, loc.getMaxX());
- EXPECT_EQUAL(25, loc.getMaxY());
-}
-
-TEST("require that santa search gives non-wrapped bounding box") {
- Location loc = parse("(2,122163600,89998536,290112,4,2000,0,109704)");
- EXPECT_GREATER_EQUAL(loc.getMaxX(), loc.getMinX());
- EXPECT_GREATER_EQUAL(loc.getMaxY(), loc.getMinY());
-}
-
-TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/prod_features.cpp b/searchlib/src/tests/features/prod_features.cpp
index f886ba59c1c..25b5ba20d26 100644
--- a/searchlib/src/tests/features/prod_features.cpp
+++ b/searchlib/src/tests/features/prod_features.cpp
@@ -62,6 +62,8 @@ using search::StringAttribute;
using search::SingleBoolAttribute;
using search::WeightedSetStringExtAttribute;
using search::attribute::WeightedEnumContent;
+using search::common::GeoLocation;
+using search::common::GeoLocationSpec;
using AttributePtr = AttributeVector::SP;
using AVC = search::attribute::Config;
@@ -507,8 +509,8 @@ Test::assertCloseness(feature_t exp, const vespalib::string & attr, double dista
int32_t x = 0;
positions.emplace_back(x, x);
setupForDistanceTest(ft, "pos", positions, false);
- ft.getQueryEnv().getLocation().setXPosition((int)distance);
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{int32_t(distance), 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{attr, p});
if (maxDistance > 0) {
ft.getIndexEnv().getProperties().add(feature + ".maxDistance",
vespalib::make_string("%u", (unsigned int)maxDistance));
@@ -857,13 +859,16 @@ Test::testDistance()
{ // non-existing attribute
FtFeatureTest ft(_factory, "distance(pos)");
ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, DataType::INT64, "pos");
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{0, 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", p});
+
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
}
{ // label
FtFeatureTest ft(_factory, "distance(label,foo)");
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{0, 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", p});
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().addScore("distance(label,foo)", std::numeric_limits<feature_t>::max())));
}
@@ -873,7 +878,8 @@ Test::testDistance()
pos->commit();
ft.getIndexEnv().getAttributeMap().add(pos);
ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, DataType::INT64, "pos");
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{0, 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", p});
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
}
@@ -883,7 +889,8 @@ Test::testDistance()
pos->commit();
ft.getIndexEnv().getAttributeMap().add(pos);
ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, DataType::INT64, "pos");
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{0, 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", p});
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
}
@@ -893,7 +900,8 @@ Test::testDistance()
pos->commit();
ft.getIndexEnv().getAttributeMap().add(pos);
ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, DataType::INT64, "pos");
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{0, 0};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", p});
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
}
@@ -939,10 +947,9 @@ Test::assert2DZDistance(feature_t exp, const vespalib::string & positions,
pos.emplace_back(x, y);
}
setupForDistanceTest(ft, "pos", pos, true);
- ft.getQueryEnv().getLocation().setXPosition(xquery);
- ft.getQueryEnv().getLocation().setYPosition(yquery);
- ft.getQueryEnv().getLocation().setXAspect(xAspect);
- ft.getQueryEnv().getLocation().setValid(true);
+ GeoLocation::Point p{xquery, yquery};
+ GeoLocation::Aspect aspect{xAspect};
+ ft.getQueryEnv().addLocation(GeoLocationSpec{"pos", {p, aspect}});
ASSERT_TRUE(ft.setup());
ASSERT_TRUE(ft.execute(RankResult().setEpsilon(1e-4).
addScore("distance(pos)", exp)));
diff --git a/searchlib/src/tests/query/query_visitor_test.cpp b/searchlib/src/tests/query/query_visitor_test.cpp
index edbc29be784..8441dc2227f 100644
--- a/searchlib/src/tests/query/query_visitor_test.cpp
+++ b/searchlib/src/tests/query/query_visitor_test.cpp
@@ -90,7 +90,7 @@ void Test::requireThatAllNodesCanBeVisited() {
checkVisit<WandTerm>(new SimpleWandTerm("field", 0, Weight(42), 57, 67, 77.7));
checkVisit<Rank>(new SimpleRank);
checkVisit<NumberTerm>(new SimpleNumberTerm("0.42", "field", 0, Weight(0)));
- const Location location(Point(10, 10), 20, 0);
+ const Location location(Point{10, 10}, 20, 0);
checkVisit<LocationTerm>(new SimpleLocationTerm(location, "field", 0, Weight(0)));
checkVisit<PrefixTerm>(new SimplePrefixTerm("t", "field", 0, Weight(0)));
checkVisit<RangeTerm>(new SimpleRangeTerm(Range(0, 1), "field", 0, Weight(0)));
diff --git a/searchlib/src/tests/query/querybuilder_test.cpp b/searchlib/src/tests/query/querybuilder_test.cpp
index d093bc4242e..269600d26d4 100644
--- a/searchlib/src/tests/query/querybuilder_test.cpp
+++ b/searchlib/src/tests/query/querybuilder_test.cpp
@@ -32,7 +32,7 @@ const size_t distance = 4;
const string int1 = "42";
const string float1 = "3.14";
const Range range(32, 64);
-const Point position(100, 100);
+const Point position{100, 100};
const int max_distance = 20;
const uint32_t x_aspect = 0;
const Location location(position, max_distance, x_aspect);
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
index 4a34a07a773..cb587d77133 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
@@ -248,11 +248,14 @@ public:
LocationPostFilterBlueprint(const FieldSpec &field, const IAttributeVector &attribute, const Location &loc)
: ComplexLeafBlueprint(field),
_attribute(attribute),
- _location()
+ _location(loc)
{
- _location.setVec(attribute);
- _location.parse(loc.getLocationString());
- uint32_t estHits = _attribute.getNumDocs();
+ uint32_t estHits = 0;
+ if (loc.valid()) {
+ _location.setVec(attribute);
+ estHits = _attribute.getNumDocs();
+ }
+ LOG(debug, "location %s in attribute with numdocs %u", loc.getOldFormatString().c_str(), estHits);
HitEstimate estimate(estHits, estHits == 0);
setEstimate(estimate);
}
@@ -272,14 +275,16 @@ Blueprint::UP
make_location_blueprint(const FieldSpec &field, const IAttributeVector &attribute, const Location &loc) {
auto post_filter = std::make_unique<LocationPostFilterBlueprint>(field, attribute, loc);
const common::Location &location = post_filter->location();
- if (location.getMinX() > location.getMaxX() ||
- location.getMinY() > location.getMaxY())
+ if (location.bounding_box.x.low > location.bounding_box.x.high ||
+ location.bounding_box.y.low > location.bounding_box.y.high)
{
return std::make_unique<queryeval::EmptyBlueprint>(field);
}
ZCurve::RangeVector rangeVector = ZCurve::find_ranges(
- location.getMinX(), location.getMinY(),
- location.getMaxX(), location.getMaxY());
+ location.bounding_box.x.low,
+ location.bounding_box.y.low,
+ location.bounding_box.x.high,
+ location.bounding_box.y.high);
auto pre_filter = std::make_unique<LocationPreFilterBlueprint>(field, attribute, rangeVector);
if (!pre_filter->should_use()) {
return post_filter;
diff --git a/searchlib/src/vespa/searchlib/common/CMakeLists.txt b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
index 5d30260a169..7e1cfd8fec5 100644
--- a/searchlib/src/vespa/searchlib/common/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
@@ -12,6 +12,9 @@ vespa_add_library(searchlib_common OBJECT
featureset.cpp
fileheadercontext.cpp
gatecallback.cpp
+ geo_location.cpp
+ geo_location_spec.cpp
+ geo_location_parser.cpp
growablebitvector.cpp
indexmetainfo.cpp
location.cpp
diff --git a/searchlib/src/vespa/searchlib/common/documentlocations.cpp b/searchlib/src/vespa/searchlib/common/documentlocations.cpp
index b03176f0ad2..b8f05581b41 100644
--- a/searchlib/src/vespa/searchlib/common/documentlocations.cpp
+++ b/searchlib/src/vespa/searchlib/common/documentlocations.cpp
@@ -21,5 +21,9 @@ DocumentLocations::setVecGuard(std::unique_ptr<search::AttributeGuard> guard) {
setVec(*_vec_guard.get()->get());
}
+DocumentLocations::DocumentLocations(DocumentLocations &&) = default;
+DocumentLocations & DocumentLocations::operator = (DocumentLocations &&) = default;
+
+
} // namespace common
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/common/documentlocations.h b/searchlib/src/vespa/searchlib/common/documentlocations.h
index 1dab68ca11f..51d5be76e65 100644
--- a/searchlib/src/vespa/searchlib/common/documentlocations.h
+++ b/searchlib/src/vespa/searchlib/common/documentlocations.h
@@ -25,8 +25,8 @@ private:
const search::attribute::IAttributeVector *_vec;
public:
- DocumentLocations(DocumentLocations &&) = default;
- DocumentLocations & operator = (DocumentLocations &&) = default;
+ DocumentLocations(DocumentLocations &&);
+ DocumentLocations & operator = (DocumentLocations &&);
DocumentLocations();
virtual ~DocumentLocations();
diff --git a/searchlib/src/vespa/searchlib/common/geo_location.cpp b/searchlib/src/vespa/searchlib/common/geo_location.cpp
new file mode 100644
index 00000000000..6dd7b83ae37
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location.cpp
@@ -0,0 +1,184 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "geo_location.h"
+
+using vespalib::geo::ZCurve;
+
+namespace search::common {
+
+namespace {
+
+ZCurve::BoundingBox to_z(GeoLocation::Box box) {
+ return ZCurve::BoundingBox(box.x.low, box.x.high,
+ box.y.low, box.y.high);
+}
+
+GeoLocation::Box
+adjust_bounding_box(GeoLocation::Box orig, GeoLocation::Point point, uint32_t radius, GeoLocation::Aspect x_aspect)
+{
+ if (radius == GeoLocation::radius_inf) {
+ // only happens if GeoLocation is explicitly constructed with "infinite" radius
+ return orig;
+ }
+ uint32_t maxdx = radius;
+ if (x_aspect.active()) {
+ // x_aspect is a 32-bit fixed-point number in range [0,1]
+ // so this implements maxdx = ceil(radius/x_aspect)
+ uint64_t maxdx2 = ((static_cast<uint64_t>(radius) << 32) + 0xffffffffu) / x_aspect.multiplier;
+ if (maxdx2 >= 0xffffffffu) {
+ maxdx = 0xffffffffu;
+ } else {
+ maxdx = static_cast<uint32_t>(maxdx2);
+ }
+ }
+ // implied limits from radius and point:
+ int64_t implied_max_x = int64_t(point.x) + int64_t(maxdx);
+ int64_t implied_min_x = int64_t(point.x) - int64_t(maxdx);
+
+ int64_t implied_max_y = int64_t(point.y) + int64_t(radius);
+ int64_t implied_min_y = int64_t(point.y) - int64_t(radius);
+
+ int32_t max_x = orig.x.high;
+ int32_t min_x = orig.x.low;
+
+ int32_t max_y = orig.y.high;
+ int32_t min_y = orig.y.low;
+
+ if (implied_max_x < max_x) max_x = implied_max_x;
+ if (implied_min_x > min_x) min_x = implied_min_x;
+
+ if (implied_max_y < max_y) max_y = implied_max_y;
+ if (implied_min_y > min_y) min_y = implied_min_y;
+
+ return GeoLocation::Box{GeoLocation::Range{min_x, max_x},
+ GeoLocation::Range{min_y, max_y}};
+}
+
+} // namespace <unnamed>
+
+GeoLocation::GeoLocation()
+ : has_point(false),
+ point{0, 0},
+ radius(radius_inf),
+ x_aspect(),
+ bounding_box(no_box),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(0,0,0,0)
+{}
+
+GeoLocation::GeoLocation(Point p)
+ : has_point(true),
+ point(p),
+ radius(radius_inf),
+ x_aspect(),
+ bounding_box(no_box),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(0,0,0,0)
+{}
+
+GeoLocation::GeoLocation(Point p, Aspect xa)
+ : has_point(true),
+ point(p),
+ radius(radius_inf),
+ x_aspect(xa),
+ bounding_box(no_box),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(0,0,0,0)
+{}
+
+GeoLocation::GeoLocation(Point p, uint32_t r)
+ : has_point(true),
+ point(p),
+ radius(r),
+ x_aspect(),
+ bounding_box(adjust_bounding_box(no_box, p, r, Aspect())),
+ _sq_radius(uint64_t(r) * uint64_t(r)),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Point p, uint32_t r, Aspect xa)
+ : has_point(true),
+ point(p),
+ radius(r),
+ x_aspect(xa),
+ bounding_box(adjust_bounding_box(no_box, p, r, xa)),
+ _sq_radius(uint64_t(r) * uint64_t(r)),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Box b)
+ : has_point(false),
+ point{0, 0},
+ radius(radius_inf),
+ x_aspect(),
+ bounding_box(b),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Box b, Point p)
+ : has_point(true),
+ point(p),
+ radius(radius_inf),
+ x_aspect(),
+ bounding_box(b),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Box b, Point p, Aspect xa)
+ : has_point(true),
+ point(p),
+ radius(radius_inf),
+ x_aspect(xa),
+ bounding_box(b),
+ _sq_radius(sq_radius_inf),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Box b, Point p, uint32_t r)
+ : has_point(true),
+ point(p),
+ radius(r),
+ x_aspect(),
+ bounding_box(adjust_bounding_box(b, p, r, Aspect())),
+ _sq_radius(uint64_t(r) * uint64_t(r)),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+GeoLocation::GeoLocation(Box b, Point p, uint32_t r, Aspect xa)
+ : has_point(true),
+ point(p),
+ radius(r),
+ x_aspect(xa),
+ bounding_box(adjust_bounding_box(b, p, r, xa)),
+ _sq_radius(uint64_t(r) * uint64_t(r)),
+ _z_bounding_box(to_z(bounding_box))
+{}
+
+uint64_t GeoLocation::sq_distance_to(Point p) const {
+ if (has_point) {
+ uint64_t dx = (p.x > point.x) ? (p.x - point.x) : (point.x - p.x);
+ if (x_aspect.active()) {
+ // x_aspect is a 32-bit fixed-point number in range [0,1]
+ // this implements dx = (dx * x_aspect)
+ dx = (dx * x_aspect.multiplier) >> 32;
+ }
+ uint64_t dy = (p.y > point.y) ? (p.y - point.y) : (point.y - p.y);
+ return dx*dx + dy*dy;
+ }
+ return 0;
+}
+
+bool GeoLocation::inside_limit(Point p) const {
+ if (p.x < bounding_box.x.low) return false;
+ if (p.x > bounding_box.x.high) return false;
+
+ if (p.y < bounding_box.y.low) return false;
+ if (p.y > bounding_box.y.high) return false;
+
+ uint64_t sq_dist = sq_distance_to(p);
+ return sq_dist <= _sq_radius;
+}
+
+} // namespace search::common
diff --git a/searchlib/src/vespa/searchlib/common/geo_location.h b/searchlib/src/vespa/searchlib/common/geo_location.h
new file mode 100644
index 00000000000..261951caf3e
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location.h
@@ -0,0 +1,92 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+#include <cstdint>
+#include <limits>
+#include <vespa/vespalib/geo/zcurve.h>
+
+namespace search::common {
+
+/**
+ * An immutable struct for a (geo) location.
+ * Contains a point with optional radius, a bounding box, or both.
+ **/
+struct GeoLocation
+{
+ // contained structs and helper constants:
+ static constexpr int32_t range_low = std::numeric_limits<int32_t>::min();
+ static constexpr int32_t range_high = std::numeric_limits<int32_t>::max();
+ static constexpr uint32_t radius_inf = std::numeric_limits<uint32_t>::max();
+ struct Point {
+ const int32_t x;
+ const int32_t y;
+ Point() = delete;
+ };
+ struct Aspect {
+ uint32_t multiplier;
+ Aspect() : multiplier(0) {}
+ Aspect(uint32_t multiplier_in) : multiplier(multiplier_in) {}
+ // for unit tests:
+ Aspect(double multiplier_in) : multiplier(multiplier_in*4294967296.0) {}
+ bool active() const { return multiplier != 0; }
+ };
+ struct Range {
+ const int32_t low;
+ const int32_t high;
+ bool active() const {
+ return (low != range_low) || (high != range_high);
+ }
+ };
+ static constexpr Range no_range = {range_low, range_high};
+ struct Box {
+ const Range x;
+ const Range y;
+ bool active() const { return x.active() || y.active(); }
+ };
+ static constexpr Box no_box = {no_range, no_range};
+
+ // actual content of struct:
+ const bool has_point;
+ Point point;
+ uint32_t radius;
+ Aspect x_aspect;
+ Box bounding_box;
+ GeoLocation();
+
+ // constructors:
+ GeoLocation(Point p);
+ GeoLocation(Point p, Aspect xa);
+ GeoLocation(Point p, uint32_t r);
+ GeoLocation(Point p, uint32_t r, Aspect xa);
+ GeoLocation(Box b);
+ GeoLocation(Box b, Point p);
+ GeoLocation(Box b, Point p, Aspect xa);
+ GeoLocation(Box b, Point p, uint32_t r);
+ GeoLocation(Box b, Point p, uint32_t r, Aspect xa);
+
+ // helper methods:
+ bool has_radius() const { return radius != radius_inf; }
+ bool valid() const { return has_point || bounding_box.active(); }
+ bool can_limit() const { return bounding_box.active(); }
+
+ uint64_t sq_distance_to(Point p) const;
+ bool inside_limit(Point p) const;
+
+ bool inside_limit(int64_t zcurve_encoded_xy) const {
+ if (_z_bounding_box.getzFailBoundingBoxTest(zcurve_encoded_xy)) return false;
+ int32_t x = 0;
+ int32_t y = 0;
+ vespalib::geo::ZCurve::decode(zcurve_encoded_xy, &x, &y);
+ return inside_limit(Point{x, y});
+ }
+
+private:
+ // constants for implementation of helper methods:
+ static constexpr uint64_t sq_radius_inf = std::numeric_limits<uint64_t>::max();
+ const uint64_t _sq_radius;
+ const vespalib::geo::ZCurve::BoundingBox _z_bounding_box;
+};
+
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/common/geo_location_parser.cpp b/searchlib/src/vespa/searchlib/common/geo_location_parser.cpp
new file mode 100644
index 00000000000..05c53348699
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location_parser.cpp
@@ -0,0 +1,209 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "geo_location_parser.h"
+#include <limits>
+#include <vespa/vespalib/stllike/asciistream.h>
+
+namespace {
+
+int getInt(const char * &p) {
+ int val;
+ bool isminus;
+ val = 0;
+ isminus = false;
+ if (*p == '-') {
+ isminus = true;
+ p++;
+ }
+ while (*p >= '0' && *p <= '9') {
+ val *= 10;
+ val += (*p++ - '0');
+ }
+ return isminus ? - val : val;
+}
+
+} // namespace <unnamed>
+
+namespace search::common {
+
+GeoLocationParser::GeoLocationParser()
+ : _valid(false),
+ _has_point(false),
+ _has_bounding_box(false),
+ _field_name(),
+ _x(0),
+ _y(0),
+ _x_aspect(0u),
+ _radius(std::numeric_limits<uint32_t>::max()),
+ _min_x(std::numeric_limits<int32_t>::min()),
+ _max_x(std::numeric_limits<int32_t>::max()),
+ _min_y(std::numeric_limits<int32_t>::min()),
+ _max_y(std::numeric_limits<int32_t>::max()),
+ _parseError(NULL)
+{}
+
+bool
+GeoLocationParser::correctDimensionalitySkip(const char * &p) {
+ if (*p == '2') {
+ p++;
+ if (*p != ',') {
+ _parseError = "Missing comma after 2D dimensionality";
+ return false;
+ }
+ p++;
+ return true;
+ }
+ _parseError = "Bad dimensionality spec, not 2D";
+ return false;
+}
+
+bool
+GeoLocationParser::parseOldFormatWithField(const std::string &str)
+{
+ auto sep = str.find(':');
+ if (sep == std::string::npos) {
+ _parseError = "Location string lacks field specification.";
+ return false;
+ }
+ _field_name = str.substr(0, sep);
+ std::string only_loc = str.substr(sep + 1);
+ return parseOldFormat(only_loc);
+}
+
+bool
+GeoLocationParser::parseOldFormat(const std::string &locStr)
+{
+ bool foundBoundingBox = false;
+ bool foundLoc = false;
+ const char *p = locStr.c_str();
+ while (*p != '\0') {
+ if (*p == '[') {
+ p++;
+ if (foundBoundingBox) {
+ _parseError = "Duplicate bounding box";
+ return false;
+ }
+ foundBoundingBox = true;
+ if (!correctDimensionalitySkip(p)) {
+ return false;
+ }
+ _min_x = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after minx";
+ return false;
+ }
+ p++;
+ _min_y = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after miny";
+ return false;
+ }
+ p++;
+ _max_x = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after maxx";
+ return false;
+ }
+ p++;
+ _max_y = getInt(p);
+ if (*p != ']') {
+ _parseError = "Missing ']' after maxy";
+ return false;
+ }
+ p++;
+ } else if (*p == '(') {
+ p++;
+ if (foundLoc) {
+ _parseError = "Duplicate location";
+ return false;
+ }
+ foundLoc = true;
+ if (!correctDimensionalitySkip(p)) {
+ return false;
+ }
+ _x = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after x position";
+ return false;
+ }
+ p++;
+ _y = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after y position";
+ return false;
+ }
+ p++;
+ _radius = getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after radius";
+ return false;
+ }
+ p++;
+ /* _tableID = */ (void) getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after tableID";
+ return false;
+ }
+ p++;
+ /* _rankMultiplier = */ (void) getInt(p);
+ if (*p != ',') {
+ _parseError = "Missing ',' after rank multiplier";
+ return false;
+ }
+ p++;
+ /* _rankOnlyOnDistance = */ (void) getInt(p);
+ if (*p == ',') {
+ p++;
+ _x_aspect = getInt(p);
+ if (*p != ')') {
+ _parseError = "Missing ')' after xAspect";
+ return false;
+ }
+ } else {
+ if (*p != ')') {
+ _parseError = "Missing ')' after rankOnlyOnDistance flag";
+ return false;
+ }
+ }
+ p++;
+ } else if (*p == ' ') {
+ p++;
+ } else {
+ _parseError = "Unexpected char in location spec";
+ return false;
+ }
+ }
+ _has_point = foundLoc;
+ _has_bounding_box = foundBoundingBox;
+ _valid = (_has_point || _has_bounding_box);
+ return _valid;
+}
+
+GeoLocation
+GeoLocationParser::getGeoLocation() const
+{
+ GeoLocation::Aspect aspect(_x_aspect);
+ if (_has_bounding_box) {
+ GeoLocation::Range x_range{_min_x, _max_x};
+ GeoLocation::Range y_range{_min_y, _max_y};
+ GeoLocation::Box bounding_box{x_range, y_range};
+ if (_has_point) {
+ GeoLocation::Point point{_x, _y};
+ if (_radius == GeoLocation::radius_inf) {
+ return GeoLocation(bounding_box, point, aspect);
+ }
+ return GeoLocation(bounding_box, point, _radius, aspect);
+ }
+ return GeoLocation(bounding_box);
+ }
+ if (_has_point) {
+ GeoLocation::Point point{_x, _y};
+ if (_radius == GeoLocation::radius_inf) {
+ return GeoLocation(point, aspect);
+ }
+ return GeoLocation(point, _radius, aspect);
+ }
+ return GeoLocation();
+}
+
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/common/geo_location_parser.h b/searchlib/src/vespa/searchlib/common/geo_location_parser.h
new file mode 100644
index 00000000000..8936a620d21
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location_parser.h
@@ -0,0 +1,47 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+#include <cstdint>
+#include "geo_location.h"
+#include "geo_location_spec.h"
+
+namespace search::common {
+
+/**
+ * Parser for a geo-location string representation.
+ **/
+class GeoLocationParser
+{
+public:
+ GeoLocationParser();
+
+ bool parseOldFormat(const std::string &locStr);
+ bool parseOldFormatWithField(const std::string &str);
+
+ std::string getFieldName() const { return _field_name; }
+ GeoLocation getGeoLocation() const;
+
+ const char * getParseError() const { return _parseError; }
+private:
+ bool _valid;
+ bool _has_point;
+ bool _has_bounding_box;
+
+ std::string _field_name;
+
+ int32_t _x; /* Query X position */
+ int32_t _y; /* Query Y position */
+ uint32_t _x_aspect; /* X distance multiplier fraction */
+ uint32_t _radius; /* Radius for euclidean distance */
+ int32_t _min_x; /* Min X coordinate */
+ int32_t _max_x; /* Max X coordinate */
+ int32_t _min_y; /* Min Y coordinate */
+ int32_t _max_y; /* Max Y coordinate */
+
+ const char *_parseError;
+ bool correctDimensionalitySkip(const char * &p);
+};
+
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/common/geo_location_spec.cpp b/searchlib/src/vespa/searchlib/common/geo_location_spec.cpp
new file mode 100644
index 00000000000..271946e2df6
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location_spec.cpp
@@ -0,0 +1,3 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "geo_location_spec.h"
diff --git a/searchlib/src/vespa/searchlib/common/geo_location_spec.h b/searchlib/src/vespa/searchlib/common/geo_location_spec.h
new file mode 100644
index 00000000000..42c2b8e6c8c
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/geo_location_spec.h
@@ -0,0 +1,21 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+#include <cstdint>
+#include "geo_location.h"
+
+namespace search::common {
+
+/**
+ * Immutable specification of a geo-location query item.
+ **/
+struct GeoLocationSpec
+{
+public:
+ const std::string field_name;
+ const GeoLocation location;
+};
+
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/common/location.cpp b/searchlib/src/vespa/searchlib/common/location.cpp
index 6927d9ab6cb..171dcecaa33 100644
--- a/searchlib/src/vespa/searchlib/common/location.cpp
+++ b/searchlib/src/vespa/searchlib/common/location.cpp
@@ -5,198 +5,6 @@
namespace search::common {
-Location::Location() :
- _zBoundingBox(0,0,0,0),
- _x(0),
- _y(0),
- _xAspect(0u),
- _radius(std::numeric_limits<uint32_t>::max()),
- _minx(std::numeric_limits<int32_t>::min()),
- _maxx(std::numeric_limits<int32_t>::max()),
- _miny(std::numeric_limits<int32_t>::min()),
- _maxy(std::numeric_limits<int32_t>::max()),
- _rankOnDistance(false),
- _pruneOnDistance(false),
- _parseError(NULL)
-{
-}
+Location::Location(const GeoLocation &from) : GeoLocation(from) {}
-
-bool
-Location::getDimensionality(const char **pp)
-{
- if (**pp == '2') {
- (*pp)++;
- if (**pp != ',') {
- _parseError = "Missing comma after 2D dimensionality";
- return false;
- }
- (*pp)++;
- return true;
- }
- _parseError = "Bad dimensionality spec, not 2D";
- return false;
-}
-
-
-int
-Location::getInt(const char **pp)
-{
- const char *p = *pp;
- int val;
- bool isminus;
-
- val = 0;
- isminus = false;
- if (*p == '-') {
- isminus = true;
- p++;
- }
- while (*p >= '0' && *p <= '9')
- val = val * 10 + *p++ - '0';
- *pp = p;
- return isminus ? - val : val;
-}
-
-bool Location::parse(const vespalib::string &locStr)
-{
- bool hadCutoff = false;
- bool hadLoc = false;
- const char *p = locStr.c_str();
- while (*p != '\0') {
- if (*p == '[') {
- p++;
- if (hadCutoff) {
- _parseError = "Duplicate square cutoff";
- return false;
- }
- hadCutoff = true;
- if (!getDimensionality(&p))
- return false;
- _minx = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after minx";
- return false;
- }
- p++;
- _miny = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after miny";
- return false;
- }
- p++;
- _maxx = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after maxx";
- return false;
- }
- p++;
- _maxy = getInt(&p);
- if (*p != ']') {
- _parseError = "Missing ']' after maxy";
- return false;
- }
- p++;
- } else if (*p == '(') {
- p++;
- if (hadLoc) {
- _parseError = "Duplicate location";
- return false;
- }
- hadLoc = true;
- if (!getDimensionality(&p))
- return false;
- _x = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after x position";
- return false;
- }
- p++;
- _y = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after y position";
- return false;
- }
- p++;
- _radius = getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after radius";
- return false;
- }
- p++;
- /* _tableID = */ (void) getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after tableID";
- return false;
- }
- p++;
- /* _rankMultiplier = */ (void) getInt(&p);
- if (*p != ',') {
- _parseError = "Missing ',' after rank multiplier";
- return false;
- }
- p++;
- /* _rankOnlyOnDistance = */ (void) (getInt(&p) != 0);
- if (*p == ',') {
- p++;
- _xAspect = getInt(&p);
- if (*p != ')') {
- _parseError = "Missing ')' after xAspect";
- return false;
- }
- } else {
- if (*p != ')') {
- _parseError = "Missing ')' after rankOnlyOnDistance flag";
- return false;
- }
- }
- p++;
- } else if (*p == ' ')
- p++;
- else {
- _parseError = "Unexpected char in location spec";
- return false;
- }
- }
-
- if (hadLoc) {
- _rankOnDistance = true;
- uint32_t maxdx = _radius;
- if (_xAspect != 0) {
- uint64_t maxdx2 = ((static_cast<uint64_t>(_radius) << 32) + 0xffffffffu) /
- _xAspect;
- if (maxdx2 >= 0xffffffffu)
- maxdx = 0xffffffffu;
- else
- maxdx = static_cast<uint32_t>(maxdx2);
- }
- if (static_cast<int32_t>(_x - maxdx) > _minx &&
- static_cast<int64_t>(_x) - static_cast<int64_t>(maxdx) >
- static_cast<int64_t>(_minx))
- _minx = _x - maxdx;
- if (static_cast<int32_t>(_x + maxdx) < _maxx &&
- static_cast<int64_t>(_x) + static_cast<int64_t>(maxdx) <
- static_cast<int64_t>(_maxx))
- _maxx = _x + maxdx;
- if (static_cast<int32_t>(_y - _radius) > _miny &&
- static_cast<int64_t>(_y) - static_cast<int64_t>(_radius) >
- static_cast<int64_t>(_miny))
- _miny = _y - _radius;
- if (static_cast<int32_t>(_y + _radius) < _maxy &&
- static_cast<int64_t>(_y) + static_cast<int64_t>(_radius) <
- static_cast<int64_t>(_maxy))
- _maxy = _y + _radius;
- }
- if (_minx != std::numeric_limits<int32_t>::min() ||
- _maxx != std::numeric_limits<int32_t>::max() ||
- _miny != std::numeric_limits<int32_t>::min() ||
- _maxy != std::numeric_limits<int32_t>::max())
- {
- _pruneOnDistance = true;
- }
- _zBoundingBox = vespalib::geo::ZCurve::BoundingBox(_minx, _maxx, _miny, _maxy);
-
- return true;
-}
-
-}
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/common/location.h b/searchlib/src/vespa/searchlib/common/location.h
index a00bb83648a..197f92326cd 100644
--- a/searchlib/src/vespa/searchlib/common/location.h
+++ b/searchlib/src/vespa/searchlib/common/location.h
@@ -3,51 +3,19 @@
#pragma once
#include "documentlocations.h"
-#include <vespa/vespalib/geo/zcurve.h>
-
-#include <vespa/vespalib/stllike/string.h>
+#include "geo_location.h"
namespace search::common {
-class Location : public DocumentLocations
+class Location : public DocumentLocations,
+ public GeoLocation
{
-private:
- static int getInt(const char **pp);
- bool getDimensionality(const char **pp);
-
public:
- Location();
- bool getRankOnDistance() const { return _rankOnDistance; }
- bool getPruneOnDistance() const { return _pruneOnDistance; }
- uint32_t getXAspect() const { return _xAspect; }
- int32_t getX() const { return _x; }
- int32_t getY() const { return _y; }
- uint32_t getRadius() const { return _radius; }
- const char * getParseError() const { return _parseError; }
- int32_t getMinX() const { return _minx; }
- int32_t getMinY() const { return _miny; }
- int32_t getMaxX() const { return _maxx; }
- int32_t getMaxY() const { return _maxy; }
- bool getzFailBoundingBoxTest(int64_t docxy) const {
- return _zBoundingBox.getzFailBoundingBoxTest(docxy);
- }
-
- bool parse(const vespalib::string &locStr);
-
-private:
- vespalib::geo::ZCurve::BoundingBox _zBoundingBox;
- int32_t _x; /* Query X position */
- int32_t _y; /* Query Y position */
- uint32_t _xAspect; /* X distance multiplier fraction */
- uint32_t _radius; /* Radius for euclidean distance */
- int32_t _minx; /* Min X coordinate */
- int32_t _maxx; /* Max X coordinate */
- int32_t _miny; /* Min Y coordinate */
- int32_t _maxy; /* Max Y coordinate */
-
- bool _rankOnDistance;
- bool _pruneOnDistance;
- const char *_parseError;
+ Location(const GeoLocation& from);
+ ~Location() {}
+ Location(Location &&) = default;
+ bool getRankOnDistance() const { return has_point; }
+ bool getPruneOnDistance() const { return can_limit(); }
};
}
diff --git a/searchlib/src/vespa/searchlib/common/locationiterators.cpp b/searchlib/src/vespa/searchlib/common/locationiterators.cpp
index 16e465bcd05..d90ed3b41f3 100644
--- a/searchlib/src/vespa/searchlib/common/locationiterators.cpp
+++ b/searchlib/src/vespa/searchlib/common/locationiterators.cpp
@@ -4,6 +4,9 @@
#include <vespa/searchlib/bitcompression/compression.h>
#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".searchlib.common.locationiterators");
+
using namespace search::common;
class FastS_2DZLocationIterator : public search::queryeval::SearchIterator
@@ -11,7 +14,6 @@ class FastS_2DZLocationIterator : public search::queryeval::SearchIterator
private:
const unsigned int _numDocs;
const bool _strict;
- const uint64_t _radius2;
const Location & _location;
std::vector<search::AttributeVector::largeint_t> _pos;
@@ -31,7 +33,6 @@ FastS_2DZLocationIterator(unsigned int numDocs,
: SearchIterator(),
_numDocs(numDocs),
_strict(strict),
- _radius2(static_cast<uint64_t>(location.getRadius()) * location.getRadius()),
_location(location),
_pos()
{
@@ -45,6 +46,8 @@ FastS_2DZLocationIterator::~FastS_2DZLocationIterator() = default;
void
FastS_2DZLocationIterator::doSeek(uint32_t docId)
{
+ LOG(debug, "FastS_2DZLocationIterator: seek(%u) with numDocs=%u endId=%u",
+ docId, _numDocs, getEndId());
if (__builtin_expect(docId >= _numDocs, false)) {
setAtEnd();
return;
@@ -62,24 +65,9 @@ FastS_2DZLocationIterator::doSeek(uint32_t docId)
}
for (uint32_t i = 0; i < numValues; i++) {
int64_t docxy(pos[i]);
- if ( ! location.getzFailBoundingBoxTest(docxy)) {
- int32_t docx = 0;
- int32_t docy = 0;
- vespalib::geo::ZCurve::decode(docxy, &docx, &docy);
- uint32_t dx = (location.getX() > docx)
- ? location.getX() - docx
- : docx - location.getX();
- if (location.getXAspect() != 0)
- dx = ((uint64_t) dx * location.getXAspect()) >> 32;
-
- uint32_t dy = (location.getY() > docy)
- ? location.getY() - docy
- : docy - location.getY();
- uint64_t dist2 = (uint64_t) dx * dx + (uint64_t) dy * dy;
- if (dist2 <= _radius2) {
- setDocId(docId);
- return;
- }
+ if (location.inside_limit(docxy)) {
+ setDocId(docId);
+ return;
}
}
diff --git a/searchlib/src/vespa/searchlib/features/distancefeature.cpp b/searchlib/src/vespa/searchlib/features/distancefeature.cpp
index 7a624e64d67..6582bcae92a 100644
--- a/searchlib/src/vespa/searchlib/features/distancefeature.cpp
+++ b/searchlib/src/vespa/searchlib/features/distancefeature.cpp
@@ -2,7 +2,7 @@
#include "distancefeature.h"
#include <vespa/searchcommon/common/schema.h>
-#include <vespa/searchlib/fef/location.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
#include <vespa/searchlib/fef/matchdata.h>
#include <vespa/document/datatype/positiondatatype.h>
#include <vespa/vespalib/geo/zcurve.h>
@@ -82,7 +82,8 @@ ConvertRawscoreToDistance::execute(uint32_t docId)
feature_t
DistanceExecutor::calculateDistance(uint32_t docId)
{
- if (_location.isValid() && _pos != nullptr) {
+ if ((! _locations.empty()) && (_pos != nullptr)) {
+ LOG(debug, "calculate 2D Z-distance from %zu locations", _locations.size());
return calculate2DZDistance(docId);
}
return DEFAULT_DISTANCE;
@@ -97,35 +98,24 @@ DistanceExecutor::calculate2DZDistance(uint32_t docId)
uint64_t sqabsdist = std::numeric_limits<uint64_t>::max();
int32_t docx = 0;
int32_t docy = 0;
- for (uint32_t i = 0; i < numValues; ++i) {
- vespalib::geo::ZCurve::decode(_intBuf[i], &docx, &docy);
- uint32_t dx;
- uint32_t dy;
- if (_location.getXPosition() > docx) {
- dx = _location.getXPosition() - docx;
- } else {
- dx = docx - _location.getXPosition();
- }
- if (_location.getXAspect() != 0) {
- dx = ((uint64_t) dx * _location.getXAspect()) >> 32;
- }
- if (_location.getYPosition() > docy) {
- dy = _location.getYPosition() - docy;
- } else {
- dy = docy - _location.getYPosition();
- }
- uint64_t sqdist = (uint64_t) dx * dx + (uint64_t) dy * dy;
- if (sqdist < sqabsdist) {
- sqabsdist = sqdist;
+ for (auto loc : _locations) {
+ assert(loc);
+ assert(loc->location.valid());
+ for (uint32_t i = 0; i < numValues; ++i) {
+ vespalib::geo::ZCurve::decode(_intBuf[i], &docx, &docy);
+ uint64_t sqdist = loc->location.sq_distance_to({docx, docy});
+ if (sqdist < sqabsdist) {
+ sqabsdist = sqdist;
+ }
}
}
return static_cast<feature_t>(std::sqrt(static_cast<feature_t>(sqabsdist)));
}
-DistanceExecutor::DistanceExecutor(const Location & location,
+DistanceExecutor::DistanceExecutor(GeoLocationSpecPtrs locations,
const search::attribute::IAttributeVector * pos) :
FeatureExecutor(),
- _location(location),
+ _locations(locations),
_pos(pos),
_intBuf()
{
@@ -231,6 +221,7 @@ DistanceBlueprint::setup(const IIndexEnvironment & env,
return setup_geopos(env, z);
}
if (allow_bad_field) {
+ // TODO remove on Vespa 8
// backwards compatibility fallback:
return setup_geopos(env, arg);
}
@@ -251,11 +242,30 @@ DistanceBlueprint::createExecutor(const IQueryEnvironment &env, vespalib::Stash
if (_use_item_label) {
return stash.create<ConvertRawscoreToDistance>(env, _arg_string);
}
+ // expect geo pos:
const search::attribute::IAttributeVector * pos = nullptr;
- const Location & location = env.getLocation();
- LOG(debug, "DistanceBlueprint::createExecutor location.valid='%s', attribute='%s'",
- location.isValid() ? "true" : "false", _arg_string.c_str());
- if (_use_geo_pos && location.isValid()) {
+ GeoLocationSpecPtrs matching_locs;
+ GeoLocationSpecPtrs other_locs;
+
+ for (auto loc_ptr : env.getAllLocations()) {
+ if (_use_geo_pos && loc_ptr && loc_ptr->location.valid()) {
+ if (loc_ptr->field_name == _arg_string) {
+ LOG(debug, "found loc from query env matching '%s'", _arg_string.c_str());
+ matching_locs.push_back(loc_ptr);
+ } else {
+ LOG(debug, "found loc(%s) from query env not matching arg(%s)",
+ loc_ptr->field_name.c_str(), _arg_string.c_str());
+ other_locs.push_back(loc_ptr);
+ }
+ }
+ }
+ if (matching_locs.empty() && other_locs.empty()) {
+ LOG(debug, "createExecutor: no valid locations");
+ return stash.create<DistanceExecutor>(matching_locs, nullptr);
+ }
+ LOG(debug, "createExecutor: valid location, attribute='%s'", _arg_string.c_str());
+
+ if (_use_geo_pos) {
pos = env.getAttributeContext().getAttribute(_arg_string);
if (pos != nullptr) {
if (!pos->isIntegerType()) {
@@ -271,8 +281,8 @@ DistanceBlueprint::createExecutor(const IQueryEnvironment &env, vespalib::Stash
LOG(warning, "The position attribute '%s' was not found. Will use default distance.", _arg_string.c_str());
}
}
-
- return stash.create<DistanceExecutor>(location, pos);
+ LOG(debug, "use '%s' locations with pos=%p", matching_locs.empty() ? "other" : "matching", pos);
+ return stash.create<DistanceExecutor>(matching_locs.empty() ? other_locs : matching_locs, pos);
}
}
diff --git a/searchlib/src/vespa/searchlib/features/distancefeature.h b/searchlib/src/vespa/searchlib/features/distancefeature.h
index 3a8edd5ee94..ece139c6546 100644
--- a/searchlib/src/vespa/searchlib/features/distancefeature.h
+++ b/searchlib/src/vespa/searchlib/features/distancefeature.h
@@ -7,12 +7,15 @@
namespace search::features {
+/** Convenience typedef. */
+using GeoLocationSpecPtrs = std::vector<const search::common::GeoLocationSpec *>;
+
/**
* Implements the executor for the distance feature.
*/
class DistanceExecutor : public fef::FeatureExecutor {
private:
- const fef::Location & _location;
+ GeoLocationSpecPtrs _locations;
const attribute::IAttributeVector * _pos;
attribute::IntegerContent _intBuf;
@@ -23,10 +26,11 @@ public:
/**
* Constructs an executor for the distance feature.
*
- * @param location the location object associated with the query environment.
+ * @param locations location objects associated with the query environment.
* @param pos the attribute to use for positions (expects zcurve encoding).
*/
- DistanceExecutor(const fef::Location & location, const attribute::IAttributeVector * pos);
+ DistanceExecutor(GeoLocationSpecPtrs locations,
+ const attribute::IAttributeVector * pos);
void execute(uint32_t docId) override;
static const feature_t DEFAULT_DISTANCE;
diff --git a/searchlib/src/vespa/searchlib/fef/CMakeLists.txt b/searchlib/src/vespa/searchlib/fef/CMakeLists.txt
index 396775b20c5..178de1b8b87 100644
--- a/searchlib/src/vespa/searchlib/fef/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/fef/CMakeLists.txt
@@ -16,7 +16,6 @@ vespa_add_library(searchlib_fef OBJECT
filetablefactory.cpp
functiontablefactory.cpp
indexproperties.cpp
- location.cpp
matchdata.cpp
matchdatalayout.cpp
objectstore.cpp
diff --git a/searchlib/src/vespa/searchlib/fef/fef.h b/searchlib/src/vespa/searchlib/fef/fef.h
index 7dd24bd17ae..b677ba128c9 100644
--- a/searchlib/src/vespa/searchlib/fef/fef.h
+++ b/searchlib/src/vespa/searchlib/fef/fef.h
@@ -35,7 +35,6 @@
#include "itablemanager.h"
#include "itermdata.h"
#include "itermfielddata.h"
-#include "location.h"
#include "matchdata.h"
#include "matchdatalayout.h"
#include "parameter.h"
diff --git a/searchlib/src/vespa/searchlib/fef/iqueryenvironment.h b/searchlib/src/vespa/searchlib/fef/iqueryenvironment.h
index 041e9ec67bc..7c6a84916f4 100644
--- a/searchlib/src/vespa/searchlib/fef/iqueryenvironment.h
+++ b/searchlib/src/vespa/searchlib/fef/iqueryenvironment.h
@@ -6,9 +6,10 @@
#include "objectstore.h"
#include <vespa/searchcommon/attribute/iattributecontext.h>
+namespace search::common { class GeoLocationSpec; }
+
namespace search::fef {
-class Location;
class Properties;
class ITermData;
@@ -24,6 +25,9 @@ public:
**/
typedef std::shared_ptr<IQueryEnvironment> SP;
+ /** Convenience typedef. */
+ using GeoLocationSpecPtrs = std::vector<const search::common::GeoLocationSpec *>;
+
/**
* Obtain the set of properties associated with this query
* environment. This set of properties is known through the system
@@ -58,9 +62,9 @@ public:
/**
* Obtain the location information associated with this query environment.
*
- * @return location object.
+ * @return pointers to location objects.
**/
- virtual const Location & getLocation() const = 0;
+ virtual GeoLocationSpecPtrs getAllLocations() const = 0;
/**
* Returns the attribute context for this query.
diff --git a/searchlib/src/vespa/searchlib/fef/location.cpp b/searchlib/src/vespa/searchlib/fef/location.cpp
deleted file mode 100644
index 978daa0f930..00000000000
--- a/searchlib/src/vespa/searchlib/fef/location.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "location.h"
-
-namespace search {
-namespace fef {
-
-Location::Location() :
- _attr(),
- _xPos(0),
- _yPos(0),
- _xAspect(0),
- _valid(false)
-{
-}
-
-} // namespace fef
-} // namespace search
diff --git a/searchlib/src/vespa/searchlib/fef/location.h b/searchlib/src/vespa/searchlib/fef/location.h
deleted file mode 100644
index 5be7d1ce822..00000000000
--- a/searchlib/src/vespa/searchlib/fef/location.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#pragma once
-
-#include <vespa/vespalib/stllike/string.h>
-
-namespace search {
-namespace fef {
-
-/**
- * This class contains location data that is associated with a query.
- **/
-class Location
-{
-private:
- vespalib::string _attr;
- int32_t _xPos;
- int32_t _yPos;
- uint32_t _xAspect;
- bool _valid;
-
-public:
- /**
- * Creates an empty object.
- **/
- Location();
-
- /**
- * Sets the name of the attribute to use for x positions.
- *
- * @param xAttr the attribute name.
- * @return this to allow chaining.
- **/
- Location &
- setAttribute(const vespalib::string & attr)
- {
- _attr = attr;
- return *this;
- }
-
- /**
- * Returns the name of the attribute to use for x positions.
- *
- * @return the attribute name.
- **/
- const vespalib::string & getAttribute() const { return _attr; }
-
- /**
- * Sets the x position of this location.
- *
- * @param xPos the x position.
- * @return this to allow chaining.
- **/
- Location & setXPosition(int32_t xPos) { _xPos = xPos; return *this; }
-
- /**
- * Returns the x position of this location.
- *
- * @return the x position.
- **/
- int32_t getXPosition() const { return _xPos; }
-
- /**
- * Sets the y position of this location.
- *
- * @param yPos the y position.
- * @return this to allow chaining.
- **/
- Location & setYPosition(int32_t yPos) { _yPos = yPos; return *this; }
-
- /**
- * Returns the y position of this location.
- *
- * @return the y position.
- **/
- int32_t getYPosition() const { return _yPos; }
-
- /**
- * Sets the x distance multiplier fraction.
- *
- * @param xAspect the x aspect.
- * @return this to allow chaining.
- **/
- Location & setXAspect(uint32_t xAspect) { _xAspect = xAspect; return *this; }
-
- /**
- * Returns the x distance multiplier fraction.
- *
- * @return the x aspect.
- **/
- uint32_t getXAspect() const { return _xAspect; }
-
- /**
- * Sets whether this is a valid location object.
- *
- * @param valid true if this is valid.
- * @return this to allow chaining.
- **/
- Location & setValid(bool valid) { _valid = valid; return *this; }
-
- /**
- * Returns whether this is a valid location object.
- *
- * @param true if this is a valid.
- **/
- bool isValid() const { return _valid; }
-};
-
-} // namespace fef
-} // namespace search
-
diff --git a/searchlib/src/vespa/searchlib/fef/phrase_splitter_query_env.h b/searchlib/src/vespa/searchlib/fef/phrase_splitter_query_env.h
index b2a3d416f5a..90bf4e8955f 100644
--- a/searchlib/src/vespa/searchlib/fef/phrase_splitter_query_env.h
+++ b/searchlib/src/vespa/searchlib/fef/phrase_splitter_query_env.h
@@ -73,7 +73,9 @@ public:
}
const Properties & getProperties() const override { return _queryEnv.getProperties(); }
- const Location & getLocation() const override { return _queryEnv.getLocation(); }
+ GeoLocationSpecPtrs getAllLocations() const override {
+ return _queryEnv.getAllLocations();
+ }
const attribute::IAttributeContext & getAttributeContext() const override { return _queryEnv.getAttributeContext(); }
double get_average_field_length(const vespalib::string &field_name) const override { return _queryEnv.get_average_field_length(field_name); }
const IIndexEnvironment & getIndexEnvironment() const override { return _queryEnv.getIndexEnvironment(); }
diff --git a/searchlib/src/vespa/searchlib/fef/test/queryenvironment.cpp b/searchlib/src/vespa/searchlib/fef/test/queryenvironment.cpp
index 4697675c071..d602e74ddfb 100644
--- a/searchlib/src/vespa/searchlib/fef/test/queryenvironment.cpp
+++ b/searchlib/src/vespa/searchlib/fef/test/queryenvironment.cpp
@@ -8,7 +8,7 @@ QueryEnvironment::QueryEnvironment(IndexEnvironment *env)
: _indexEnv(env),
_terms(),
_properties(),
- _location(),
+ _locations(),
_attrCtx((env == nullptr) ? attribute::IAttributeContext::UP() : env->getAttributeMap().createContext())
{
}
diff --git a/searchlib/src/vespa/searchlib/fef/test/queryenvironment.h b/searchlib/src/vespa/searchlib/fef/test/queryenvironment.h
index 40898281794..d8d5c802360 100644
--- a/searchlib/src/vespa/searchlib/fef/test/queryenvironment.h
+++ b/searchlib/src/vespa/searchlib/fef/test/queryenvironment.h
@@ -4,12 +4,14 @@
#include "indexenvironment.h"
#include <vespa/searchcommon/attribute/iattributecontext.h>
#include <vespa/searchlib/fef/iqueryenvironment.h>
-#include <vespa/searchlib/fef/location.h>
#include <vespa/searchlib/fef/simpletermdata.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
#include <unordered_map>
namespace search::fef::test {
+using search::common::GeoLocationSpec;
+
/**
* Implementation of the IQueryEnvironment interface used for testing.
*/
@@ -22,7 +24,7 @@ private:
IndexEnvironment *_indexEnv;
std::vector<SimpleTermData> _terms;
Properties _properties;
- Location _location;
+ std::vector<GeoLocationSpec> _locations;
search::attribute::IAttributeContext::UP _attrCtx;
std::unordered_map<std::string, double> _avg_field_lengths;
@@ -38,7 +40,13 @@ public:
const Properties &getProperties() const override { return _properties; }
uint32_t getNumTerms() const override { return _terms.size(); }
const ITermData *getTerm(uint32_t idx) const override { return idx < _terms.size() ? &_terms[idx] : NULL; }
- const Location & getLocation() const override { return _location; }
+ GeoLocationSpecPtrs getAllLocations() const override {
+ GeoLocationSpecPtrs locations;
+ for (const auto & loc : _locations) {
+ locations.push_back(&loc);
+ }
+ return locations;
+ }
const search::attribute::IAttributeContext &getAttributeContext() const override { return *_attrCtx; }
double get_average_field_length(const vespalib::string& field_name) const override {
auto itr = _avg_field_lengths.find(field_name);
@@ -82,7 +90,7 @@ public:
Properties & getProperties() { return _properties; }
/** Returns a reference to the location of this. */
- Location & getLocation() { return _location; }
+ void addLocation(const GeoLocationSpec &spec) { _locations.push_back(spec); }
std::unordered_map<std::string, double>& get_avg_field_lengths() { return _avg_field_lengths; }
};
diff --git a/searchlib/src/vespa/searchlib/parsequery/parse.h b/searchlib/src/vespa/searchlib/parsequery/parse.h
index b4dd9826b84..68e259b92e8 100644
--- a/searchlib/src/vespa/searchlib/parsequery/parse.h
+++ b/searchlib/src/vespa/searchlib/parsequery/parse.h
@@ -53,7 +53,7 @@ public:
ITEM_REGEXP = 24,
ITEM_WORD_ALTERNATIVES = 25,
ITEM_NEAREST_NEIGHBOR = 26,
- ITEM_LOCATION_TERM = 27,
+ ITEM_GEO_LOCATION_TERM = 27,
ITEM_MAX = 28, // Indicates how long tables must be.
ITEM_UNDEF = 31,
};
diff --git a/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.cpp b/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.cpp
index 17cbd6dce1b..1820fb0e969 100644
--- a/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.cpp
+++ b/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.cpp
@@ -207,7 +207,7 @@ SimpleQueryStackDumpIterator::next()
}
break;
case ParseItem::ITEM_NUMTERM:
- case ParseItem::ITEM_LOCATION_TERM:
+ case ParseItem::ITEM_GEO_LOCATION_TERM:
case ParseItem::ITEM_TERM:
case ParseItem::ITEM_PREFIXTERM:
case ParseItem::ITEM_SUBSTRINGTERM:
diff --git a/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp b/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
index f1599e820ef..66466b030d0 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
+++ b/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
@@ -63,8 +63,13 @@ QueryNode::Build(const QueryNode * parent, const QueryNodeResultFactory & factor
}
}
break;
+ case ParseItem::ITEM_GEO_LOCATION_TERM:
+ // TODO implement this:
+ // vespalib::string field = queryRep.getIndexName();
+ // vespalib::stringref location_term = queryRep.getTerm();
+ // qn = std::make_unique<LocationQueryNode> ...something ....
+ // break;
case ParseItem::ITEM_NUMTERM:
- case ParseItem::ITEM_LOCATION_TERM:
case ParseItem::ITEM_TERM:
case ParseItem::ITEM_PREFIXTERM:
case ParseItem::ITEM_REGEXP:
diff --git a/searchlib/src/vespa/searchlib/query/tree/location.cpp b/searchlib/src/vespa/searchlib/query/tree/location.cpp
index 216c5ec5ad0..6e678f9e682 100644
--- a/searchlib/src/vespa/searchlib/query/tree/location.cpp
+++ b/searchlib/src/vespa/searchlib/query/tree/location.cpp
@@ -6,58 +6,71 @@
#include <vespa/vespalib/stllike/asciistream.h>
using vespalib::asciistream;
+using search::common::GeoLocation;
namespace search::query {
-Location::Location(const Point &point, uint32_t max_dist, uint32_t x_aspect) {
- asciistream loc;
- loc << "(2" // dimensionality
- << "," << point.x
- << "," << point.y
- << "," << max_dist
- << "," << "0" // table id.
- << "," << "1" // rank multiplier.
- << "," << "0" // rank only on distance.
- << "," << x_aspect // x aspect.
- << ")";
- _location_string = loc.str();
+static GeoLocation::Box convert(const Rectangle &rect) {
+ GeoLocation::Range x_range{rect.left, rect.right};
+ GeoLocation::Range y_range{rect.top, rect.bottom};
+ return GeoLocation::Box{x_range, y_range};
}
+Location::Location(const Point &p, uint32_t max_dist, uint32_t aspect)
+ : Parent(p, max_dist, GeoLocation::Aspect(aspect))
+{}
+
Location::Location(const Rectangle &rect,
- const Point &point, uint32_t max_dist, uint32_t x_aspect)
-{
- asciistream loc;
- loc << "(2" // dimensionality
- << "," << point.x
- << "," << point.y
- << "," << max_dist
- << "," << "0" // table id.
- << "," << "1" // rank multiplier.
- << "," << "0" // rank only on distance.
- << "," << x_aspect // x aspect.
- << ")";
- loc << "[2," << rect.left
- << "," << rect.top
- << "," << rect.right
- << "," << rect.bottom
- << "]" ;
- _location_string = loc.str();
+ const Point &p, uint32_t max_dist, uint32_t aspect)
+ : Parent(convert(rect), p, max_dist, GeoLocation::Aspect(aspect))
+{}
-}
+Location::Location(const Rectangle &rect)
+ : Parent(convert(rect))
+{}
-Location::Location(const Rectangle &rect) {
- asciistream loc;
- loc << "[2," << rect.left
- << "," << rect.top
- << "," << rect.right
- << "," << rect.bottom
- << "]" ;
- _location_string = loc.str();
+bool
+Location::operator==(const Location &other) const
+{
+ auto me = getOldFormatString();
+ auto it = other.getOldFormatString();
+ if (me == it) {
+ return true;
+ } else {
+ // dump 'me' and 'it' here if unit tests fail
+ return false;
+ }
+}
+
+std::string
+Location::getOldFormatString() const
+{
+ // we need to product what search::common::GeoLocationParser can parse
+ vespalib::asciistream buf;
+ if (has_point) {
+ buf << "(2" // dimensionality
+ << "," << point.x
+ << "," << point.y
+ << "," << radius
+ << "," << "0" // table id.
+ << "," << "1" // rank multiplier.
+ << "," << "0" // rank only on distance.
+ << "," << x_aspect.multiplier // aspect multiplier
+ << ")";
+ }
+ if (bounding_box.active()) {
+ buf << "[2," << bounding_box.x.low
+ << "," << bounding_box.y.low
+ << "," << bounding_box.x.high
+ << "," << bounding_box.y.high
+ << "]" ;
+ }
+ return buf.str();
}
vespalib::asciistream &operator<<(vespalib::asciistream &out, const Location &loc) {
- return out << loc.getLocationString();
+ return out << loc.getOldFormatString();
}
}
diff --git a/searchlib/src/vespa/searchlib/query/tree/location.h b/searchlib/src/vespa/searchlib/query/tree/location.h
index e1826c7184a..6b8090f45e1 100644
--- a/searchlib/src/vespa/searchlib/query/tree/location.h
+++ b/searchlib/src/vespa/searchlib/query/tree/location.h
@@ -2,29 +2,26 @@
#pragma once
-#include <vespa/vespalib/stllike/string.h>
+#include <string>
+#include <vespa/searchlib/common/geo_location_spec.h>
+#include "point.h"
+#include "rectangle.h"
namespace vespalib { class asciistream; }
namespace search::query {
-struct Point;
-struct Rectangle;
-
-class Location {
- vespalib::string _location_string;
+class Location : public search::common::GeoLocation {
+ using Parent = search::common::GeoLocation;
public:
- Location() : _location_string() {}
+ Location() {}
+ Location(const Parent &spec) : Parent(spec) {}
+ ~Location() {}
Location(const Point &p, uint32_t dist, uint32_t x_asp);
Location(const Rectangle &rect);
Location(const Rectangle &rect, const Point &p, uint32_t dist, uint32_t x_asp);
- Location(const vespalib::string &s) : _location_string(s) {}
- bool operator==(const Location &other) const {
- return _location_string == other._location_string;
- }
- const vespalib::string &getLocationString() const {
- return _location_string;
- }
+ bool operator==(const Location &other) const;
+ std::string getOldFormatString() const;
};
vespalib::asciistream &operator<<(vespalib::asciistream &out, const Location &loc);
diff --git a/searchlib/src/vespa/searchlib/query/tree/point.h b/searchlib/src/vespa/searchlib/query/tree/point.h
index 89d0bc1db44..48700681158 100644
--- a/searchlib/src/vespa/searchlib/query/tree/point.h
+++ b/searchlib/src/vespa/searchlib/query/tree/point.h
@@ -3,18 +3,10 @@
#pragma once
#include <cstdint>
+#include <vespa/searchlib/common/geo_location.h>
namespace search::query {
-struct Point {
- int64_t x;
- int64_t y;
- Point() : x(0), y(0) {}
- Point(int64_t x_in, int64_t y_in) : x(x_in), y(y_in) {}
-};
-
-inline bool operator==(const Point &p1, const Point &p2) {
- return p1.x == p2.x && p1.y == p2.y;
-}
+using Point = search::common::GeoLocation::Point;
}
diff --git a/searchlib/src/vespa/searchlib/query/tree/rectangle.h b/searchlib/src/vespa/searchlib/query/tree/rectangle.h
index 97be9ddeb32..358e994aacd 100644
--- a/searchlib/src/vespa/searchlib/query/tree/rectangle.h
+++ b/searchlib/src/vespa/searchlib/query/tree/rectangle.h
@@ -5,20 +5,14 @@
namespace search::query {
struct Rectangle {
- int64_t left;
- int64_t top;
- int64_t right;
- int64_t bottom;
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
Rectangle() : left(0), top(0), right(0), bottom(0) {}
- Rectangle(int64_t l, int64_t t, int64_t r, int64_t b)
+ Rectangle(int32_t l, int32_t t, int32_t r, int32_t b)
: left(l), top(t), right(r), bottom(b) {}
};
-inline bool operator==(const Rectangle &r1, const Rectangle &r2) {
- return r1.left == r2.left && r1.right == r2.right
- && r1.top == r2.top && r1.bottom == r2.bottom;
}
-
-}
-
diff --git a/searchlib/src/vespa/searchlib/query/tree/stackdumpcreator.cpp b/searchlib/src/vespa/searchlib/query/tree/stackdumpcreator.cpp
index f33520d8b0e..82302e4ab48 100644
--- a/searchlib/src/vespa/searchlib/query/tree/stackdumpcreator.cpp
+++ b/searchlib/src/vespa/searchlib/query/tree/stackdumpcreator.cpp
@@ -228,7 +228,7 @@ class QueryNodeConverter : public QueryVisitor {
}
void visit(LocationTerm &node) override {
- createTerm(node, ParseItem::ITEM_LOCATION_TERM);
+ createTerm(node, ParseItem::ITEM_GEO_LOCATION_TERM);
}
void visit(PrefixTerm &node) override {
diff --git a/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h b/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
index 898db9785f6..a7e00d41555 100644
--- a/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
+++ b/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
@@ -6,6 +6,7 @@
#include "querybuilder.h"
#include "term.h"
#include <vespa/searchlib/parsequery/stackdumpiterator.h>
+#include <vespa/searchlib/common/geo_location_parser.h>
#include <vespa/vespalib/objects/hexdump.h>
namespace search::query {
@@ -141,17 +142,15 @@ private:
t = &builder.addStringTerm(term, view, id, weight);
} else if (type == ParseItem::ITEM_SUFFIXTERM) {
t = &builder.addSuffixTerm(term, view, id, weight);
- } else if (type == ParseItem::ITEM_LOCATION_TERM) {
- Location loc(term);
+ } else if (type == ParseItem::ITEM_GEO_LOCATION_TERM) {
+ search::common::GeoLocationParser parser;
+ parser.parseOldFormat(term);
+ Location loc(parser.getGeoLocation());
t = &builder.addLocationTerm(loc, view, id, weight);
} else if (type == ParseItem::ITEM_NUMTERM) {
if (term[0] == '[' || term[0] == '<' || term[0] == '>') {
Range range(term);
t = &builder.addRangeTerm(range, view, id, weight);
- } else if (term[0] == '(') {
- // TODO: handled above, should remove this block
- Location loc(term);
- t = &builder.addLocationTerm(loc, view, id, weight);
} else {
t = &builder.addNumberTerm(term, view, id, weight);
}
diff --git a/searchsummary/src/tests/docsummary/matched_elements_filter/matched_elements_filter_test.cpp b/searchsummary/src/tests/docsummary/matched_elements_filter/matched_elements_filter_test.cpp
index 0ac2f09e1b0..55c363a12c1 100644
--- a/searchsummary/src/tests/docsummary/matched_elements_filter/matched_elements_filter_test.cpp
+++ b/searchsummary/src/tests/docsummary/matched_elements_filter/matched_elements_filter_test.cpp
@@ -167,7 +167,6 @@ public:
~StateCallback() {}
void FillSummaryFeatures(GetDocsumsState*, IDocsumEnvironment*) override {}
void FillRankFeatures(GetDocsumsState*, IDocsumEnvironment*) override {}
- void ParseLocation(GetDocsumsState*) override {}
std::unique_ptr<MatchingElements> fill_matching_elements(const MatchingElementsFields&) override {
auto result = std::make_unique<MatchingElements>();
result->add_matching_elements(doc_id, _field_name, _matching_elements);
diff --git a/searchsummary/src/tests/docsummary/positionsdfw_test.cpp b/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
index 6fd0c39f06f..683bab49353 100644
--- a/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
+++ b/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
@@ -108,7 +108,6 @@ public:
struct MyGetDocsumsStateCallback : GetDocsumsStateCallback {
virtual void FillSummaryFeatures(GetDocsumsState *, IDocsumEnvironment *) override {}
virtual void FillRankFeatures(GetDocsumsState *, IDocsumEnvironment *) override {}
- virtual void ParseLocation(GetDocsumsState *) override {}
std::unique_ptr<MatchingElements> fill_matching_elements(const MatchingElementsFields &) override { abort(); }
};
diff --git a/searchsummary/src/tests/docsummary/slime_summary/slime_summary_test.cpp b/searchsummary/src/tests/docsummary/slime_summary/slime_summary_test.cpp
index 6fceef37f09..69249056c17 100644
--- a/searchsummary/src/tests/docsummary/slime_summary/slime_summary_test.cpp
+++ b/searchsummary/src/tests/docsummary/slime_summary/slime_summary_test.cpp
@@ -77,7 +77,6 @@ struct DocsumFixture : IDocsumStore, GetDocsumsStateCallback {
uint32_t getSummaryClassId() const override { return 0; }
void FillSummaryFeatures(GetDocsumsState *, IDocsumEnvironment *) override { }
void FillRankFeatures(GetDocsumsState *, IDocsumEnvironment *) override { }
- void ParseLocation(GetDocsumsState *) override { }
std::unique_ptr<MatchingElements> fill_matching_elements(const search::MatchingElementsFields &) override { abort(); }
};
diff --git a/searchsummary/src/tests/extractkeywords/extractkeywordstest.cpp b/searchsummary/src/tests/extractkeywords/extractkeywordstest.cpp
index 87317234a27..cd695984e03 100644
--- a/searchsummary/src/tests/extractkeywords/extractkeywordstest.cpp
+++ b/searchsummary/src/tests/extractkeywords/extractkeywordstest.cpp
@@ -179,7 +179,7 @@ ExtractKeywordsTest::RunTest(int testno, bool verify)
case 1:
{
// check that skipping these works also:
- stack.Push(new search::SimpleQueryStackItem(search::ParseItem::ITEM_LOCATION_TERM, "no"));
+ stack.Push(new search::SimpleQueryStackItem(search::ParseItem::ITEM_GEO_LOCATION_TERM, "no"));
stack.Push(new search::SimpleQueryStackItem(search::ParseItem::ITEM_NEAREST_NEIGHBOR, "no"));
// multi term query
stack.Push(new search::SimpleQueryStackItem(search::ParseItem::ITEM_TERM, "foobar"));
diff --git a/searchsummary/src/tests/extractkeywords/simplequerystackitem.cpp b/searchsummary/src/tests/extractkeywords/simplequerystackitem.cpp
index 5a4b6d76b8f..f12822949f9 100644
--- a/searchsummary/src/tests/extractkeywords/simplequerystackitem.cpp
+++ b/searchsummary/src/tests/extractkeywords/simplequerystackitem.cpp
@@ -24,7 +24,7 @@ void assert_term_type(ParseItem::ItemType type) {
assert(type == ParseItem::ITEM_TERM ||
type == ParseItem::ITEM_NUMTERM ||
type == ParseItem::ITEM_NEAREST_NEIGHBOR ||
- type == ParseItem::ITEM_LOCATION_TERM ||
+ type == ParseItem::ITEM_GEO_LOCATION_TERM ||
type == ParseItem::ITEM_PREFIXTERM ||
type == ParseItem::ITEM_SUBSTRINGTERM ||
type == ParseItem::ITEM_SUFFIXTERM ||
@@ -152,7 +152,7 @@ SimpleQueryStackItem::AppendBuffer(RawBuf *buf) const
break;
case ITEM_TERM:
case ITEM_NUMTERM:
- case ITEM_LOCATION_TERM:
+ case ITEM_GEO_LOCATION_TERM:
case ITEM_PREFIXTERM:
case ITEM_SUBSTRINGTERM:
case ITEM_EXACTSTRINGTERM:
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
index ebbf97e9f55..1f9b553b56a 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
@@ -2,11 +2,22 @@
#include "docsumstate.h"
#include <vespa/juniper/rpinterface.h>
+#include <vespa/document/datatype/positiondatatype.h>
#include <vespa/searchcommon/attribute/iattributecontext.h>
-#include <vespa/searchlib/common/location.h>
+#include <vespa/searchlib/common/geo_location.h>
+#include <vespa/searchlib/common/geo_location_parser.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
#include <vespa/searchlib/common/matching_elements.h>
+#include <vespa/searchlib/parsequery/parse.h>
+#include <vespa/searchlib/parsequery/stackdumpiterator.h>
#include "docsum_field_writer_state.h"
+#include <vespa/log/log.h>
+LOG_SETUP(".searchsummary.docsummary.docsumstate");
+
+using search::common::GeoLocationParser;
+using search::common::GeoLocationSpec;
+
namespace search::docsummary {
GetDocsumsState::GetDocsumsState(GetDocsumsStateCallback &callback)
@@ -22,7 +33,7 @@ GetDocsumsState::GetDocsumsState(GetDocsumsStateCallback &callback)
_attributes(),
_fieldWriterStates(),
_jsonStringer(),
- _parsedLocation(),
+ _parsedLocations(),
_summaryFeatures(NULL),
_summaryFeaturesCached(false),
_rankFeatures(NULL),
@@ -58,4 +69,43 @@ GetDocsumsState::get_matching_elements(const MatchingElementsFields &matching_el
return *_matching_elements;
}
+void
+GetDocsumsState::parse_locations()
+{
+ using document::PositionDataType;
+ assert(_parsedLocations.empty()); // only allowed to call this once
+ if (! _args.getLocation().empty()) {
+ GeoLocationParser parser;
+ if (parser.parseOldFormatWithField(_args.getLocation())) {
+ auto view = parser.getFieldName();
+ auto attr_name = PositionDataType::getZCurveFieldName(view);
+ GeoLocationSpec spec{attr_name, parser.getGeoLocation()};
+ _parsedLocations.push_back(spec);
+ } else {
+ LOG(warning, "could not parse location string '%s' from request",
+ _args.getLocation().c_str());
+ }
+ }
+ auto stackdump = _args.getStackDump();
+ if (! stackdump.empty()) {
+ search::SimpleQueryStackDumpIterator iterator(stackdump);
+ while (iterator.next()) {
+ if (iterator.getType() == search::ParseItem::ITEM_GEO_LOCATION_TERM) {
+ vespalib::string view = iterator.getIndexName();
+ vespalib::string term = iterator.getTerm();
+ GeoLocationParser parser;
+ if (parser.parseOldFormat(term)) {
+ auto attr_name = PositionDataType::getZCurveFieldName(view);
+ GeoLocationSpec spec{attr_name, parser.getGeoLocation()};
+ _parsedLocations.push_back(spec);
+ } else {
+ LOG(warning, "could not parse location string '%s' from stack dump",
+ term.c_str());
+ }
+ }
+ }
+ }
+}
+
+
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
index 57cae341682..46f8e52dd37 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
@@ -5,6 +5,7 @@
#include <vespa/searchlib/util/rawbuf.h>
#include <vespa/searchsummary/docsummary/getdocsumargs.h>
#include <vespa/searchlib/common/featureset.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
#include <vespa/vespalib/util/jsonwriter.h>
namespace juniper {
@@ -34,7 +35,6 @@ class GetDocsumsStateCallback
public:
virtual void FillSummaryFeatures(GetDocsumsState * state, IDocsumEnvironment * env) = 0;
virtual void FillRankFeatures(GetDocsumsState * state, IDocsumEnvironment * env) = 0;
- virtual void ParseLocation(GetDocsumsState * state) = 0;
virtual std::unique_ptr<MatchingElements> fill_matching_elements(const MatchingElementsFields &matching_elems_fields) = 0;
virtual ~GetDocsumsStateCallback(void) { }
GetDocsumsStateCallback(const GetDocsumsStateCallback &) = delete;
@@ -80,7 +80,8 @@ public:
vespalib::JSONStringer _jsonStringer;
// used by AbsDistanceDFW
- std::unique_ptr<search::common::Location> _parsedLocation;
+ std::vector<search::common::GeoLocationSpec> _parsedLocations;
+ void parse_locations();
// used by SummaryFeaturesDFW
FeatureSet::SP _summaryFeatures;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
index 37239fe9da6..8cc577355cf 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
@@ -270,7 +270,7 @@ JuniperQueryAdapter::Traverse(juniper::IQueryVisitor *v) const
case search::ParseItem::ITEM_PREDICATE_QUERY:
case search::ParseItem::ITEM_SAME_ELEMENT:
case search::ParseItem::ITEM_NEAREST_NEIGHBOR:
- case search::ParseItem::ITEM_LOCATION_TERM:
+ case search::ParseItem::ITEM_GEO_LOCATION_TERM:
if (!v->VisitOther(&item, iterator.getArity())) {
rc = SkipItem(&iterator);
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.cpp b/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.cpp
index 4e1544ee5d7..0af92adf2d2 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.cpp
@@ -8,6 +8,7 @@ GetDocsumArgs::GetDocsumArgs()
: _ranking(),
_resultClassName(),
_dumpFeatures(false),
+ _locations_possible(true),
_stackItems(0),
_stackDump(),
_location(),
@@ -27,6 +28,7 @@ GetDocsumArgs::initFromDocsumRequest(const search::engine::DocsumRequest &req)
_stackItems = req.stackItems;
_stackDump = req.stackDump;
_location = req.location;
+ _locations_possible = true;
_timeout = req.getTimeLeft();
_propertiesMap = req.propertiesMap;
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.h b/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.h
index c17f44baec9..0231b004674 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/getdocsumargs.h
@@ -17,6 +17,7 @@ private:
vespalib::string _ranking;
vespalib::string _resultClassName;
bool _dumpFeatures;
+ bool _locations_possible;
uint32_t _stackItems;
std::vector<char> _stackDump;
vespalib::string _location;
@@ -31,15 +32,14 @@ public:
void SetRankProfile(const vespalib::string &ranking) { _ranking = ranking; }
void setResultClassName(vespalib::stringref name) { _resultClassName = name; }
void SetStackDump(uint32_t stackItems, uint32_t stackDumpLen, const char *stackDump);
- void setLocation(vespalib::stringref location) {
- _location = location;
- }
-
+ void locations_possible(bool value) { _locations_possible = value; }
+ bool locations_possible() const { return _locations_possible; }
+ const vespalib::string &getLocation() const { return _location; }
+ void setLocation(const vespalib::string & location) { _location = location; }
void setTimeout(vespalib::duration timeout) { _timeout = timeout; }
vespalib::duration getTimeout() const { return _timeout; }
const vespalib::string & getResultClassName() const { return _resultClassName; }
- const vespalib::string & getLocation() const { return _location; }
const vespalib::stringref getStackDump() const {
return vespalib::stringref(&_stackDump[0], _stackDump.size());
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/matched_elements_filter_dfw.h b/searchsummary/src/vespa/searchsummary/docsummary/matched_elements_filter_dfw.h
index 087ddfd8d40..353f0df8bee 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/matched_elements_filter_dfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/matched_elements_filter_dfw.h
@@ -9,8 +9,9 @@ namespace search::attribute { class IAttributeContext; }
namespace search::docsummary {
/**
- * Field writer that filters matched elements (according to the query) from a complex field
- * (map of primitives, map of struct, array of struct) that is retrieved from the document store.
+ * Field writer that filters matched elements (according to the query) from a multi-value or complex field
+ * (array of primitive, weighted set of primitive, map of primitives, map of struct, array of struct)
+ * that is retrieved from the document store.
*/
class MatchedElementsFilterDFW : public IDocsumFieldWriter {
private:
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
index ecdde13b919..4fc2b1f4221 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
@@ -22,44 +22,57 @@ using search::attribute::BasicType;
using search::attribute::IntegerContent;
using search::common::Location;
+LocationAttrDFW::AllLocations
+LocationAttrDFW::getAllLocations(GetDocsumsState *state)
+{
+ AllLocations retval;
+ if (! state->_args.locations_possible()) {
+ return retval;
+ }
+ if (state->_parsedLocations.empty()) {
+ state->parse_locations();
+ }
+ for (const auto & loc : state->_parsedLocations) {
+ if (loc.location.valid()) {
+ LOG(debug, "found location(field %s) for DFW(field %s)\n",
+ loc.field_name.c_str(), getAttributeName().c_str());
+ if (getAttributeName() == loc.field_name) {
+ retval.matching.push_back(&loc.location);
+ } else {
+ retval.other.push_back(&loc.location);
+ }
+ }
+ }
+ if (retval.empty()) {
+ // avoid doing things twice
+ state->_args.locations_possible(false);
+ }
+ return retval;
+}
+
AbsDistanceDFW::AbsDistanceDFW(const vespalib::string & attrName) :
- AttrDFW(attrName)
+ LocationAttrDFW(attrName)
{ }
uint64_t
-AbsDistanceDFW::findMinDistance(uint32_t docid, GetDocsumsState *state)
+AbsDistanceDFW::findMinDistance(uint32_t docid, GetDocsumsState *state,
+ const std::vector<const GeoLoc *> &locations)
{
- search::common::Location &location = *state->_parsedLocation;
- const auto& attribute = get_attribute(*state);
-
uint64_t absdist = std::numeric_limits<int64_t>::max();
- int32_t docx = 0;
- int32_t docy = 0;
- IntegerContent pos;
- pos.fill(attribute, docid);
- uint32_t numValues = pos.size();
- for (uint32_t i = 0; i < numValues; i++) {
- int64_t docxy(pos[i]);
- vespalib::geo::ZCurve::decode(docxy, &docx, &docy);
- uint32_t dx;
- if (location.getX() > docx) {
- dx = location.getX() - docx;
- } else {
- dx = docx - location.getX();
- }
- if (location.getXAspect() != 0) {
- dx = ((uint64_t) dx * location.getXAspect()) >> 32;
- }
- uint32_t dy;
- if (location.getY() > docy) {
- dy = location.getY() - docy;
- } else {
- dy = docy - location.getY();
- }
- uint64_t dist2 = dx * (uint64_t) dx +
- dy * (uint64_t) dy;
- if (dist2 < absdist) {
- absdist = dist2;
+ const auto& attribute = get_attribute(*state);
+ for (auto location : locations) {
+ int32_t docx = 0;
+ int32_t docy = 0;
+ IntegerContent pos;
+ pos.fill(attribute, docid);
+ uint32_t numValues = pos.size();
+ for (uint32_t i = 0; i < numValues; i++) {
+ int64_t docxy(pos[i]);
+ vespalib::geo::ZCurve::decode(docxy, &docx, &docy);
+ uint64_t dist2 = location->sq_distance_to(GeoLoc::Point{docx, docy});
+ if (dist2 < absdist) {
+ absdist = dist2;
+ }
}
}
return (uint64_t) std::sqrt((double) absdist);
@@ -68,22 +81,11 @@ AbsDistanceDFW::findMinDistance(uint32_t docid, GetDocsumsState *state)
void
AbsDistanceDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target)
{
- bool forceEmpty = true;
-
- const vespalib::string &locationStr = state->_args.getLocation();
- if (locationStr.size() > 0) {
- if (!state->_parsedLocation) {
- state->_callback.ParseLocation(state);
- }
- assert(state->_parsedLocation);
- if (state->_parsedLocation->getParseError() == nullptr) {
- forceEmpty = false;
- }
+ const auto & all_locations = getAllLocations(state);
+ if (all_locations.empty()) {
+ return;
}
- if (forceEmpty) return;
-
- uint64_t absdist = findMinDistance(docid, state);
-
+ uint64_t absdist = findMinDistance(docid, state, all_locations.best());
if (type == RES_INT) {
target.insertLong(absdist);
} else {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
index 999da6f1860..c135737e44c 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
@@ -3,13 +3,40 @@
#pragma once
#include "attributedfw.h"
+#include <vespa/searchlib/common/geo_location_spec.h>
namespace search::docsummary {
-class AbsDistanceDFW : public AttrDFW
+class LocationAttrDFW : public AttrDFW
+{
+public:
+ using GeoLoc = search::common::GeoLocation;
+
+ LocationAttrDFW(const vespalib::string & attrName)
+ : AttrDFW(attrName)
+ {}
+
+ struct AllLocations {
+ std::vector<const GeoLoc *> matching;
+ std::vector<const GeoLoc *> other;
+
+ ~AllLocations() {}
+
+ bool empty() const {
+ return matching.empty() && other.empty();
+ }
+ const std::vector<const GeoLoc *> & best() const {
+ return matching.empty() ? other : matching;
+ }
+ };
+ AllLocations getAllLocations(GetDocsumsState *state);
+};
+
+class AbsDistanceDFW : public LocationAttrDFW
{
private:
- uint64_t findMinDistance(uint32_t docid, GetDocsumsState *state);
+ uint64_t findMinDistance(uint32_t docid, GetDocsumsState *state,
+ const std::vector<const GeoLoc *> &locations);
public:
AbsDistanceDFW(const vespalib::string & attrName);
diff --git a/searchsummary/src/vespa/searchsummary/test/mock_state_callback.h b/searchsummary/src/vespa/searchsummary/test/mock_state_callback.h
index b3ee405c856..f8b51ca14d0 100644
--- a/searchsummary/src/vespa/searchsummary/test/mock_state_callback.h
+++ b/searchsummary/src/vespa/searchsummary/test/mock_state_callback.h
@@ -18,7 +18,6 @@ public:
~MockStateCallback() override { }
void FillSummaryFeatures(GetDocsumsState*, IDocsumEnvironment*) override { }
void FillRankFeatures(GetDocsumsState*, IDocsumEnvironment*) override { }
- void ParseLocation(GetDocsumsState*) override { }
std::unique_ptr<MatchingElements> fill_matching_elements(const search::MatchingElementsFields&) override {
return std::make_unique<MatchingElements>(_matching_elems);
}
diff --git a/staging_vespalib/src/tests/state_server/state_server_test.cpp b/staging_vespalib/src/tests/state_server/state_server_test.cpp
index e61d3d216cd..2a07de10968 100644
--- a/staging_vespalib/src/tests/state_server/state_server_test.cpp
+++ b/staging_vespalib/src/tests/state_server/state_server_test.cpp
@@ -12,7 +12,7 @@
#include <vespa/vespalib/net/simple_metrics_producer.h>
#include <vespa/vespalib/net/simple_component_config_producer.h>
#include <vespa/vespalib/util/stringfmt.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/net/state_explorer.h>
#include <vespa/vespalib/net/slime_explorer.h>
#include <vespa/vespalib/net/generic_state_handler.h>
@@ -40,7 +40,7 @@ std::map<vespalib::string,vespalib::string> empty_params;
vespalib::string run_cmd(const vespalib::string &cmd) {
std::string out;
- ASSERT_TRUE(SlaveProc::run(cmd.c_str(), out));
+ ASSERT_TRUE(ChildProcess::run(cmd.c_str(), out));
return out;
}
diff --git a/standalone-container/pom.xml b/standalone-container/pom.xml
index b9062b9284c..479a76e2fc6 100644
--- a/standalone-container/pom.xml
+++ b/standalone-container/pom.xml
@@ -81,6 +81,7 @@
<extensions>true</extensions>
<configuration>
<discApplicationClass>com.yahoo.container.standalone.StandaloneContainerApplication</discApplicationClass>
+ <buildLegacyVespaPlatformBundle>true</buildLegacyVespaPlatformBundle>
<discPreInstallBundle>
configdefinitions-jar-with-dependencies.jar,
config-provisioning-jar-with-dependencies.jar,
diff --git a/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java b/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
index dd755e8e6dd..8ca20fdf7cd 100644
--- a/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
+++ b/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
@@ -2,8 +2,9 @@
package com.yahoo.container.standalone;
import com.yahoo.config.ConfigInstance;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.di.config.Subscriber;
import com.yahoo.vespa.config.ConfigKey;
import org.junit.Ignore;
@@ -23,7 +24,8 @@ import static org.junit.Assert.assertThat;
* @author ollivir
*/
public class StandaloneSubscriberTest {
- private static ConfigKey<ConfigInstance> bundlesKey = key("bundles");
+ private static ConfigKey<ConfigInstance> platformBundlesKey = key("platform-bundles");
+ private static ConfigKey<ConfigInstance> applicationBundlesKey = key("application-bundles");
private static ConfigKey<ConfigInstance> componentsKey = key("components");
private static ConfigKey<ConfigInstance> key(String name) {
@@ -35,16 +37,19 @@ public class StandaloneSubscriberTest {
public void standalone_subscriber() throws Exception {
withContainerModel("<container version=\"1.0\"></container>", root -> {
Set<ConfigKey<ConfigInstance>> keys = new HashSet<>();
- keys.add(bundlesKey);
+ keys.add(platformBundlesKey);
+ keys.add(applicationBundlesKey);
keys.add(componentsKey);
Subscriber subscriber = new StandaloneSubscriberFactory(root).getSubscriber(keys);
Map<ConfigKey<ConfigInstance>, ConfigInstance> config = subscriber.config();
assertThat(config.size(), is(2));
- BundlesConfig bundlesConfig = (BundlesConfig) config.get(bundlesKey);
+ PlatformBundlesConfig platformBundlesConfig = (PlatformBundlesConfig) config.get(platformBundlesKey);
+ ApplicationBundlesConfig applicationBundlesConfig = (ApplicationBundlesConfig) config.get(applicationBundlesKey);
ComponentsConfig componentsConfig = (ComponentsConfig) config.get(componentsKey);
- assertThat(bundlesConfig.bundle().size(), is(0));
+ assertThat(platformBundlesConfig.bundlePaths().size(), is(0));
+ assertThat(applicationBundlesConfig.bundles().size(), is(0));
assertThat(componentsConfig.components().size(), greaterThan(10));
return null;
});
diff --git a/storage/src/tests/common/metricstest.cpp b/storage/src/tests/common/metricstest.cpp
index d698cbb5e05..1e0144e9efb 100644
--- a/storage/src/tests/common/metricstest.cpp
+++ b/storage/src/tests/common/metricstest.cpp
@@ -175,16 +175,16 @@ void MetricsTest::createFakeLoad()
thread.internalJoin.count.inc(3 * n);
thread.mergeBuckets.count.inc(2 * n);
- thread.bytesMerged.inc(1000 * n);
thread.getBucketDiff.count.inc(4 * n);
thread.getBucketDiffReply.inc(4 * n);
thread.applyBucketDiff.count.inc(4 * n);
thread.applyBucketDiffReply.inc(4 * n);
- thread.mergeLatencyTotal.addValue(300 * n);
- thread.mergeMetadataReadLatency.addValue(20 * n);
- thread.mergeDataReadLatency.addValue(40 * n);
- thread.mergeDataWriteLatency.addValue(50 * n);
- thread.mergeAverageDataReceivedNeeded.addValue(0.8);
+ thread.merge_handler_metrics.bytesMerged.inc(1000 * n);
+ thread.merge_handler_metrics.mergeLatencyTotal.addValue(300 * n);
+ thread.merge_handler_metrics.mergeMetadataReadLatency.addValue(20 * n);
+ thread.merge_handler_metrics.mergeDataReadLatency.addValue(40 * n);
+ thread.merge_handler_metrics.mergeDataWriteLatency.addValue(50 * n);
+ thread.merge_handler_metrics.mergeAverageDataReceivedNeeded.addValue(0.8);
}
}
for (uint32_t i=0; i<_visitorMetrics->threads.size(); ++i) {
diff --git a/storage/src/tests/persistence/filestorage/operationabortingtest.cpp b/storage/src/tests/persistence/filestorage/operationabortingtest.cpp
index e9f878bfe1e..93c484368de 100644
--- a/storage/src/tests/persistence/filestorage/operationabortingtest.cpp
+++ b/storage/src/tests/persistence/filestorage/operationabortingtest.cpp
@@ -31,9 +31,9 @@ class BlockingMockProvider : public PersistenceProviderWrapper
public:
typedef std::unique_ptr<BlockingMockProvider> UP;
- mutable uint32_t _bucketInfoInvocations;
- uint32_t _createBucketInvocations;
- uint32_t _deleteBucketInvocations;
+ mutable std::atomic<uint32_t> _bucketInfoInvocations;
+ std::atomic<uint32_t> _createBucketInvocations;
+ std::atomic<uint32_t> _deleteBucketInvocations;
BlockingMockProvider(spi::PersistenceProvider& wrappedProvider,
vespalib::Barrier& queueBarrier,
diff --git a/storage/src/tests/persistence/mergehandlertest.cpp b/storage/src/tests/persistence/mergehandlertest.cpp
index 262906a4baf..df0ea3e6680 100644
--- a/storage/src/tests/persistence/mergehandlertest.cpp
+++ b/storage/src/tests/persistence/mergehandlertest.cpp
@@ -901,7 +901,7 @@ TEST_F(MergeHandlerTest, apply_bucket_diff_spi_failures) {
EXPECT_EQ("", doTestSPIException(handler, providerWrapper, invoker, *it));
// Casual, in-place testing of bug 6752085.
// This will fail if we give NaN to the metric in question.
- EXPECT_TRUE(std::isfinite(getEnv()._metrics.mergeAverageDataReceivedNeeded.getLast()));
+ EXPECT_TRUE(std::isfinite(getEnv()._metrics.merge_handler_metrics.mergeAverageDataReceivedNeeded.getLast()));
}
}
diff --git a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp
index 43cf43b02b6..73788d0affe 100644
--- a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp
+++ b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp
@@ -277,8 +277,8 @@ TwoPhaseUpdateOperation::schedulePutsWithUpdatedDocument(std::shared_ptr<documen
op.start(intermediate, _manager.getClock().getTimeInMillis());
transitionTo(SendState::PUTS_SENT);
- LOG(debug, "Update(%s): sending Put commands with doc %s",
- update_doc_id().c_str(), doc->toString(true).c_str());
+ LOG(debug, "Update(%s): sending Puts at timestamp %" PRIu64, update_doc_id().c_str(), putTimestamp);
+ LOG(spam, "Update(%s): Put document is: %s", update_doc_id().c_str(), doc->toString(true).c_str());
if (intermediate._reply.get()) {
sendReplyWithResult(sender, intermediate._reply->getResult());
diff --git a/storage/src/vespa/storage/distributor/pending_bucket_space_db_transition.h b/storage/src/vespa/storage/distributor/pending_bucket_space_db_transition.h
index 695f80750aa..232f1186879 100644
--- a/storage/src/vespa/storage/distributor/pending_bucket_space_db_transition.h
+++ b/storage/src/vespa/storage/distributor/pending_bucket_space_db_transition.h
@@ -50,6 +50,7 @@ private:
uint16_t _distributorIndex;
bool _bucketOwnershipTransfer;
std::unordered_map<uint16_t, size_t> _rejectedRequests;
+ std::unordered_map<uint16_t, size_t> _failed_requests; // Also includes rejections
BucketDatabase::MergingProcessor::Result merge(BucketDatabase::Merger&) override;
void insert_remaining_at_end(BucketDatabase::TrailingInserter&) override;
@@ -122,6 +123,13 @@ public:
auto iter = _rejectedRequests.find(node);
return ((iter != _rejectedRequests.end()) ? iter->second : 0);
}
+ void increment_request_failures(uint16_t node) {
+ _failed_requests[node]++;
+ }
+ [[nodiscard]] size_t request_failures(uint16_t node) const noexcept {
+ auto iter = _failed_requests.find(node);
+ return ((iter != _failed_requests.end()) ? iter->second : 0);
+ }
};
}
diff --git a/storage/src/vespa/storage/distributor/pendingclusterstate.cpp b/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
index 62a520abc87..3dda989ff74 100644
--- a/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
+++ b/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
@@ -12,7 +12,7 @@
#include <vespa/vespalib/util/xmlstream.hpp>
#include <climits>
-#include <vespa/log/log.h>
+#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".pendingclusterstate");
using document::BucketSpace;
@@ -248,7 +248,22 @@ PendingClusterState::Summary::Summary(const std::string& prevClusterState,
PendingClusterState::Summary::Summary(const Summary &) = default;
PendingClusterState::Summary & PendingClusterState::Summary::operator = (const Summary &) = default;
-PendingClusterState::Summary::~Summary() { }
+PendingClusterState::Summary::~Summary() = default;
+
+void PendingClusterState::update_reply_failure_statistics(const api::ReturnCode& result, const BucketSpaceAndNode& source) {
+ auto transition_iter = _pendingTransitions.find(source.bucketSpace);
+ assert(transition_iter != _pendingTransitions.end());
+ auto& transition = *transition_iter->second;
+ transition.increment_request_failures(source.node);
+ // Edge triggered (rate limited) warning for content node bucket fetching failures
+ if (transition.request_failures(source.node) == RequestFailureWarningEdgeTriggerThreshold) {
+ LOGBP(warning, "Have failed multiple bucket info fetch requests towards node %u. Last received error is: %s",
+ source.node, result.toString().c_str());
+ }
+ if (result.getResult() == api::ReturnCode::REJECTED) {
+ transition.incrementRequestRejections(source.node);
+ }
+}
bool
PendingClusterState::onRequestBucketInfoReply(const std::shared_ptr<api::RequestBucketInfoReply>& reply)
@@ -266,11 +281,7 @@ PendingClusterState::onRequestBucketInfoReply(const std::shared_ptr<api::Request
resendTime += framework::MilliSecTime(100);
_delayedRequests.emplace_back(resendTime, bucketSpaceAndNode);
_sentMessages.erase(iter);
- if (result.getResult() == api::ReturnCode::REJECTED) {
- auto transitionIter = _pendingTransitions.find(bucketSpaceAndNode.bucketSpace);
- assert(transitionIter != _pendingTransitions.end());
- transitionIter->second->incrementRequestRejections(bucketSpaceAndNode.node);
- }
+ update_reply_failure_statistics(result, bucketSpaceAndNode);
return true;
}
diff --git a/storage/src/vespa/storage/distributor/pendingclusterstate.h b/storage/src/vespa/storage/distributor/pendingclusterstate.h
index 7aa35b32b8e..f79a3185c67 100644
--- a/storage/src/vespa/storage/distributor/pendingclusterstate.h
+++ b/storage/src/vespa/storage/distributor/pendingclusterstate.h
@@ -155,6 +155,10 @@ public:
std::string requestNodesToString() const;
private:
+ // With 100ms resend timeout, this requires a particular node to have failed
+ // for _at least_ threshold/10 seconds before a log warning is emitted.
+ constexpr static size_t RequestFailureWarningEdgeTriggerThreshold = 20;
+
/**
* Creates a pending cluster state that represents
* a set system state command from the fleet controller.
@@ -211,6 +215,7 @@ private:
std::string getPrevClusterStateBundleString() const {
return _prevClusterStateBundle.getBaselineClusterState()->toString();
}
+ void update_reply_failure_statistics(const api::ReturnCode& result, const BucketSpaceAndNode& source);
std::shared_ptr<api::SetSystemStateCommand> _cmd;
diff --git a/storage/src/vespa/storage/persistence/filestorage/CMakeLists.txt b/storage/src/vespa/storage/persistence/filestorage/CMakeLists.txt
index d394ec3a5cf..182bf04b1cf 100644
--- a/storage/src/vespa/storage/persistence/filestorage/CMakeLists.txt
+++ b/storage/src/vespa/storage/persistence/filestorage/CMakeLists.txt
@@ -5,6 +5,7 @@ vespa_add_library(storage_filestorpersistence OBJECT
filestorhandlerimpl.cpp
filestormanager.cpp
filestormetrics.cpp
+ merge_handler_metrics.cpp
mergestatus.cpp
modifiedbucketchecker.cpp
DEPENDS
diff --git a/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp b/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp
index 65ee78f7642..85604299e85 100644
--- a/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp
+++ b/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp
@@ -130,9 +130,9 @@ FileStorManager::configure(std::unique_ptr<vespa::config::content::StorFilestorC
_metrics->initDiskMetrics(_disks.size(), _component.getLoadTypes()->getMetricLoadTypes(), numStripes, numThreads);
_filestorHandler = std::make_unique<FileStorHandler>(numThreads, numStripes, *this, *_metrics, _partitions, _compReg);
- uint32_t numResposeThreads = computeNumResponseThreads(_config->numResponseThreads);
- if (numResposeThreads > 0) {
- _sequencedExecutor = vespalib::SequencedTaskExecutor::create(numResposeThreads, 10000, selectSequencer(_config->responseSequencerType));
+ uint32_t numResponseThreads = computeNumResponseThreads(_config->numResponseThreads);
+ if (numResponseThreads > 0) {
+ _sequencedExecutor = vespalib::SequencedTaskExecutor::create(numResponseThreads, 10000, selectSequencer(_config->responseSequencerType));
}
for (uint32_t i=0; i<_component.getDiskCount(); ++i) {
if (_partitions[i].isUp()) {
diff --git a/storage/src/vespa/storage/persistence/filestorage/filestormetrics.cpp b/storage/src/vespa/storage/persistence/filestorage/filestormetrics.cpp
index 0be046b2e9e..a80b17d4f19 100644
--- a/storage/src/vespa/storage/persistence/filestorage/filestormetrics.cpp
+++ b/storage/src/vespa/storage/persistence/filestorage/filestormetrics.cpp
@@ -152,23 +152,9 @@ FileStorThreadMetrics::FileStorThreadMetrics(const std::string& name, const std:
mergeBuckets("mergebuckets", "Number of times buckets have been merged.", this),
getBucketDiff("getbucketdiff", "Number of getbucketdiff commands that have been processed.", this),
applyBucketDiff("applybucketdiff", "Number of applybucketdiff commands that have been processed.", this),
- bytesMerged("bytesmerged", {}, "Total number of bytes merged into this node.", this),
getBucketDiffReply("getbucketdiffreply", {}, "Number of getbucketdiff replies that have been processed.", this),
applyBucketDiffReply("applybucketdiffreply", {}, "Number of applybucketdiff replies that have been processed.", this),
- mergeLatencyTotal("mergelatencytotal", {},
- "Latency of total merge operation, from master node receives "
- "it, until merge is complete and master node replies.", this),
- mergeMetadataReadLatency("mergemetadatareadlatency", {},
- "Latency of time used in a merge step to check metadata of "
- "current node to see what data it has.", this),
- mergeDataReadLatency("mergedatareadlatency", {},
- "Latency of time used in a merge step to read data other "
- "nodes need.", this),
- mergeDataWriteLatency("mergedatawritelatency", {},
- "Latency of time used in a merge step to write data needed to "
- "current node.", this),
- mergeAverageDataReceivedNeeded("mergeavgdatareceivedneeded", {}, "Amount of data transferred from previous node "
- "in chain that we needed to apply locally.", this),
+ merge_handler_metrics(this),
batchingSize("batchingsize", {}, "Number of operations batched per bucket (only counts "
"batches of size > 1)", this)
{ }
diff --git a/storage/src/vespa/storage/persistence/filestorage/filestormetrics.h b/storage/src/vespa/storage/persistence/filestorage/filestormetrics.h
index 3bc7fa33660..be1c5c48213 100644
--- a/storage/src/vespa/storage/persistence/filestorage/filestormetrics.h
+++ b/storage/src/vespa/storage/persistence/filestorage/filestormetrics.h
@@ -10,6 +10,7 @@
#pragma once
+#include "merge_handler_metrics.h"
#include <vespa/metrics/metrics.h>
#include <vespa/documentapi/loadtypes/loadtypeset.h>
@@ -99,15 +100,9 @@ struct FileStorThreadMetrics : public metrics::MetricSet
Op mergeBuckets;
Op getBucketDiff;
Op applyBucketDiff;
-
- metrics::LongCountMetric bytesMerged;
metrics::LongCountMetric getBucketDiffReply;
metrics::LongCountMetric applyBucketDiffReply;
- metrics::DoubleAverageMetric mergeLatencyTotal;
- metrics::DoubleAverageMetric mergeMetadataReadLatency;
- metrics::DoubleAverageMetric mergeDataReadLatency;
- metrics::DoubleAverageMetric mergeDataWriteLatency;
- metrics::DoubleAverageMetric mergeAverageDataReceivedNeeded;
+ MergeHandlerMetrics merge_handler_metrics;
metrics::LongAverageMetric batchingSize;
FileStorThreadMetrics(const std::string& name, const std::string& desc, const metrics::LoadTypeSet& lt);
diff --git a/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.cpp b/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.cpp
new file mode 100644
index 00000000000..d4e82b8a64f
--- /dev/null
+++ b/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.cpp
@@ -0,0 +1,28 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "merge_handler_metrics.h"
+
+namespace storage {
+
+MergeHandlerMetrics::MergeHandlerMetrics(metrics::MetricSet* owner)
+ : bytesMerged("bytesmerged", {}, "Total number of bytes merged into this node.", owner),
+ mergeLatencyTotal("mergelatencytotal", {},
+ "Latency of total merge operation, from master node receives "
+ "it, until merge is complete and master node replies.", owner),
+ mergeMetadataReadLatency("mergemetadatareadlatency", {},
+ "Latency of time used in a merge step to check metadata of "
+ "current node to see what data it has.", owner),
+ mergeDataReadLatency("mergedatareadlatency", {},
+ "Latency of time used in a merge step to read data other "
+ "nodes need.", owner),
+ mergeDataWriteLatency("mergedatawritelatency", {},
+ "Latency of time used in a merge step to write data needed to "
+ "current node.", owner),
+ mergeAverageDataReceivedNeeded("mergeavgdatareceivedneeded", {}, "Amount of data transferred from previous node "
+ "in chain that we needed to apply locally.", owner),
+ put_latency("put_latency", {}, "Latency of individual puts that are part of merge operations", owner),
+ remove_latency("remove_latency", {}, "Latency of individual removes that are part of merge operations", owner)
+{}
+
+MergeHandlerMetrics::~MergeHandlerMetrics() = default;
+
+}
diff --git a/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.h b/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.h
new file mode 100644
index 00000000000..ffa3cf204a3
--- /dev/null
+++ b/storage/src/vespa/storage/persistence/filestorage/merge_handler_metrics.h
@@ -0,0 +1,32 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/metrics/metrics.h>
+
+namespace storage {
+
+// Provides a convenient wrapper for all MergeHandler-related metrics.
+// This is _not_ its own MetricSet; metrics are owned by an explicitly provided
+// parent. This is to prevent metric paths from changing, as external aggregation
+// depends on the existing paths.
+struct MergeHandlerMetrics {
+ metrics::LongCountMetric bytesMerged;
+ // Aggregate metrics:
+ metrics::DoubleAverageMetric mergeLatencyTotal;
+ metrics::DoubleAverageMetric mergeMetadataReadLatency;
+ metrics::DoubleAverageMetric mergeDataReadLatency;
+ metrics::DoubleAverageMetric mergeDataWriteLatency;
+ metrics::DoubleAverageMetric mergeAverageDataReceivedNeeded;
+ // Individual operation metrics. These capture both count and latency sum, so
+ // no need for explicit count metric on the side.
+ metrics::DoubleAverageMetric put_latency;
+ metrics::DoubleAverageMetric remove_latency;
+ // Iteration over metadata and document payload data is already covered by
+ // the merge[Meta]Data(Read|Write)Latency metrics, so not repeated here. Can be
+ // explicitly added if deemed required.
+
+ explicit MergeHandlerMetrics(metrics::MetricSet* owner);
+ ~MergeHandlerMetrics();
+};
+
+}
diff --git a/storage/src/vespa/storage/persistence/mergehandler.cpp b/storage/src/vespa/storage/persistence/mergehandler.cpp
index 3483b15dd0e..70894858887 100644
--- a/storage/src/vespa/storage/persistence/mergehandler.cpp
+++ b/storage/src/vespa/storage/persistence/mergehandler.cpp
@@ -5,7 +5,6 @@
#include <vespa/vespalib/stllike/asciistream.h>
#include <vespa/vdslib/distribution/distribution.h>
#include <vespa/document/fieldset/fieldsets.h>
-#include <vespa/storage/common/bucketoperationlogger.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/vespalib/util/exceptions.h>
#include <algorithm>
@@ -34,7 +33,7 @@ MergeHandler::MergeHandler(spi::PersistenceProvider& spi,
namespace {
-int getDeleteFlag() {
+constexpr int getDeleteFlag() {
// Referred into old slotfile code before. Where should this number come from?
return 2;
}
@@ -71,8 +70,7 @@ checkResult(const spi::Result& result,
}
-class IteratorGuard
-{
+class IteratorGuard {
spi::PersistenceProvider& _spi;
spi::IteratorId _iteratorId;
spi::Context& _context;
@@ -84,15 +82,13 @@ public:
_iteratorId(iteratorId),
_context(context)
{}
- ~IteratorGuard()
- {
+ ~IteratorGuard() {
assert(_iteratorId != 0);
_spi.destroyIterator(_iteratorId, _context);
}
};
-struct IndirectDocEntryTimestampPredicate
-{
+struct IndirectDocEntryTimestampPredicate {
bool operator()(const spi::DocEntry::UP& e1,
const spi::DocEntry::UP& e2) const
{
@@ -106,8 +102,7 @@ struct IndirectDocEntryTimestampPredicate
}
};
-struct DiffEntryTimestampPredicate
-{
+struct DiffEntryTimestampPredicate {
bool operator()(const api::ApplyBucketDiffCommand::Entry& e,
const api::Timestamp timestamp) const
{
@@ -221,7 +216,6 @@ MergeHandler::buildBucketInfoList(
bucket.toString().c_str(),
providerInfo.toString().c_str(),
dbInfo.toString().c_str());
- DUMP_LOGGED_BUCKET_OPERATIONS(bucket.getBucketId());
}
entry->setBucketInfo(providerInfo);
@@ -538,16 +532,14 @@ MergeHandler::applyDiffEntry(const spi::Bucket& bucket,
// Regular put entry
Document::SP doc(deserializeDiffDocument(e, repo));
DocumentId docId = doc->getId();
- checkResult(_spi.put(bucket, timestamp, std::move(doc), context),
- bucket,
- docId,
- "put");
+ framework::MilliSecTimer start_time(_env._component.getClock());
+ checkResult(_spi.put(bucket, timestamp, std::move(doc), context), bucket, docId, "put");
+ _env._metrics.merge_handler_metrics.put_latency.addValue(start_time.getElapsedTimeAsDouble());
} else {
DocumentId docId(e._docName);
- checkResult(_spi.remove(bucket, timestamp, docId, context),
- bucket,
- docId,
- "remove");
+ framework::MilliSecTimer start_time(_env._component.getClock());
+ checkResult(_spi.remove(bucket, timestamp, docId, context), bucket, docId, "remove");
+ _env._metrics.merge_handler_metrics.remove_latency.addValue(start_time.getElapsedTimeAsDouble());
}
}
@@ -660,10 +652,10 @@ MergeHandler::applyDiffLocally(
}
if (byteCount + notNeededByteCount != 0) {
- _env._metrics.mergeAverageDataReceivedNeeded.addValue(
+ _env._metrics.merge_handler_metrics.mergeAverageDataReceivedNeeded.addValue(
static_cast<double>(byteCount) / (byteCount + notNeededByteCount));
}
- _env._metrics.bytesMerged.inc(byteCount);
+ _env._metrics.merge_handler_metrics.bytesMerged.inc(byteCount);
LOG(debug, "Merge(%s): Applied %u entries locally from ApplyBucketDiff.",
bucket.toString().c_str(), addedCount);
@@ -870,7 +862,7 @@ MergeHandler::processBucketMerge(const spi::Bucket& bucket, MergeStatus& status,
if (applyDiffNeedLocalData(cmd->getDiff(), 0, true)) {
framework::MilliSecTimer startTime(_env._component.getClock());
fetchLocalData(bucket, cmd->getLoadType(), cmd->getDiff(), 0, context);
- _env._metrics.mergeDataReadLatency.addValue(
+ _env._metrics.merge_handler_metrics.mergeDataReadLatency.addValue(
startTime.getElapsedTimeAsDouble());
}
status.pendingId = cmd->getMsgId();
@@ -966,7 +958,7 @@ MergeHandler::handleMergeBucket(api::MergeBucketCommand& cmd, MessageTracker::UP
"Bucket not found in buildBucketInfo step");
return tracker;
}
- _env._metrics.mergeMetadataReadLatency.addValue(s->startTime.getElapsedTimeAsDouble());
+ _env._metrics.merge_handler_metrics.mergeMetadataReadLatency.addValue(s->startTime.getElapsedTimeAsDouble());
LOG(spam, "Sending GetBucketDiff %" PRIu64 " for %s to next node %u "
"with diff of %u entries.",
cmd2->getMsgId(),
@@ -1039,19 +1031,19 @@ namespace {
result.push_back(b);
++j;
} else {
- // If we find equal timestamped entries that are not the
- // same.. Flag an error. But there is nothing we can do
- // about it. Note it as if it is the same entry so we
- // dont try to merge them.
+ // If we find equal timestamped entries that are not the
+ // same.. Flag an error. But there is nothing we can do
+ // about it. Note it as if it is the same entry so we
+ // dont try to merge them.
if (!(a == b)) {
if (a._gid == b._gid && a._flags == b._flags) {
if ((a._flags & getDeleteFlag()) != 0 &&
(b._flags & getDeleteFlag()) != 0)
{
- // Unfortunately this can happen, for instance
- // if a remove comes to a bucket out of sync
- // and reuses different headers in the two
- // versions.
+ // Unfortunately this can happen, for instance
+ // if a remove comes to a bucket out of sync
+ // and reuses different headers in the two
+ // versions.
LOG(debug, "Found entries with equal timestamps of "
"the same gid who both are remove "
"entries: %s <-> %s.",
@@ -1071,9 +1063,9 @@ namespace {
} else if ((a._flags & getDeleteFlag())
!= (b._flags & getDeleteFlag()))
{
- // If we find one remove and one put entry on the
- // same timestamp we are going to keep the remove
- // entry to make the copies consistent.
+ // If we find one remove and one put entry on the
+ // same timestamp we are going to keep the remove
+ // entry to make the copies consistent.
const api::GetBucketDiffCommand::Entry& deletedEntry(
(a._flags & getDeleteFlag()) != 0 ? a : b);
result.push_back(deletedEntry);
@@ -1146,7 +1138,7 @@ MergeHandler::handleGetBucketDiff(api::GetBucketDiffCommand& cmd, MessageTracker
LOG(error, "Diffing %s found suspect entries.",
bucket.toString().c_str());
}
- _env._metrics.mergeMetadataReadLatency.addValue(
+ _env._metrics.merge_handler_metrics.mergeMetadataReadLatency.addValue(
startTime.getElapsedTimeAsDouble());
// If last node in merge chain, we can send reply straight away
@@ -1243,7 +1235,6 @@ MergeHandler::handleGetBucketDiffReply(api::GetBucketDiffReply& reply,
LOG(warning, "Got GetBucketDiffReply for %s which we have no "
"merge state for.",
bucket.toString().c_str());
- DUMP_LOGGED_BUCKET_OPERATIONS(bucket.getBucketId());
return;
}
@@ -1252,7 +1243,6 @@ MergeHandler::handleGetBucketDiffReply(api::GetBucketDiffReply& reply,
LOG(warning, "Got GetBucketDiffReply for %s which had message "
"id %" PRIu64 " when we expected %" PRIu64 ". Ignoring reply.",
bucket.toString().c_str(), reply.getMsgId(), s.pendingId);
- DUMP_LOGGED_BUCKET_OPERATIONS(bucket.getBucketId());
return;
}
api::StorageReply::SP replyToSend;
@@ -1280,7 +1270,7 @@ MergeHandler::handleGetBucketDiffReply(api::GetBucketDiffReply& reply,
// We have sent something on, and shouldn't reply now.
clearState = false;
} else {
- _env._metrics.mergeLatencyTotal.addValue(
+ _env._metrics.merge_handler_metrics.mergeLatencyTotal.addValue(
s.startTime.getElapsedTimeAsDouble());
}
}
@@ -1327,7 +1317,7 @@ MergeHandler::handleApplyBucketDiff(api::ApplyBucketDiffCommand& cmd, MessageTra
if (applyDiffNeedLocalData(cmd.getDiff(), index, !lastInChain)) {
framework::MilliSecTimer startTime(_env._component.getClock());
fetchLocalData(bucket, cmd.getLoadType(), cmd.getDiff(), index, tracker->context());
- _env._metrics.mergeDataReadLatency.addValue(startTime.getElapsedTimeAsDouble());
+ _env._metrics.merge_handler_metrics.mergeDataReadLatency.addValue(startTime.getElapsedTimeAsDouble());
} else {
LOG(spam, "Merge(%s): Moving %zu entries, didn't need "
"local data on node %u (%u).",
@@ -1340,7 +1330,7 @@ MergeHandler::handleApplyBucketDiff(api::ApplyBucketDiffCommand& cmd, MessageTra
framework::MilliSecTimer startTime(_env._component.getClock());
api::BucketInfo info(applyDiffLocally(bucket, cmd.getLoadType(),
cmd.getDiff(), index, tracker->context()));
- _env._metrics.mergeDataWriteLatency.addValue(
+ _env._metrics.merge_handler_metrics.mergeDataWriteLatency.addValue(
startTime.getElapsedTimeAsDouble());
} else {
LOG(spam, "Merge(%s): Didn't need fetched data on node %u (%u).",
@@ -1409,7 +1399,6 @@ MergeHandler::handleApplyBucketDiffReply(api::ApplyBucketDiffReply& reply,
LOG(warning, "Got ApplyBucketDiffReply for %s which we have no "
"merge state for.",
bucket.toString().c_str());
- DUMP_LOGGED_BUCKET_OPERATIONS(bucket.getBucketId());
return;
}
@@ -1418,7 +1407,6 @@ MergeHandler::handleApplyBucketDiffReply(api::ApplyBucketDiffReply& reply,
LOG(warning, "Got ApplyBucketDiffReply for %s which had message "
"id %" PRIu64 " when we expected %" PRIu64 ". Ignoring reply.",
bucket.toString().c_str(), reply.getMsgId(), s.pendingId);
- DUMP_LOGGED_BUCKET_OPERATIONS(bucket.getBucketId());
return;
}
bool clearState = true;
@@ -1436,7 +1424,7 @@ MergeHandler::handleApplyBucketDiffReply(api::ApplyBucketDiffReply& reply,
framework::MilliSecTimer startTime(_env._component.getClock());
fetchLocalData(bucket, reply.getLoadType(), diff, index,
s.context);
- _env._metrics.mergeDataReadLatency.addValue(
+ _env._metrics.merge_handler_metrics.mergeDataReadLatency.addValue(
startTime.getElapsedTimeAsDouble());
}
if (applyDiffHasLocallyNeededData(diff, index)) {
@@ -1444,7 +1432,7 @@ MergeHandler::handleApplyBucketDiffReply(api::ApplyBucketDiffReply& reply,
api::BucketInfo info(
applyDiffLocally(bucket, reply.getLoadType(), diff,
index, s.context));
- _env._metrics.mergeDataWriteLatency.addValue(
+ _env._metrics.merge_handler_metrics.mergeDataWriteLatency.addValue(
startTime.getElapsedTimeAsDouble());
} else {
LOG(spam, "Merge(%s): Didn't need fetched data on node %u (%u)",
@@ -1491,7 +1479,7 @@ MergeHandler::handleApplyBucketDiffReply(api::ApplyBucketDiffReply& reply,
// We have sent something on and shouldn't reply now.
clearState = false;
} else {
- _env._metrics.mergeLatencyTotal.addValue(
+ _env._metrics.merge_handler_metrics.mergeLatencyTotal.addValue(
s.startTime.getElapsedTimeAsDouble());
}
}
diff --git a/storage/src/vespa/storage/persistence/persistencethread.cpp b/storage/src/vespa/storage/persistence/persistencethread.cpp
index 76c241d0627..a96e9870d36 100644
--- a/storage/src/vespa/storage/persistence/persistencethread.cpp
+++ b/storage/src/vespa/storage/persistence/persistencethread.cpp
@@ -919,7 +919,7 @@ PersistenceThread::processLockedMessage(FileStorHandler::LockedMessage lock) {
void
PersistenceThread::run(framework::ThreadHandle& thread)
{
- LOG(debug, "Started persistence thread with pid %d", getpid());
+ LOG(debug, "Started persistence thread");
while (!thread.interrupted() && !_env._fileStorHandler.closed(_env._partition)) {
thread.registerTick();
@@ -933,7 +933,7 @@ PersistenceThread::run(framework::ThreadHandle& thread)
vespalib::MonitorGuard flushMonitorGuard(_flushMonitor);
flushMonitorGuard.broadcast();
}
- LOG(debug, "Closing down persistence thread %d", getpid());
+ LOG(debug, "Closing down persistence thread");
vespalib::MonitorGuard flushMonitorGuard(_flushMonitor);
_closed = true;
flushMonitorGuard.broadcast();
diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.cpp b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
index d0d1056d4fd..c0adb01ad47 100644
--- a/storage/src/vespa/storage/storageserver/communicationmanager.cpp
+++ b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
@@ -46,7 +46,7 @@ void
CommunicationManager::receiveStorageReply(const std::shared_ptr<api::StorageReply>& reply)
{
assert(reply);
- enque_or_process(reply);
+ enqueue_or_process(reply);
}
namespace {
@@ -101,7 +101,7 @@ CommunicationManager::handleMessage(std::unique_ptr<mbus::Message> msg)
cmd->setTrace(docMsgPtr->getTrace());
cmd->setTransportContext(std::make_unique<StorageTransportContext>(std::move(docMsgPtr)));
- enque_or_process(std::move(cmd));
+ enqueue_or_process(std::move(cmd));
} else if (protocolName == mbusprot::StorageProtocol::NAME) {
std::unique_ptr<mbusprot::StorageCommand> storMsgPtr(static_cast<mbusprot::StorageCommand*>(msg.release()));
@@ -113,7 +113,7 @@ CommunicationManager::handleMessage(std::unique_ptr<mbus::Message> msg)
cmd->setTrace(storMsgPtr->getTrace());
cmd->setTransportContext(std::make_unique<StorageTransportContext>(std::move(storMsgPtr)));
- enque_or_process(std::move(cmd));
+ enqueue_or_process(std::move(cmd));
} else {
LOGBM(warning, "Received unsupported message type %d for protocol '%s'",
msg->getType(), msg->getProtocol().c_str());
@@ -443,7 +443,7 @@ CommunicationManager::process(const std::shared_ptr<api::StorageMessage>& msg)
}
void
-CommunicationManager::enque_or_process(std::shared_ptr<api::StorageMessage> msg)
+CommunicationManager::enqueue_or_process(std::shared_ptr<api::StorageMessage> msg)
{
assert(msg);
if (_skip_thread) {
diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.h b/storage/src/vespa/storage/storageserver/communicationmanager.h
index c2d5ea7039d..23b59f5a42a 100644
--- a/storage/src/vespa/storage/storageserver/communicationmanager.h
+++ b/storage/src/vespa/storage/storageserver/communicationmanager.h
@@ -114,7 +114,7 @@ private:
bool _skip_thread;
void updateMetrics(const MetricLockGuard &) override;
- void enque_or_process(std::shared_ptr<api::StorageMessage> msg);
+ void enqueue_or_process(std::shared_ptr<api::StorageMessage> msg);
// Test needs access to configure() for live reconfig testing.
friend struct CommunicationManagerTest;
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
index fd16f43050a..2595514e6f4 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
@@ -19,16 +19,22 @@ ComponentRegisterImpl::ComponentRegisterImpl()
_shutdownListener(nullptr)
{ }
-ComponentRegisterImpl::~ComponentRegisterImpl() { }
+ComponentRegisterImpl::~ComponentRegisterImpl() = default;
void
ComponentRegisterImpl::registerComponent(ManagedComponent& mc)
{
vespalib::LockGuard lock(_componentLock);
_components.push_back(&mc);
- if (_clock != 0) mc.setClock(*_clock);
- if (_threadPool != 0) mc.setThreadPool(*_threadPool);
- if (_metricManager != 0) mc.setMetricRegistrator(*this);
+ if (_clock) {
+ mc.setClock(*_clock);
+ }
+ if (_threadPool) {
+ mc.setThreadPool(*_threadPool);
+ }
+ if (_metricManager) {
+ mc.setMetricRegistrator(*this);
+ }
mc.setUpgradeFlag(_upgradeFlag);
}
@@ -36,7 +42,7 @@ void
ComponentRegisterImpl::requestShutdown(vespalib::stringref reason)
{
vespalib::LockGuard lock(_componentLock);
- if (_shutdownListener != 0) {
+ if (_shutdownListener) {
_shutdownListener->requestShutdown(reason);
}
}
@@ -47,7 +53,7 @@ ComponentRegisterImpl::setMetricManager(metrics::MetricManager& mm)
std::vector<ManagedComponent*> components;
{
vespalib::LockGuard lock(_componentLock);
- assert(_metricManager == 0);
+ assert(_metricManager == nullptr);
components = _components;
_metricManager = &mm;
}
@@ -55,8 +61,8 @@ ComponentRegisterImpl::setMetricManager(metrics::MetricManager& mm)
metrics::MetricLockGuard lock(mm.getMetricLock());
mm.registerMetric(lock, _topMetricSet);
}
- for (uint32_t i=0; i<components.size(); ++i) {
- components[i]->setMetricRegistrator(*this);
+ for (auto* component : _components) {
+ component->setMetricRegistrator(*this);
}
}
@@ -64,10 +70,10 @@ void
ComponentRegisterImpl::setClock(Clock& c)
{
vespalib::LockGuard lock(_componentLock);
- assert(_clock == 0);
+ assert(_clock == nullptr);
_clock = &c;
- for (uint32_t i=0; i<_components.size(); ++i) {
- _components[i]->setClock(c);
+ for (auto* component : _components) {
+ component->setClock(c);
}
}
@@ -75,10 +81,10 @@ void
ComponentRegisterImpl::setThreadPool(ThreadPool& tp)
{
vespalib::LockGuard lock(_componentLock);
- assert(_threadPool == 0);
+ assert(_threadPool == nullptr);
_threadPool = &tp;
- for (uint32_t i=0; i<_components.size(); ++i) {
- _components[i]->setThreadPool(tp);
+ for (auto* component : _components) {
+ component->setThreadPool(tp);
}
}
@@ -87,8 +93,8 @@ ComponentRegisterImpl::setUpgradeFlag(UpgradeFlags flag)
{
vespalib::LockGuard lock(_componentLock);
_upgradeFlag = flag;
- for (uint32_t i=0; i<_components.size(); ++i) {
- _components[i]->setUpgradeFlag(_upgradeFlag);
+ for (auto* component : _components) {
+ component->setUpgradeFlag(_upgradeFlag);
}
}
@@ -96,14 +102,14 @@ const StatusReporter*
ComponentRegisterImpl::getStatusReporter(vespalib::stringref id)
{
vespalib::LockGuard lock(_componentLock);
- for (uint32_t i=0; i<_components.size(); ++i) {
- if (_components[i]->getStatusReporter() != 0
- && _components[i]->getStatusReporter()->getId() == id)
+ for (auto* component : _components) {
+ if ((component->getStatusReporter() != nullptr)
+ && (component->getStatusReporter()->getId() == id))
{
- return _components[i]->getStatusReporter();
+ return component->getStatusReporter();
}
}
- return 0;
+ return nullptr;
}
std::vector<const StatusReporter*>
@@ -111,9 +117,9 @@ ComponentRegisterImpl::getStatusReporters()
{
std::vector<const StatusReporter*> reporters;
vespalib::LockGuard lock(_componentLock);
- for (uint32_t i=0; i<_components.size(); ++i) {
- if (_components[i]->getStatusReporter() != 0) {
- reporters.push_back(_components[i]->getStatusReporter());
+ for (auto* component : _components) {
+ if (component->getStatusReporter() != nullptr) {
+ reporters.emplace_back(component->getStatusReporter());
}
}
return reporters;
@@ -147,9 +153,9 @@ ComponentRegisterImpl::registerUpdateHook(vespalib::stringref name,
SecondTime period)
{
vespalib::LockGuard lock(_componentLock);
- metrics::UpdateHook::UP hookPtr(new MetricHookWrapper(name, hook));
+ auto hookPtr = std::make_unique<MetricHookWrapper>(name, hook);
_metricManager->addMetricUpdateHook(*hookPtr, period.getTime());
- _hooks.push_back(std::move(hookPtr));
+ _hooks.emplace_back(std::move(hookPtr));
}
metrics::MetricLockGuard
@@ -162,11 +168,7 @@ void
ComponentRegisterImpl::registerShutdownListener(ShutdownListener& listener)
{
vespalib::LockGuard lock(_componentLock);
- if (_shutdownListener != 0) {
- throw vespalib::IllegalStateException(
- "A shutdown listener is already registered. Add functionality "
- "for having multiple if we need multiple.", VESPA_STRLOC);
- }
+ assert(_shutdownListener == nullptr);
_shutdownListener = &listener;
}
diff --git a/storageframework/src/vespa/storageframework/generic/metric/metricregistrator.h b/storageframework/src/vespa/storageframework/generic/metric/metricregistrator.h
index f8ab6699d3c..6cdbf58d555 100644
--- a/storageframework/src/vespa/storageframework/generic/metric/metricregistrator.h
+++ b/storageframework/src/vespa/storageframework/generic/metric/metricregistrator.h
@@ -22,7 +22,7 @@ namespace storage::framework {
struct MetricUpdateHook;
struct MetricRegistrator {
- virtual ~MetricRegistrator() {}
+ virtual ~MetricRegistrator() = default;
virtual void registerMetric(metrics::Metric&) = 0;
virtual void registerUpdateHook(vespalib::stringref name, MetricUpdateHook& hook, SecondTime period) = 0;
diff --git a/streamingvisitors/src/vespa/searchvisitor/matching_elements_filler.cpp b/streamingvisitors/src/vespa/searchvisitor/matching_elements_filler.cpp
index fe717313ca4..d51bd57e942 100644
--- a/streamingvisitors/src/vespa/searchvisitor/matching_elements_filler.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/matching_elements_filler.cpp
@@ -83,6 +83,9 @@ Matcher::select_query_nodes(const MatchingElementsFields& fields, const QueryNod
if (fields.has_struct_field(query_term->getIndex())) {
_sub_field_terms.emplace_back(fields.get_enclosing_field(query_term->getIndex()), query_term);
}
+ if (fields.has_field(query_term->getIndex())) {
+ _sub_field_terms.emplace_back(query_term->getIndex(), query_term);
+ }
} else if (auto and_not = as<AndNotQueryNode>(query_node)) {
select_query_nodes(fields, *(*and_not)[0]);
} else if (auto intermediate = as<QueryConnector>(query_node)) {
diff --git a/streamingvisitors/src/vespa/searchvisitor/queryenvironment.cpp b/streamingvisitors/src/vespa/searchvisitor/queryenvironment.cpp
index 37809b207ad..06bdb9f69bb 100644
--- a/streamingvisitors/src/vespa/searchvisitor/queryenvironment.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/queryenvironment.cpp
@@ -1,12 +1,16 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "queryenvironment.h"
-#include <vespa/searchlib/common/location.h>
+#include <vespa/searchlib/common/geo_location.h>
+#include <vespa/searchlib/common/geo_location_spec.h>
+#include <vespa/searchlib/common/geo_location_parser.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchvisitor.queryenvironment");
using search::IAttributeManager;
+using search::common::GeoLocationParser;
+using search::common::GeoLocationSpec;
using search::fef::Properties;
using vespalib::string;
@@ -14,34 +18,24 @@ namespace streaming {
namespace {
-search::fef::Location
+std::vector<GeoLocationSpec>
parseLocation(const string & location_str)
{
- search::fef::Location fefLocation;
+ std::vector<GeoLocationSpec> fefLocations;
if (location_str.empty()) {
- return fefLocation;
+ return fefLocations;
}
- string::size_type pos = location_str.find(':');
- if (pos == string::npos) {
- LOG(warning, "Location string lacks attribute vector specification. loc='%s'. Location ignored.",
- location_str.c_str());
- return fefLocation;
- }
- string attr = location_str.substr(0, pos);
- const string location = location_str.substr(pos + 1);
-
- search::common::Location locationSpec;
- if (!locationSpec.parse(location)) {
+ GeoLocationParser locationParser;
+ if (!locationParser.parseOldFormatWithField(location_str)) {
LOG(warning, "Location parse error (location: '%s'): %s. Location ignored.",
- location.c_str(), locationSpec.getParseError());
- return fefLocation;
+ location_str.c_str(), locationParser.getParseError());
+ return fefLocations;
+ }
+ auto loc = locationParser.getGeoLocation();
+ if (loc.has_point) {
+ fefLocations.push_back(GeoLocationSpec{locationParser.getFieldName(), loc});
}
- fefLocation.setAttribute(attr);
- fefLocation.setXPosition(locationSpec.getX());
- fefLocation.setYPosition(locationSpec.getY());
- fefLocation.setXAspect(locationSpec.getXAspect());
- fefLocation.setValid(true);
- return fefLocation;
+ return fefLocations;
}
}
@@ -54,7 +48,7 @@ QueryEnvironment::QueryEnvironment(const string & location_str,
_properties(properties),
_attrCtx(attrMgr->createContext()),
_queryTerms(),
- _location(parseLocation(location_str))
+ _locations(parseLocation(location_str))
{
}
diff --git a/streamingvisitors/src/vespa/searchvisitor/queryenvironment.h b/streamingvisitors/src/vespa/searchvisitor/queryenvironment.h
index f580cec8870..df354d578e6 100644
--- a/streamingvisitors/src/vespa/searchvisitor/queryenvironment.h
+++ b/streamingvisitors/src/vespa/searchvisitor/queryenvironment.h
@@ -6,7 +6,6 @@
#include <vespa/searchlib/attribute/iattributemanager.h>
#include <vespa/searchlib/fef/iindexenvironment.h>
#include <vespa/searchlib/fef/iqueryenvironment.h>
-#include <vespa/searchlib/fef/location.h>
#include <vespa/searchlib/fef/properties.h>
#include "indexenvironment.h"
@@ -23,7 +22,7 @@ private:
const search::fef::Properties &_properties;
search::attribute::IAttributeContext::UP _attrCtx;
std::vector<const search::fef::ITermData *> _queryTerms;
- search::fef::Location _location;
+ std::vector<search::common::GeoLocationSpec> _locations;
public:
typedef std::unique_ptr<QueryEnvironment> UP;
@@ -49,7 +48,13 @@ public:
}
// inherit documentation
- virtual const search::fef::Location & getLocation() const override { return _location; }
+ GeoLocationSpecPtrs getAllLocations() const override {
+ GeoLocationSpecPtrs retval;
+ for (const auto & loc : _locations) {
+ retval.push_back(&loc);
+ }
+ return retval;
+ }
// inherit documentation
virtual const search::attribute::IAttributeContext & getAttributeContext() const override { return *_attrCtx; }
diff --git a/vbench/src/tests/app_dumpurl/app_dumpurl_test.cpp b/vbench/src/tests/app_dumpurl/app_dumpurl_test.cpp
index 0b4a83bf714..d19a5a323f2 100644
--- a/vbench/src/tests/app_dumpurl/app_dumpurl_test.cpp
+++ b/vbench/src/tests/app_dumpurl/app_dumpurl_test.cpp
@@ -1,11 +1,11 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/testapp.h>
#include <vbench/test/all.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/net/crypto_engine.h>
using namespace vbench;
-using vespalib::SlaveProc;
+using vespalib::ChildProcess;
using InputReader = vespalib::InputReader;
using OutputWriter = vespalib::OutputWriter;
@@ -32,7 +32,7 @@ void readUntil(Input &input, SimpleBuffer &buffer, const string &end) {
TEST("dumpurl usage") {
std::string out;
- EXPECT_FALSE(SlaveProc::run("../../apps/dumpurl/vbench_dumpurl_app", out));
+ EXPECT_FALSE(ChildProcess::run("../../apps/dumpurl/vbench_dumpurl_app", out));
fprintf(stderr, "%s\n", out.c_str());
}
@@ -48,7 +48,7 @@ TEST_MT_F("run dumpurl", 2, ServerSocket()) {
out.write("data");
} else {
std::string out;
- EXPECT_TRUE(SlaveProc::run(strfmt("../../apps/dumpurl/vbench_dumpurl_app localhost %d /foo",
+ EXPECT_TRUE(ChildProcess::run(strfmt("../../apps/dumpurl/vbench_dumpurl_app localhost %d /foo",
f1.port()).c_str(), out));
fprintf(stderr, "%s\n", out.c_str());
}
diff --git a/vbench/src/tests/app_vbench/app_vbench_test.cpp b/vbench/src/tests/app_vbench/app_vbench_test.cpp
index 8b5bb71425d..0e89d30e853 100644
--- a/vbench/src/tests/app_vbench/app_vbench_test.cpp
+++ b/vbench/src/tests/app_vbench/app_vbench_test.cpp
@@ -1,7 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/testapp.h>
#include <vbench/test/all.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/net/crypto_engine.h>
#include <vespa/vespalib/net/tls/tls_crypto_engine.h>
#include <vespa/vespalib/test/make_tls_options_for_testing.h>
@@ -11,7 +11,7 @@
#include <fcntl.h>
using namespace vbench;
-using vespalib::SlaveProc;
+using vespalib::ChildProcess;
using InputReader = vespalib::InputReader;
using OutputWriter = vespalib::OutputWriter;
@@ -32,7 +32,7 @@ void write_file(const vespalib::string &file_name, const vespalib::string &conte
TEST("vbench usage") {
std::string out;
- EXPECT_FALSE(SlaveProc::run("../../apps/vbench/vbench_app", out));
+ EXPECT_FALSE(ChildProcess::run("../../apps/vbench/vbench_app", out));
fprintf(stderr, "%s\n", out.c_str());
}
@@ -69,14 +69,14 @@ struct Servers {
TEST_MT_F("run vbench", 2, Servers()) {
if (thread_id == 0) {
std::string out;
- EXPECT_TRUE(SlaveProc::run(strfmt("sed 's/_LOCAL_PORT_/%d/' vbench.cfg.template > vbench.cfg", f1.portal->listen_port()).c_str()));
- EXPECT_TRUE(SlaveProc::run("../../apps/vbench/vbench_app run vbench.cfg 2> vbench.out", out));
+ EXPECT_TRUE(ChildProcess::run(strfmt("sed 's/_LOCAL_PORT_/%d/' vbench.cfg.template > vbench.cfg", f1.portal->listen_port()).c_str()));
+ EXPECT_TRUE(ChildProcess::run("../../apps/vbench/vbench_app run vbench.cfg 2> vbench.out", out));
fprintf(stderr, "null crypto: %s\n", out.c_str());
EXPECT_GREATER(f1.my_get.cnt, 10u);
} else {
std::string tls_out;
- EXPECT_TRUE(SlaveProc::run(strfmt("sed 's/_LOCAL_PORT_/%d/' vbench.tls.cfg.template > vbench.tls.cfg", f1.tls_portal->listen_port()).c_str()));
- EXPECT_TRUE(SlaveProc::run("../../apps/vbench/vbench_app run vbench.tls.cfg 2> vbench.tls.out", tls_out));
+ EXPECT_TRUE(ChildProcess::run(strfmt("sed 's/_LOCAL_PORT_/%d/' vbench.tls.cfg.template > vbench.tls.cfg", f1.tls_portal->listen_port()).c_str()));
+ EXPECT_TRUE(ChildProcess::run("../../apps/vbench/vbench_app run vbench.tls.cfg 2> vbench.tls.out", tls_out));
fprintf(stderr, "tls crypto: %s\n", tls_out.c_str());
EXPECT_GREATER(f1.my_tls_get.cnt, 10u);
}
diff --git a/vespa-osgi-testrunner/CMakeLists.txt b/vespa-osgi-testrunner/CMakeLists.txt
index 58aba186710..4b097a2f7bf 100644
--- a/vespa-osgi-testrunner/CMakeLists.txt
+++ b/vespa-osgi-testrunner/CMakeLists.txt
@@ -1,2 +1,3 @@
# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
install_fat_java_artifact(vespa-osgi-testrunner)
+install_config_definition(src/main/resources/configdefinitions/junit-test-runner.def com.yahoo.vespa.testrunner.junit-test-runner.def)
diff --git a/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java b/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java
deleted file mode 100644
index 1b3941df7bf..00000000000
--- a/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.collections;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.function.Predicate;
-
-/**
- * Class holding the result of a partition-by-predicate operation.
- **/
-public class PredicateSplit<E> {
- public final List<E> falseValues; /// list of values where the predicate returned false
- public final List<E> trueValues; /// list of values where the predicate returned true
-
- private PredicateSplit() {
- falseValues = new ArrayList<E>();
- trueValues = new ArrayList<E>();
- }
-
- /**
- * Perform a partition-by-predicate operation.
- * Each value in the input is tested by the predicate and
- * added to either the falseValues list or the trueValues list.
- * @param collection The input collection.
- * @param predicate A test for selecting the target list.
- * @return Two lists bundled in an object.
- **/
- public static <V> PredicateSplit<V> partition(Iterable<V> collection, Predicate<? super V> predicate)
- {
- PredicateSplit<V> r = new PredicateSplit<V>();
- for (V value : collection) {
- if (predicate.test(value)) {
- r.trueValues.add(value);
- } else {
- r.falseValues.add(value);
- }
- }
- return r;
- }
-}
diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/JobMetrics.java b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/JobMetrics.java
new file mode 100644
index 00000000000..a43e2156025
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/JobMetrics.java
@@ -0,0 +1,41 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.concurrent.maintenance;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+
+/**
+ * Tracks and forwards maintenance job metrics.
+ *
+ * @author mpolden
+ */
+public class JobMetrics {
+
+ private final BiConsumer<String, Long> metricConsumer;
+
+ private final Map<String, Long> incompleteRuns = new ConcurrentHashMap<>();
+
+ public JobMetrics(BiConsumer<String, Long> metricConsumer) {
+ this.metricConsumer = metricConsumer;
+ }
+
+ /** Record a run for given job */
+ public void recordRunOf(String job) {
+ incompleteRuns.compute(job, (ignored, run) -> run == null ? 1 : ++run);
+ }
+
+ /** Record successful run of given job */
+ public void recordSuccessOf(String job) {
+ incompleteRuns.put(job, 0L);
+ }
+
+ /** Forward metrics for given job to metric consumer */
+ public void forward(String job) {
+ Long incompleteRuns = this.incompleteRuns.get(job);
+ if (incompleteRuns != null) {
+ metricConsumer.accept(job, incompleteRuns);
+ }
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
index 9c40e5ec54f..eb9b91c812c 100644
--- a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
+++ b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
@@ -26,17 +26,19 @@ public abstract class Maintainer implements Runnable, AutoCloseable {
private final String name;
private final JobControl jobControl;
+ private final JobMetrics jobMetrics;
private final Duration interval;
private final ScheduledExecutorService service;
- public Maintainer(String name, Duration interval, Instant startedAt, JobControl jobControl, List<String> clusterHostnames) {
- this(name, interval, staggeredDelay(interval, startedAt, HostName.getLocalhost(), clusterHostnames), jobControl);
+ public Maintainer(String name, Duration interval, Instant startedAt, JobControl jobControl, JobMetrics jobMetrics, List<String> clusterHostnames) {
+ this(name, interval, staggeredDelay(interval, startedAt, HostName.getLocalhost(), clusterHostnames), jobControl, jobMetrics);
}
- public Maintainer(String name, Duration interval, Duration initialDelay, JobControl jobControl) {
+ public Maintainer(String name, Duration interval, Duration initialDelay, JobControl jobControl, JobMetrics jobMetrics) {
this.name = name;
this.interval = requireInterval(interval);
this.jobControl = Objects.requireNonNull(jobControl);
+ this.jobMetrics = Objects.requireNonNull(jobMetrics);
service = new ScheduledThreadPoolExecutor(1, r -> new Thread(r, name() + "-worker"));
service.scheduleAtFixedRate(this, initialDelay.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
jobControl.started(name(), this);
@@ -72,8 +74,8 @@ public abstract class Maintainer implements Runnable, AutoCloseable {
@Override
public final String toString() { return name(); }
- /** Called once each time this maintenance job should run */
- protected abstract void maintain();
+ /** Called once each time this maintenance job should run. Returns whether the maintenance run was succesful */
+ protected abstract boolean maintain();
/** Returns the interval at which this job is set to run */
protected Duration interval() { return interval; }
@@ -82,7 +84,13 @@ public abstract class Maintainer implements Runnable, AutoCloseable {
@SuppressWarnings("unused")
public final void lockAndMaintain() {
try (var lock = jobControl.lockJob(name())) {
- maintain();
+ try {
+ jobMetrics.recordRunOf(name());
+ if (maintain()) jobMetrics.recordSuccessOf(name());
+ } finally {
+ // Always forward metrics
+ jobMetrics.forward(name());
+ }
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java b/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java
index 9f3d3b837f8..58164801c7c 100644
--- a/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java
+++ b/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java
@@ -2,7 +2,7 @@
package com.yahoo.geo;
/**
- * utility for parsing geographical coordinates
+ * Utility for parsing geographical coordinates
*
* @author arnej27959
**/
diff --git a/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java b/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java
new file mode 100644
index 00000000000..1ae68afa4ac
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java
@@ -0,0 +1,79 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.geo;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Utility for parsing a geographical distance with unit.
+ **/
+@Beta
+public class DistanceParser {
+ // according to wikipedia:
+ // Earth's equatorial radius = 6378137 meter - not used
+ // meters per mile = 1609.344
+ // 180 degrees equals one half diameter equals PI*r
+ // Earth's polar radius = 6356752 meter
+
+ public final static double m2deg = 180.0 / (Math.PI * 6356752.0);
+ public final static double km2deg = 1000.000 * 180.0 / (Math.PI * 6356752.0);
+ public final static double mi2deg = 1609.344 * 180.0 / (Math.PI * 6356752.0);
+
+ private final double degrees;
+
+ public double getDegrees() { return degrees; }
+
+ /**
+ * Parse a distance in some kind of units, converting to geographical degrees.
+ * Note that the number and the unit should be separated by a single space only,
+ * or not separated at all.
+ * Supported units are "m", "km", "miles", and "deg",
+ * the last one meaning degrees with no conversion.
+ * For brevity "mi" = "miles" and "d" = "deg".
+ **/
+ static public double parse(String distance) {
+ var parser = new DistanceParser(distance, false);
+ return parser.degrees;
+ }
+
+ DistanceParser(String distance, boolean assumeMicroDegrees) {
+ if (distance.endsWith(" km")) {
+ double km = Double.valueOf(distance.substring(0, distance.length()-3));
+ degrees = km * km2deg;
+ } else if (distance.endsWith(" m")) {
+ double meters = Double.valueOf(distance.substring(0, distance.length()-2));
+ degrees = meters * m2deg;
+ } else if (distance.endsWith(" miles")) {
+ double miles = Double.valueOf(distance.substring(0, distance.length()-6));
+ degrees = miles * mi2deg;
+ } else if (distance.endsWith(" mi")) {
+ double miles = Double.valueOf(distance.substring(0, distance.length()-3));
+ degrees = miles * mi2deg;
+ } else if (distance.endsWith(" deg")) {
+ degrees = Double.valueOf(distance.substring(0, distance.length()-4));
+ } else if (distance.endsWith(" d")) {
+ degrees = Double.valueOf(distance.substring(0, distance.length()-2));
+ } else if (distance.endsWith("km")) {
+ double km = Double.valueOf(distance.substring(0, distance.length()-2));
+ degrees = km * km2deg;
+ } else if (distance.endsWith("m")) {
+ double meters = Double.valueOf(distance.substring(0, distance.length()-1));
+ degrees = meters * m2deg;
+ } else if (distance.endsWith("miles")) {
+ double miles = Double.valueOf(distance.substring(0, distance.length()-5));
+ degrees = miles * mi2deg;
+ } else if (distance.endsWith("mi")) {
+ double miles = Double.valueOf(distance.substring(0, distance.length()-2));
+ degrees = miles * mi2deg;
+ } else if (distance.endsWith("deg")) {
+ degrees = Double.valueOf(distance.substring(0, distance.length()-3));
+ } else if (distance.endsWith("d")) {
+ degrees = Double.valueOf(distance.substring(0, distance.length()-1));
+ } else if (assumeMicroDegrees) {
+ degrees = Integer.parseInt(distance) * 0.000001;
+ } else {
+ throw new IllegalArgumentException("missing unit for distance: "+distance);
+ }
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java b/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java
new file mode 100644
index 00000000000..cf23a24e702
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java
@@ -0,0 +1,282 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.geo;
+
+/**
+ * Utility for parsing one geographical coordinate
+ *
+ * @author arnej27959
+ **/
+class OneDegreeParser {
+ /**
+ * the parsed latitude (degrees north if positive)
+ **/
+ public double latitude = 0;
+ public boolean foundLatitude = false;
+
+ /**
+ * the parsed longitude (degrees east if positive)
+ **/
+ public double longitude = 0;
+ public boolean foundLongitude = false;
+
+ public static boolean isDigit(char ch) {
+ return (ch >= '0' && ch <= '9');
+ }
+ public static boolean isCompassDirection(char ch) {
+ return (ch == 'N' || ch == 'S' || ch == 'E' || ch == 'W');
+ }
+
+ private String parseString = null;
+ private int len = 0;
+ private int pos = 0;
+
+ public String toString() {
+ if (foundLatitude) {
+ return parseString + " -> latitude(" + latitude + ")";
+ } else {
+ return parseString + " -> longitude(" + longitude + ")";
+ }
+ }
+
+ private char getNextChar() throws IllegalArgumentException {
+ if (pos == len) {
+ pos++;
+ return 0;
+ } else if (pos > len) {
+ throw new IllegalArgumentException("position after end of string when parsing <"+parseString+">");
+ } else {
+ return parseString.charAt(pos++);
+ }
+ }
+
+ /**
+ * Parse the given string.
+ *
+ * The string must contain either a latitude or a longitude.
+ * A latitude should contain "N" or "S" and a number signifying
+ * degrees north or south, or a signed number.
+ * A longitude should contain "E" or "W" and a number
+ * signifying degrees east or west, or a signed number.
+ * <br>
+ * Fractional degrees are recommended as the main input format,
+ * but degrees plus fractional minutes may be used for testing.
+ * You can use the degree sign (U+00B0 as seen in unicode at
+ * http://www.unicode.org/charts/PDF/U0080.pdf) to separate
+ * degrees from minutes, put the direction (NSEW) between as a
+ * separator, or use a small letter 'o' as a replacement for the
+ * degrees sign.
+ * <br>
+ * Some valid input formats: <br>
+ * "37.416383" and "-122.024683" → Sunnyvale <br>
+ * "N37.416383" and "W122.024683" → Sunnyvale <br>
+ * "37N24.983" and "122W01.481" → same <br>
+ * "N37\u00B024.983" and "W122\u00B001.481" → same <br>
+ * "63.418417" and "10.433033" → Trondheim <br>
+ * "N63.418417" and "E10.433033" → same <br>
+ * "N63o25.105" and "E10o25.982" → same <br>
+ * "E10o25.982" and "N63o25.105" → same <br>
+ * "N63.418417" and "E10.433033" → same <br>
+ * "63N25.105" and "10E25.982" → same <br>
+ * @param assumeNorthSouth Latitude assumed, otherwise longitude
+ * @param toParse Latitude or longitude string to parse
+ *
+ **/
+ public OneDegreeParser(boolean assumeNorthSouth, String toParse) throws IllegalArgumentException {
+ this.parseString = toParse;
+ this.len = parseString.length();
+ consumeString(assumeNorthSouth);
+ }
+
+ private void consumeString(boolean assumeNorthSouth) throws IllegalArgumentException {
+ char ch = getNextChar();
+
+ double degrees = 0.0;
+ double minutes = 0.0;
+ double seconds = 0.0;
+ boolean degSet = false;
+ boolean minSet = false;
+ boolean secSet = false;
+ boolean dirSet = false;
+ boolean foundDot = false;
+ boolean foundDigits = false;
+
+ boolean findingLatitude = false;
+ boolean findingLongitude = false;
+
+ double sign = +1.0;
+
+ int lastpos = -1;
+
+ // sign must be first character in string if present:
+ if (ch == '+') {
+ // unary plus is a nop
+ ch = getNextChar();
+ } else if (ch == '-') {
+ sign = -1.0;
+ ch = getNextChar();
+ }
+ do {
+ // did we find a valid char?
+ boolean valid = false;
+ if (pos == lastpos) {
+ throw new IllegalArgumentException("internal logic error at <"+parseString+"> pos:"+pos);
+ } else {
+ lastpos = pos;
+ }
+
+ // first, see if we can find some number
+ double accum = 0.0;
+
+ if (isDigit(ch) || ch == '.') {
+ valid = true;
+ if (foundDigits) {
+ throw new IllegalArgumentException("found digits after not consuming previous digits when parsing <"+parseString+">");
+ }
+ double divider = 1.0;
+ foundDot = false;
+ while (isDigit(ch)) {
+ foundDigits = true;
+ accum *= 10;
+ accum += (ch - '0');
+ ch = getNextChar();
+ }
+ if (ch == '.') {
+ foundDot = true;
+ ch = getNextChar();
+ while (isDigit(ch)) {
+ foundDigits = true;
+ accum *= 10;
+ accum += (ch - '0');
+ divider *= 10;
+ ch = getNextChar();
+ }
+ }
+ if (!foundDigits) {
+ throw new IllegalArgumentException("just a . is not a valid number when parsing <"+parseString+">");
+ }
+ accum /= divider;
+ }
+
+ // next, did we find a separator after the number?
+ // degree sign is a separator after degrees, before minutes
+ if (ch == '\u00B0' || ch == 'o') {
+ valid = true;
+ if (degSet) {
+ throw new IllegalArgumentException("degrees sign only valid just after degrees when parsing <"+parseString+">");
+ }
+ if (!foundDigits) {
+ throw new IllegalArgumentException("must have number before degrees sign when parsing <"+parseString+">");
+ }
+ if (foundDot) {
+ throw new IllegalArgumentException("cannot have fractional degrees before degrees sign when parsing <"+parseString+">");
+ }
+ ch = getNextChar();
+ }
+ // apostrophe is a separator after minutes, before seconds
+ if (ch == '\'') {
+ if (minSet || !degSet || !foundDigits) {
+ throw new IllegalArgumentException("minutes sign only valid just after minutes when parsing <"+parseString+">");
+ }
+ if (foundDot) {
+ throw new IllegalArgumentException("cannot have fractional minutes before minutes sign when parsing <"+parseString+">");
+ }
+ ch = getNextChar();
+ }
+
+ // if we found some number, assign it into the next unset variable
+ if (foundDigits) {
+ valid = true;
+ if (degSet) {
+ if (minSet) {
+ if (secSet) {
+ throw new IllegalArgumentException("extra number after full field when parsing <"+parseString+">");
+ } else {
+ seconds = accum;
+ secSet = true;
+ }
+ } else {
+ minutes = accum;
+ minSet = true;
+ if (foundDot) {
+ secSet = true;
+ }
+ }
+ } else {
+ degrees = accum;
+ degSet = true;
+ if (foundDot) {
+ minSet = true;
+ secSet = true;
+ }
+ }
+ foundDot = false;
+ foundDigits = false;
+ }
+
+ // there may to be a direction (NSEW) somewhere, too
+ if (isCompassDirection(ch)) {
+ valid = true;
+ if (dirSet) {
+ throw new IllegalArgumentException("already set direction once, cannot add direction: "+ch+" when parsing <"+parseString+">");
+ }
+ dirSet = true;
+ if (ch == 'S' || ch == 'W') {
+ sign = -1;
+ } else {
+ sign = 1;
+ }
+ if (ch == 'E' || ch == 'W') {
+ findingLongitude = true;
+ } else {
+ findingLatitude = true;
+ }
+ ch = getNextChar();
+ }
+
+ // lastly, did we find the end-of-string?
+ if (ch == 0) {
+ valid = true;
+ if (!dirSet) {
+ if (assumeNorthSouth) {
+ findingLatitude = true;
+ } else {
+ findingLongitude = true;
+ }
+ }
+ if (!degSet) {
+ throw new IllegalArgumentException("end of field without any number seen when parsing <"+parseString+">");
+ }
+ degrees += minutes / 60.0;
+ degrees += seconds / 3600.0;
+ degrees *= sign;
+
+ if (findingLatitude) {
+ if (degrees < -90.0 || degrees > 90.0) {
+ throw new IllegalArgumentException("out of range [-90,+90]: "+degrees+" when parsing <"+parseString+">");
+ }
+ latitude = degrees;
+ foundLatitude = true;
+ } else if (findingLongitude) {
+ if (degrees < -180.0 || degrees > 180.0) {
+ throw new IllegalArgumentException("out of range [-180,+180]: "+degrees+" when parsing <"+parseString+">");
+ }
+ longitude = degrees;
+ foundLongitude = true;
+ }
+ break;
+ }
+ if (!valid) {
+ throw new IllegalArgumentException("invalid character: "+ch+" when parsing <"+parseString+">");
+ }
+ } while (ch != 0);
+ // everything parsed OK
+ if (foundLatitude && foundLongitude) {
+ throw new IllegalArgumentException("found both latitude and longitude from: "+parseString);
+ }
+ if (foundLatitude || foundLongitude) {
+ return;
+ }
+ throw new IllegalArgumentException("found neither latitude nor longitude from: "+parseString);
+ }
+}
diff --git a/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java b/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java
new file mode 100644
index 00000000000..84b87614182
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java
@@ -0,0 +1,57 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.geo;
+
+/**
+ * Utility for holding one geographical coordinate
+ *
+ * @author arnej27959
+ **/
+public class ParsedDegree {
+ /**
+ * the parsed latitude or longitude
+ * Degrees north or east if positive
+ * Degrees south or west if negative
+ **/
+ public final double degrees;
+
+ // one of these two flag will be true:
+ public final boolean isLatitude;
+ public final boolean isLongitude;
+
+ public ParsedDegree(double value, boolean isLat, boolean isLon) {
+ this.degrees = value;
+ this.isLatitude = isLat;
+ this.isLongitude = isLon;
+ if (isLat && isLon) {
+ throw new IllegalArgumentException("value cannot be both latitude and longitude at once");
+ }
+ if (isLat || isLon) {
+ return;
+ }
+ throw new IllegalArgumentException("value must be either latitude or longitude");
+ }
+
+ static public ParsedDegree fromString(String toParse, boolean assumeLatitude, boolean assumeLongitude) {
+ if (assumeLatitude && assumeLongitude) {
+ throw new IllegalArgumentException("value cannot be both latitude and longitude at once");
+ }
+ var parser = new OneDegreeParser(assumeLatitude, toParse);
+ if (parser.foundLatitude) {
+ return new ParsedDegree(parser.latitude, true, false);
+ }
+ if (parser.foundLongitude) {
+ return new ParsedDegree(parser.longitude, false, true);
+ }
+ throw new IllegalArgumentException("could not parse: "+toParse);
+ }
+
+ public String toString() {
+ if (isLatitude) {
+ return "Latitude: "+degrees+" degrees";
+ } else {
+ return "Longitude: "+degrees+" degrees";
+ }
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/text/Utf8.java b/vespajlib/src/main/java/com/yahoo/text/Utf8.java
index 6f40b590a64..cb8ca244fe2 100644
--- a/vespajlib/src/main/java/com/yahoo/text/Utf8.java
+++ b/vespajlib/src/main/java/com/yahoo/text/Utf8.java
@@ -13,10 +13,10 @@ import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
/**
- * utility class with functions for handling UTF-8
+ * Utility class with functions for handling UTF-8
*
* @author arnej27959
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
* @author baldersheim
*
*/
diff --git a/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java b/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java
deleted file mode 100644
index 05a719d6a4d..00000000000
--- a/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.collections;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.ArrayList;
-
-import static org.junit.Assert.assertEquals;
-
-public class PredicateSplitTestCase {
- @Test
- public void requireThatSplitWorks() {
- List<Integer> l = new ArrayList<Integer>();
- l.add(1);
- l.add(6);
- l.add(2);
- l.add(4);
- l.add(5);
- PredicateSplit<Integer> result = PredicateSplit.partition(l, x -> (x % 2 == 0));
- assertEquals((long) result.falseValues.size(), 2L);
- assertEquals((long) result.falseValues.get(0), 1L);
- assertEquals((long) result.falseValues.get(1), 5L);
-
- assertEquals((long) result.trueValues.size(), 3L);
- assertEquals((long) result.trueValues.get(0), 6L);
- assertEquals((long) result.trueValues.get(1), 2L);
- assertEquals((long) result.trueValues.get(2), 4L);
- }
-}
diff --git a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlStateMock.java b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlStateMock.java
new file mode 100644
index 00000000000..28c701a67db
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlStateMock.java
@@ -0,0 +1,35 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.concurrent.maintenance;
+
+import com.yahoo.transaction.Mutex;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author mpolden
+ */
+class JobControlStateMock implements JobControlState {
+
+ private final Set<String> inactiveJobs = new HashSet<>();
+
+ @Override
+ public Set<String> readInactiveJobs() {
+ return Collections.unmodifiableSet(inactiveJobs);
+ }
+
+ @Override
+ public Mutex lockMaintenanceJob(String job) {
+ return () -> {};
+ }
+
+ public void setActive(String job, boolean active) {
+ if (active) {
+ inactiveJobs.remove(job);
+ } else {
+ inactiveJobs.add(job);
+ }
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlTest.java b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlTest.java
index 0640ab2835a..a0ca9b529c5 100644
--- a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlTest.java
+++ b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/JobControlTest.java
@@ -1,15 +1,8 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.concurrent.maintenance;
-import com.yahoo.transaction.Mutex;
import org.junit.Test;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -21,18 +14,13 @@ public class JobControlTest {
@Test
public void testJobControl() {
- MockJobControlState state = new MockJobControlState();
+ JobControlStateMock state = new JobControlStateMock();
JobControl jobControl = new JobControl(state);
- MockMaintainer maintainer1 = new MockMaintainer();
- MockMaintainer maintainer2 = new MockMaintainer();
- assertTrue(jobControl.jobs().isEmpty());
-
String job1 = "Job1";
String job2 = "Job2";
-
- jobControl.started(job1, maintainer1);
- jobControl.started(job2, maintainer2);
+ TestMaintainer maintainer1 = new TestMaintainer(job1, jobControl);
+ TestMaintainer maintainer2 = new TestMaintainer(job2, jobControl);
assertEquals(2, jobControl.jobs().size());
assertTrue(jobControl.jobs().contains(job1));
assertTrue(jobControl.jobs().contains(job2));
@@ -59,79 +47,36 @@ public class JobControlTest {
// Run jobs on-demand
jobControl.run(job1);
jobControl.run(job1);
- assertEquals(2, maintainer1.maintenanceInvocations);
+ assertEquals(2, maintainer1.totalRuns());
jobControl.run(job2);
- assertEquals(1, maintainer2.maintenanceInvocations);
+ assertEquals(1, maintainer2.totalRuns());
// Running jobs on-demand ignores inactive flag
state.setActive(job1, false);
jobControl.run(job1);
- assertEquals(3, maintainer1.maintenanceInvocations);
+ assertEquals(3, maintainer1.totalRuns());
}
@Test
public void testJobControlMayDeactivateJobs() {
- MockJobControlState state = new MockJobControlState();
+ JobControlStateMock state = new JobControlStateMock();
JobControl jobControl = new JobControl(state);
- MockMaintainer mockMaintainer = new MockMaintainer(jobControl);
+ TestMaintainer mockMaintainer = new TestMaintainer(null, jobControl);
- assertTrue(jobControl.jobs().contains("MockMaintainer"));
+ assertTrue(jobControl.jobs().contains("TestMaintainer"));
- assertEquals(0, mockMaintainer.maintenanceInvocations);
+ assertEquals(0, mockMaintainer.totalRuns());
mockMaintainer.run();
- assertEquals(1, mockMaintainer.maintenanceInvocations);
+ assertEquals(1, mockMaintainer.totalRuns());
- state.setActive("MockMaintainer", false);
+ state.setActive("TestMaintainer", false);
mockMaintainer.run();
- assertEquals(1, mockMaintainer.maintenanceInvocations);
+ assertEquals(1, mockMaintainer.totalRuns());
- state.setActive("MockMaintainer", true);
+ state.setActive("TestMaintainer", true);
mockMaintainer.run();
- assertEquals(2, mockMaintainer.maintenanceInvocations);
- }
-
- private static class MockJobControlState implements JobControlState {
-
- private final Set<String> inactiveJobs = new HashSet<>();
-
- @Override
- public Set<String> readInactiveJobs() {
- return new HashSet<>(inactiveJobs);
- }
-
- @Override
- public Mutex lockMaintenanceJob(String job) {
- return () -> {};
- }
-
- public void setActive(String job, boolean active) {
- if (active) {
- inactiveJobs.remove(job);
- } else {
- inactiveJobs.add(job);
- }
- }
-
- }
-
- private static class MockMaintainer extends Maintainer {
-
- int maintenanceInvocations = 0;
-
- private MockMaintainer(JobControl jobControl) {
- super(null, Duration.ofHours(1), Instant.now(), jobControl, List.of());
- }
-
- private MockMaintainer() {
- this(new JobControl(new MockJobControlState()));
- }
-
- @Override
- protected void maintain() {
- maintenanceInvocations++;
- }
-
+ assertEquals(2, mockMaintainer.totalRuns());
}
}
diff --git a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/MaintainerTest.java b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/MaintainerTest.java
index 820d1fc3d1d..2bfaad894a5 100644
--- a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/MaintainerTest.java
+++ b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/MaintainerTest.java
@@ -6,6 +6,7 @@ import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
@@ -36,4 +37,33 @@ public class MaintainerTest {
assertEquals(300, Maintainer.staggeredDelay(interval, now, "cfg0", cluster).toMillis());
}
+ @Test
+ public void success_metric() {
+ AtomicLong consecutiveFailures = new AtomicLong();
+ JobMetrics jobMetrics = new JobMetrics((job, count) -> consecutiveFailures.set(count));
+ TestMaintainer maintainer = new TestMaintainer(jobMetrics);
+
+ // Maintainer fails twice in a row
+ maintainer.successOnNextRun(false).run();
+ assertEquals(1, consecutiveFailures.get());
+ maintainer.successOnNextRun(false).run();
+ assertEquals(2, consecutiveFailures.get());
+
+ // Maintainer runs successfully
+ maintainer.successOnNextRun(true).run();
+ assertEquals(0, consecutiveFailures.get());
+
+ // Maintainer runs successfully again
+ maintainer.run();
+ assertEquals(0, consecutiveFailures.get());
+
+ // Maintainer throws
+ maintainer.throwOnNextRun(true).run();
+ assertEquals(1, consecutiveFailures.get());
+
+ // Maintainer recovers
+ maintainer.throwOnNextRun(false).run();
+ assertEquals(0, consecutiveFailures.get());
+ }
+
}
diff --git a/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/TestMaintainer.java b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/TestMaintainer.java
new file mode 100644
index 00000000000..5eae643fe40
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/concurrent/maintenance/TestMaintainer.java
@@ -0,0 +1,48 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.concurrent.maintenance;
+
+import java.time.Duration;
+
+/**
+ * @author mpolden
+ */
+class TestMaintainer extends Maintainer {
+
+ private int totalRuns = 0;
+ private boolean success = true;
+ private boolean throwing = false;
+
+ public TestMaintainer(String name, JobControl jobControl, JobMetrics jobMetrics) {
+ super(name, Duration.ofDays(1), Duration.ofDays(1), jobControl, jobMetrics);
+ }
+
+ public TestMaintainer(JobMetrics jobMetrics) {
+ this(null, new JobControl(new JobControlStateMock()), jobMetrics);
+ }
+
+ public TestMaintainer(String name, JobControl jobControl) {
+ this(name, jobControl, new JobMetrics((job, instant) -> {}));
+ }
+
+ public int totalRuns() {
+ return totalRuns;
+ }
+
+ public TestMaintainer successOnNextRun(boolean success) {
+ this.success = success;
+ return this;
+ }
+
+ public TestMaintainer throwOnNextRun(boolean throwing) {
+ this.throwing = throwing;
+ return this;
+ }
+
+ @Override
+ protected boolean maintain() {
+ if (throwing) throw new RuntimeException("Maintenance run failed");
+ totalRuns++;
+ return success;
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/geo/OneDegreeParserTestCase.java b/vespajlib/src/test/java/com/yahoo/geo/OneDegreeParserTestCase.java
new file mode 100644
index 00000000000..b0da6a0a131
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/geo/OneDegreeParserTestCase.java
@@ -0,0 +1,204 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.geo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the OneDegreeParser class.
+ *
+ * @author arnej27959
+ */
+public class OneDegreeParserTestCase {
+
+ private static final double delta = 0.000000000001;
+
+ private OneDegreeParser parser;
+
+ private void checkLat(boolean assumeLatitude, String toParse, double expected) {
+ parser = new OneDegreeParser(assumeLatitude, toParse);
+ assertEquals(expected, parser.latitude, delta);
+ assertTrue(parser.foundLatitude);
+ assertFalse(parser.foundLongitude);
+ }
+ private void checkLon(boolean assumeLatitude, String toParse, double expected) {
+ parser = new OneDegreeParser(assumeLatitude, toParse);
+ assertEquals(expected, parser.longitude, delta);
+ assertFalse(parser.foundLatitude);
+ assertTrue(parser.foundLongitude);
+ }
+ private void checkLat(String toParse, double expected) {
+ checkLat(true, toParse, expected);
+ checkLat(false, toParse, expected);
+ }
+ private void checkLon(String toParse, double expected) {
+ checkLon(true, toParse, expected);
+ checkLon(false, toParse, expected);
+ }
+
+ private void checkZeroLat(boolean assumeLatitude, String toParse) {
+ checkLat(assumeLatitude, toParse, 0d);
+ }
+
+ private void checkZeroLon(boolean assumeLatitude, String toParse) {
+ checkLon(assumeLatitude, toParse, 0d);
+ }
+
+ /**
+ * Tests different inputs that should all produce 0 or -0.
+ */
+ @Test
+ public void testZero() {
+ checkZeroLat(true, "0");
+ checkZeroLat(true, "0.0");
+ checkZeroLat(true, "0o0.0");
+ checkZeroLat(true, "0o0'0");
+ checkZeroLat(true, "0\u00B00'0");
+
+ checkZeroLon(false, "0");
+ checkZeroLon(false, "0.0");
+ checkZeroLon(false, "0o0.0");
+ checkZeroLon(false, "0o0'0");
+ checkZeroLon(false, "0\u00B00'0");
+
+ checkZeroLat(false, "N0");
+ checkZeroLat(false, "N0.0");
+ checkZeroLat(false, "N0\u00B00'0");
+ checkZeroLat(false, "S0");
+ checkZeroLat(false, "S0.0");
+ checkZeroLat(false, "S0o0'0");
+ checkZeroLat(false, "S0\u00B00'0");
+
+ checkZeroLon(true, "E0");
+ checkZeroLon(true, "E0.0");
+ checkZeroLon(true, "E0\u00B00'0");
+ checkZeroLon(true, "W0");
+ checkZeroLon(true, "W0.0");
+ checkZeroLon(true, "W0o0'0");
+ checkZeroLon(true, "W0\u00B00'0");
+ }
+
+ /**
+ * Tests inputs that are close to 0.
+ */
+ @Test
+ public void testNearZero() {
+ checkLat("N0.0001", 0.0001);
+ checkLat("S0.0001", -0.0001);
+ checkLon("E0.0001", 0.0001);
+ checkLon("W0.0001", -0.0001);
+
+ checkLat("N0.000001", 0.000001);
+ checkLat("S0.000001", -0.000001);
+ checkLon("E0.000001", 0.000001);
+ checkLon("W0.000001", -0.000001);
+
+ checkLat("N0\u00B00'1", 1/3600d);
+ checkLat("S0\u00B00'1", -1/3600d);
+ checkLon("E0\u00B00'1", 1/3600d);
+ checkLon("W0\u00B00'1", -1/3600d);
+ }
+
+ /**
+ * Tests inputs that are close to latitude 90/-90 degrees and longitude 180/-180 degrees.
+ */
+ @Test
+ public void testNearBoundary() {
+ checkLat("N89.9999", 89.9999);
+ checkLat("S89.9999", -89.9999);
+ checkLon("E179.9999", 179.9999);
+ checkLon("W179.9999", -179.9999);
+
+ checkLat("N89.999999", 89.999999);
+ checkLat("S89.999999", -89.999999);
+ checkLon("E179.999999", 179.999999);
+ checkLon("W179.999999", -179.999999);
+
+ checkLat("N89\u00B059'59", 89+59/60d+59/3600d);
+ checkLat("S89\u00B059'59", -(89+59/60d+59/3600d));
+ checkLon("E179\u00B059'59", 179+59/60d+59/3600d);
+ checkLon("W179\u00B059'59", -(179+59/60d+59/3600d));
+ }
+
+ /**
+ * Tests inputs that are on latitude 90/-90 degrees and longitude 180/-180 degrees.
+ */
+ @Test
+ public void testOnBoundary() {
+ checkLat("N90", 90d);
+ checkLat("N90\u00B00'0", 90d);
+ checkLat("S90", -90d);
+ checkLat("S90\u00B00'0", -90d);
+
+ checkLon("E180", 180d);
+ checkLon("E180\u00B00'0", 180d);
+ checkLon("W180", -180d);
+ checkLon("W180\u00B00'0", -180d);
+ }
+
+ private String parseException(boolean assumeLatitude, String toParse) {
+ String message = "";
+ try {
+ parser = new OneDegreeParser(assumeLatitude, toParse);
+ assertTrue(false);
+ } catch (IllegalArgumentException e) {
+ message = e.getMessage();
+ }
+ return message;
+ }
+
+ /**
+ * Tests inputs that are above latitude 90/-90 degrees and longitude 180/-180 degrees.
+ */
+ @Test
+ public void testAboveBoundary() {
+ String message = parseException(false, "N90.0001");
+ assertEquals("out of range [-90,+90]: 90.0001 when parsing <N90.0001>", message);
+ message = parseException(false, "S90.0001");
+ assertEquals("out of range [-90,+90]: -90.0001 when parsing <S90.0001>", message);
+ message = parseException(true, "E180.0001");
+ assertEquals("out of range [-180,+180]: 180.0001 when parsing <E180.0001>", message);
+ message = parseException(true, "W180.0001");
+ assertEquals("out of range [-180,+180]: -180.0001 when parsing <W180.0001>", message);
+ message = parseException(false, "N90.000001");
+ assertEquals("out of range [-90,+90]: 90.000001 when parsing <N90.000001>", message);
+ message = parseException(false, "S90.000001");
+ assertEquals("out of range [-90,+90]: -90.000001 when parsing <S90.000001>", message);
+ message = parseException(true, "E180.000001");
+ assertEquals("out of range [-180,+180]: 180.000001 when parsing <E180.000001>", message);
+ message = parseException(true, "W180.000001");
+ assertEquals("out of range [-180,+180]: -180.000001 when parsing <W180.000001>", message);
+ }
+
+ /**
+ * Tests various inputs that contain syntax errors.
+ */
+ @Test
+ public void testInputErrors() {
+ String message = parseException(false, "N90S90");
+ assertEquals("already set direction once, cannot add direction: S when parsing <N90S90>", message);
+ message = parseException(false, "E120W120");
+ assertEquals("already set direction once, cannot add direction: W when parsing <E120W120>", message);
+ message = parseException(false, "E");
+ assertEquals("end of field without any number seen when parsing <E>", message);
+ message = parseException(false, "");
+ assertEquals("end of field without any number seen when parsing <>", message);
+ message = parseException(false, "NW25");
+ assertEquals("already set direction once, cannot add direction: W when parsing <NW25>", message);
+ message = parseException(false, "N16.25\u00B0");
+ assertEquals("cannot have fractional degrees before degrees sign when parsing <N16.25\u00B0>", message);
+ message = parseException(false, "N16\u00B022.40'");
+ assertEquals("cannot have fractional minutes before minutes sign when parsing <N16\u00B022.40'>", message);
+ message = parseException(false, "");
+ assertEquals("end of field without any number seen when parsing <>", message);
+ message = parseException(false, "Yahoo!");
+ assertEquals("invalid character: Y when parsing <Yahoo!>", message);
+ message = parseException(false, "N63O025.105");
+ assertEquals("invalid character: O when parsing <N63O025.105>", message);
+ }
+
+}
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index 1ca9816a921..c894a798b91 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -97,7 +97,7 @@ vespa_define_module(
src/tests/sharedptr
src/tests/signalhandler
src/tests/simple_thread_bundle
- src/tests/slaveproc
+ src/tests/child_process
src/tests/slime
src/tests/slime/external_data_value
src/tests/slime/summary-feature-benchmark
diff --git a/vespalib/src/tests/assert/assert_test.cpp b/vespalib/src/tests/assert/assert_test.cpp
index 860f56304ff..8b770f0f3eb 100644
--- a/vespalib/src/tests/assert/assert_test.cpp
+++ b/vespalib/src/tests/assert/assert_test.cpp
@@ -1,6 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/util/assert.h>
#include <vespa/vespalib/io/fileutil.h>
@@ -16,19 +16,19 @@ TEST("that it borks the first time.") {
vespalib::rmdir("var", true);
ASSERT_TRUE(vespalib::mkdir(assertDir, true));
{
- SlaveProc proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
+ ChildProcess proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
proc.wait();
ASSERT_EQUAL(proc.getExitCode() & 0x7f, 6);
}
{
- SlaveProc proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
+ ChildProcess proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
proc.readLine(assertName);
proc.wait();
ASSERT_EQUAL(proc.getExitCode() & 0x7f, 0);
}
ASSERT_EQUAL(0, unlink(assertName.c_str()));
{
- SlaveProc proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
+ ChildProcess proc("ulimit -c 0 && exec env VESPA_HOME=./ ./vespalib_asserter_app myassert 10000");
proc.wait();
ASSERT_EQUAL(proc.getExitCode() & 0x7f, 6);
}
diff --git a/vespalib/src/tests/child_process/.gitignore b/vespalib/src/tests/child_process/.gitignore
new file mode 100644
index 00000000000..7e094c772a6
--- /dev/null
+++ b/vespalib/src/tests/child_process/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+child_process_test
+vespalib_child_process_test_app
diff --git a/vespalib/src/tests/child_process/CMakeLists.txt b/vespalib/src/tests/child_process/CMakeLists.txt
new file mode 100644
index 00000000000..1c9af730510
--- /dev/null
+++ b/vespalib/src/tests/child_process/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_child_process_test_app TEST
+ SOURCES
+ child_process_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_child_process_test_app COMMAND vespalib_child_process_test_app)
diff --git a/vespalib/src/tests/slaveproc/slaveproc_test.cpp b/vespalib/src/tests/child_process/child_process_test.cpp
index 547da991211..ada29cd370d 100644
--- a/vespalib/src/tests/slaveproc/slaveproc_test.cpp
+++ b/vespalib/src/tests/child_process/child_process_test.cpp
@@ -1,57 +1,57 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
-using vespalib::SlaveProc;
+using vespalib::ChildProcess;
TEST("simple run, ignore output") {
- EXPECT_TRUE(SlaveProc::run("echo foo"));
+ EXPECT_TRUE(ChildProcess::run("echo foo"));
}
TEST("simple run, ignore output, failure") {
- EXPECT_TRUE(!SlaveProc::run("false"));
+ EXPECT_TRUE(!ChildProcess::run("false"));
}
TEST("simple run, ignore output, timeout") {
- EXPECT_TRUE(!SlaveProc::run("exec sleep 60", 10));
+ EXPECT_TRUE(!ChildProcess::run("exec sleep 60", 10));
}
TEST("simple run") {
std::string out;
- EXPECT_TRUE(SlaveProc::run("/bin/echo -n foo", out));
+ EXPECT_TRUE(ChildProcess::run("/bin/echo -n foo", out));
EXPECT_EQUAL(out, "foo");
}
TEST("simple run, strip single-line trailing newline") {
std::string out;
- EXPECT_TRUE(SlaveProc::run("echo foo", out));
+ EXPECT_TRUE(ChildProcess::run("echo foo", out));
EXPECT_EQUAL(out, "foo");
}
TEST("simple run, don't strip multi-line output") {
std::string out;
- EXPECT_TRUE(SlaveProc::run("perl -e 'print \"foo\\n\\n\"'", out));
+ EXPECT_TRUE(ChildProcess::run("perl -e 'print \"foo\\n\\n\"'", out));
EXPECT_EQUAL(out, "foo\n\n");
}
TEST("simple run with input") {
std::string in = "bar";
std::string out;
- EXPECT_TRUE(SlaveProc::run(in, "cat", out));
+ EXPECT_TRUE(ChildProcess::run(in, "cat", out));
EXPECT_EQUAL(out, "bar");
}
TEST("simple run with input, strip single-line trailing newline") {
std::string in = "bar\n";
std::string out;
- EXPECT_TRUE(SlaveProc::run(in, "cat", out));
+ EXPECT_TRUE(ChildProcess::run(in, "cat", out));
EXPECT_EQUAL(out, "bar");
}
TEST("simple run with input, don't strip multi-line output") {
std::string in = "bar\n\n";
std::string out;
- EXPECT_TRUE(SlaveProc::run(in, "cat", out));
+ EXPECT_TRUE(ChildProcess::run(in, "cat", out));
EXPECT_EQUAL("bar\n\n", out);
}
@@ -64,11 +64,11 @@ TEST_MT("simple run, partial output due to timeout", 2) {
(thread_id == 0) ? "out" : "", timeout);
if (thread_id == 0) {
out.clear();
- EXPECT_TRUE(!SlaveProc::run(my_cmd, out, timeout));
+ EXPECT_TRUE(!ChildProcess::run(my_cmd, out, timeout));
} else {
out.clear();
std::string in = "ignored\n";
- EXPECT_TRUE(!SlaveProc::run(in, my_cmd, out, timeout));
+ EXPECT_TRUE(!ChildProcess::run(in, my_cmd, out, timeout));
}
if (out == "foo") {
break;
@@ -78,7 +78,7 @@ TEST_MT("simple run, partial output due to timeout", 2) {
}
TEST("proc failure") {
- SlaveProc proc("false");
+ ChildProcess proc("false");
// read with length 0 will wait for output
EXPECT_TRUE(proc.read(NULL, 0) == 0);
EXPECT_TRUE(proc.wait(60000));
@@ -90,7 +90,7 @@ TEST("basic read/write") {
int x;
int read;
char buf[64];
- SlaveProc proc("cat");
+ ChildProcess proc("cat");
EXPECT_TRUE(proc.running());
EXPECT_TRUE(!proc.failed());
@@ -117,7 +117,7 @@ TEST("basic read/write") {
TEST("continuos run, readLine") {
std::string str;
- SlaveProc proc("cat");
+ ChildProcess proc("cat");
EXPECT_TRUE(proc.running());
EXPECT_TRUE(!proc.failed());
@@ -142,7 +142,7 @@ TEST("continuos run, readLine") {
TEST("readLine, eof flushes last line") {
std::string str;
- SlaveProc proc("cat");
+ ChildProcess proc("cat");
EXPECT_TRUE(proc.running());
EXPECT_TRUE(!proc.failed());
@@ -166,7 +166,7 @@ TEST("readLine, eof flushes last line") {
TEST("long continuos run, readLine") {
std::string in;
std::string out;
- SlaveProc proc("cat");
+ ChildProcess proc("cat");
EXPECT_TRUE(proc.running());
EXPECT_TRUE(!proc.failed());
diff --git a/vespalib/src/tests/drop-file-from-cache/drop_file_from_cache_test.cpp b/vespalib/src/tests/drop-file-from-cache/drop_file_from_cache_test.cpp
index 63defa58c41..787e46cfd91 100644
--- a/vespalib/src/tests/drop-file-from-cache/drop_file_from_cache_test.cpp
+++ b/vespalib/src/tests/drop-file-from-cache/drop_file_from_cache_test.cpp
@@ -1,23 +1,23 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
-using vespalib::SlaveProc;
+using vespalib::ChildProcess;
TEST("no arguments") {
- SlaveProc drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache");
+ ChildProcess drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache");
drop.wait();
EXPECT_EQUAL(1, drop.getExitCode());
}
TEST("file does not exist") {
- SlaveProc drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache not_exist");
+ ChildProcess drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache not_exist");
drop.wait();
EXPECT_EQUAL(2, drop.getExitCode());
}
TEST("All is well") {
- SlaveProc drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache vespalib_drop_file_from_cache_test_app");
+ ChildProcess drop("../../apps/vespa-drop-file-from-cache/vespa-drop-file-from-cache vespalib_drop_file_from_cache_test_app");
drop.wait();
EXPECT_EQUAL(0, drop.getExitCode());
}
diff --git a/vespalib/src/tests/exception_classes/silenceuncaught_test.cpp b/vespalib/src/tests/exception_classes/silenceuncaught_test.cpp
index 8d81d9e0821..89030a19cd6 100644
--- a/vespalib/src/tests/exception_classes/silenceuncaught_test.cpp
+++ b/vespalib/src/tests/exception_classes/silenceuncaught_test.cpp
@@ -1,7 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/util/exception.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
using namespace vespalib;
@@ -14,32 +14,32 @@ using namespace vespalib;
#endif
TEST("that uncaught exception causes negative exitcode.") {
- SlaveProc proc("ulimit -c 0 && exec ./vespalib_caught_uncaught_app uncaught");
+ ChildProcess proc("ulimit -c 0 && exec ./vespalib_caught_uncaught_app uncaught");
proc.wait();
EXPECT_LESS(proc.getExitCode(), 0);
}
TEST("that uncaught silenced exception causes exitcode 66") {
- SlaveProc proc("exec ./vespalib_caught_uncaught_app silenced_and_uncaught");
+ ChildProcess proc("exec ./vespalib_caught_uncaught_app silenced_and_uncaught");
proc.wait();
EXPECT_EQUAL(proc.getExitCode(), 66);
}
TEST("that caught silenced exception followed by an uncaught causes negative exitcode.") {
- SlaveProc proc("ulimit -c 0 && exec ./vespalib_caught_uncaught_app uncaught_after_silenced_and_caught");
+ ChildProcess proc("ulimit -c 0 && exec ./vespalib_caught_uncaught_app uncaught_after_silenced_and_caught");
proc.wait();
EXPECT_LESS(proc.getExitCode(), 0);
}
TEST("that caught silenced exception causes exitcode 0") {
- SlaveProc proc("exec ./vespalib_caught_uncaught_app silenced_and_caught");
+ ChildProcess proc("exec ./vespalib_caught_uncaught_app silenced_and_caught");
proc.wait();
EXPECT_EQUAL(proc.getExitCode(), 0);
}
#ifndef __SANITIZE_ADDRESS__
TEST("that mmap within limits are fine cause exitcode 0") {
- SlaveProc proc("exec ./vespalib_mmap_app 150000000 10485760 1");
+ ChildProcess proc("exec ./vespalib_mmap_app 150000000 10485760 1");
proc.wait();
EXPECT_EQUAL(proc.getExitCode(), 0);
}
@@ -48,13 +48,13 @@ TEST("that mmap within limits are fine cause exitcode 0") {
// setrlimit with RLIMIT_AS is broken on Darwin
#else
TEST("that mmap beyond limits cause negative exitcode.") {
- SlaveProc proc("ulimit -c 0 && exec ./vespalib_mmap_app 100000000 10485760 10");
+ ChildProcess proc("ulimit -c 0 && exec ./vespalib_mmap_app 100000000 10485760 10");
proc.wait();
EXPECT_LESS(proc.getExitCode(), 0);
}
TEST("that mmap beyond limits with set VESPA_SILENCE_CORE_ON_OOM cause exitcode 66.") {
- SlaveProc proc("VESPA_SILENCE_CORE_ON_OOM=1 exec ./vespalib_mmap_app 100000000 10485760 10");
+ ChildProcess proc("VESPA_SILENCE_CORE_ON_OOM=1 exec ./vespalib_mmap_app 100000000 10485760 10");
proc.wait();
EXPECT_EQUAL(proc.getExitCode(), 66);
}
diff --git a/vespalib/src/tests/host_name/host_name_test.cpp b/vespalib/src/tests/host_name/host_name_test.cpp
index 4e0c59d836d..442da2ac2e8 100644
--- a/vespalib/src/tests/host_name/host_name_test.cpp
+++ b/vespalib/src/tests/host_name/host_name_test.cpp
@@ -1,7 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/util/host_name.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
using namespace vespalib;
diff --git a/vespalib/src/tests/make_fixture_macros/make_fixture_macros_test.cpp b/vespalib/src/tests/make_fixture_macros/make_fixture_macros_test.cpp
index a7509014b00..db7a51222b7 100644
--- a/vespalib/src/tests/make_fixture_macros/make_fixture_macros_test.cpp
+++ b/vespalib/src/tests/make_fixture_macros/make_fixture_macros_test.cpp
@@ -1,12 +1,12 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
using namespace vespalib;
bool runPrint(const char *cmd) {
std::string out;
- bool res = SlaveProc::run(cmd, out);
+ bool res = ChildProcess::run(cmd, out);
fprintf(stderr, "%s", out.c_str());
return res;
}
diff --git a/vespalib/src/tests/slaveproc/.gitignore b/vespalib/src/tests/slaveproc/.gitignore
deleted file mode 100644
index 8851e99df13..00000000000
--- a/vespalib/src/tests/slaveproc/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.depend
-Makefile
-slaveproc_test
-vespalib_slaveproc_test_app
diff --git a/vespalib/src/tests/slaveproc/CMakeLists.txt b/vespalib/src/tests/slaveproc/CMakeLists.txt
deleted file mode 100644
index 58211d671bc..00000000000
--- a/vespalib/src/tests/slaveproc/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(vespalib_slaveproc_test_app TEST
- SOURCES
- slaveproc_test.cpp
- DEPENDS
- vespalib
-)
-vespa_add_test(NAME vespalib_slaveproc_test_app COMMAND vespalib_slaveproc_test_app)
diff --git a/vespalib/src/tests/tutorial/make_tutorial.cpp b/vespalib/src/tests/tutorial/make_tutorial.cpp
index 4890b0db62a..de0eb585e3c 100644
--- a/vespalib/src/tests/tutorial/make_tutorial.cpp
+++ b/vespalib/src/tests/tutorial/make_tutorial.cpp
@@ -1,6 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <sys/mman.h>
#include <sys/stat.h>
@@ -20,7 +20,7 @@ std::string readFile(const std::string &filename) {
std::string runCommand(const std::string &cmd) {
std::string out;
- ASSERT_TRUE(SlaveProc::run(cmd.c_str(), out));
+ ASSERT_TRUE(ChildProcess::run(cmd.c_str(), out));
return out;
}
diff --git a/vespalib/src/vespa/vespalib/util/CMakeLists.txt b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
index 4029c4881c4..0973653861f 100644
--- a/vespalib/src/vespa/vespalib/util/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
@@ -46,7 +46,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT
sig_catch.cpp
signalhandler.cpp
simple_thread_bundle.cpp
- slaveproc.cpp
+ child_process.cpp
stash.cpp
string_hash.cpp
stringfmt.cpp
diff --git a/vespalib/src/vespa/vespalib/util/slaveproc.cpp b/vespalib/src/vespa/vespalib/util/child_process.cpp
index 6f40aa9a4d7..ce0c2eb1779 100644
--- a/vespalib/src/vespa/vespalib/util/slaveproc.cpp
+++ b/vespalib/src/vespa/vespalib/util/child_process.cpp
@@ -1,17 +1,17 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "guard.h"
-#include "slaveproc.h"
+#include "child_process.h"
#include <cstring>
namespace vespalib {
-namespace slaveproc {
+namespace child_process {
using namespace std::chrono;
/**
- * @brief SlaveProc internal timeout management.
+ * @brief ChildProcess internal timeout management.
**/
class Timer
{
@@ -54,14 +54,14 @@ public:
}
};
-} // namespace slaveproc
+} // namespace child_process
-using slaveproc::Timer;
+using child_process::Timer;
//-----------------------------------------------------------------------------
void
-SlaveProc::Reader::OnReceiveData(const void *data, size_t length)
+ChildProcess::Reader::OnReceiveData(const void *data, size_t length)
{
const char *buf = (const char *) data;
MonitorGuard lock(_cond);
@@ -80,7 +80,7 @@ SlaveProc::Reader::OnReceiveData(const void *data, size_t length)
bool
-SlaveProc::Reader::hasData()
+ChildProcess::Reader::hasData()
{
// NB: caller has lock on _cond
return (!_data.empty() || !_queue.empty());
@@ -88,7 +88,7 @@ SlaveProc::Reader::hasData()
bool
-SlaveProc::Reader::waitForData(Timer &timer, MonitorGuard &lock)
+ChildProcess::Reader::waitForData(Timer &timer, MonitorGuard &lock)
{
// NB: caller has lock on _cond
CounterGuard count(_waitCnt);
@@ -100,7 +100,7 @@ SlaveProc::Reader::waitForData(Timer &timer, MonitorGuard &lock)
void
-SlaveProc::Reader::updateEOF()
+ChildProcess::Reader::updateEOF()
{
// NB: caller has lock on _cond
if (_data.empty() && _queue.empty() && _gotEOF) {
@@ -109,7 +109,7 @@ SlaveProc::Reader::updateEOF()
}
-SlaveProc::Reader::Reader()
+ChildProcess::Reader::Reader()
: _cond(),
_queue(),
_data(),
@@ -120,13 +120,13 @@ SlaveProc::Reader::Reader()
}
-SlaveProc::Reader::~Reader()
+ChildProcess::Reader::~Reader()
{
}
uint32_t
-SlaveProc::Reader::read(char *buf, uint32_t len, int msTimeout)
+ChildProcess::Reader::read(char *buf, uint32_t len, int msTimeout)
{
if (eof()) {
return 0;
@@ -156,7 +156,7 @@ SlaveProc::Reader::read(char *buf, uint32_t len, int msTimeout)
bool
-SlaveProc::Reader::readLine(std::string &line, int msTimeout)
+ChildProcess::Reader::readLine(std::string &line, int msTimeout)
{
line.clear();
if (eof()) {
@@ -193,7 +193,7 @@ SlaveProc::Reader::readLine(std::string &line, int msTimeout)
//-----------------------------------------------------------------------------
void
-SlaveProc::checkProc()
+ChildProcess::checkProc()
{
if (_running) {
bool stillRunning;
@@ -205,7 +205,7 @@ SlaveProc::checkProc()
}
-SlaveProc::SlaveProc(const char *cmd)
+ChildProcess::ChildProcess(const char *cmd)
: _reader(),
_proc(cmd, true, &_reader),
_running(false),
@@ -217,11 +217,11 @@ SlaveProc::SlaveProc(const char *cmd)
}
-SlaveProc::~SlaveProc() = default;
+ChildProcess::~ChildProcess() = default;
bool
-SlaveProc::write(const char *buf, uint32_t len)
+ChildProcess::write(const char *buf, uint32_t len)
{
if (len == 0) {
return true;
@@ -231,28 +231,28 @@ SlaveProc::write(const char *buf, uint32_t len)
bool
-SlaveProc::close()
+ChildProcess::close()
{
return _proc.WriteStdin(nullptr, 0);
}
uint32_t
-SlaveProc::read(char *buf, uint32_t len, int msTimeout)
+ChildProcess::read(char *buf, uint32_t len, int msTimeout)
{
return _reader.read(buf, len, msTimeout);
}
bool
-SlaveProc::readLine(std::string &line, int msTimeout)
+ChildProcess::readLine(std::string &line, int msTimeout)
{
return _reader.readLine(line, msTimeout);
}
bool
-SlaveProc::wait(int msTimeout)
+ChildProcess::wait(int msTimeout)
{
bool done = true;
checkProc();
@@ -273,7 +273,7 @@ SlaveProc::wait(int msTimeout)
bool
-SlaveProc::running()
+ChildProcess::running()
{
checkProc();
return _running;
@@ -281,24 +281,24 @@ SlaveProc::running()
bool
-SlaveProc::failed()
+ChildProcess::failed()
{
checkProc();
return _failed;
}
int
-SlaveProc::getExitCode()
+ChildProcess::getExitCode()
{
return _exitCode;
}
bool
-SlaveProc::run(const std::string &input, const char *cmd,
+ChildProcess::run(const std::string &input, const char *cmd,
std::string &output, int msTimeout)
{
- SlaveProc proc(cmd);
+ ChildProcess proc(cmd);
Timer timer(msTimeout);
char buf[4096];
proc.write(input.data(), input.length());
@@ -317,7 +317,7 @@ SlaveProc::run(const std::string &input, const char *cmd,
bool
-SlaveProc::run(const char *cmd, std::string &output, int msTimeout)
+ChildProcess::run(const char *cmd, std::string &output, int msTimeout)
{
std::string input; // empty input
return run(input, cmd, output, msTimeout);
@@ -325,7 +325,7 @@ SlaveProc::run(const char *cmd, std::string &output, int msTimeout)
bool
-SlaveProc::run(const char *cmd, int msTimeout)
+ChildProcess::run(const char *cmd, int msTimeout)
{
std::string input; // empty input
std::string output; // ignore output
diff --git a/vespalib/src/vespa/vespalib/util/slaveproc.h b/vespalib/src/vespa/vespalib/util/child_process.h
index c08a13f0b1d..0ae7206ac48 100644
--- a/vespalib/src/vespa/vespalib/util/slaveproc.h
+++ b/vespalib/src/vespa/vespalib/util/child_process.h
@@ -8,17 +8,17 @@
#include <queue>
#include "sync.h"
-namespace vespalib::slaveproc { class Timer; }
+namespace vespalib::child_process { class Timer; }
namespace vespalib {
/**
- * @brief Slave Process utility class for running external programs
+ * @brief Child Process utility class for running external programs
*
* Designed for use in unit tests and other places
* where you need to run, control and communicate with
* some external program.
**/
-class SlaveProc
+class ChildProcess
{
private:
class Reader : public FastOS_ProcessRedirectListener
@@ -33,7 +33,7 @@ private:
void OnReceiveData(const void *data, size_t length) override;
bool hasData();
- bool waitForData(slaveproc::Timer &timer, MonitorGuard &lock);
+ bool waitForData(child_process::Timer &timer, MonitorGuard &lock);
void updateEOF();
public:
@@ -54,19 +54,19 @@ private:
void checkProc();
public:
- SlaveProc(const SlaveProc &) = delete;
- SlaveProc &operator=(const SlaveProc &) = delete;
+ ChildProcess(const ChildProcess &) = delete;
+ ChildProcess &operator=(const ChildProcess &) = delete;
/**
- * @brief Run a slave process
+ * @brief Run a child process
*
* Starts a process running the given command
* @param cmd A shell command line to run
**/
- explicit SlaveProc(const char *cmd);
+ explicit ChildProcess(const char *cmd);
/** @brief destructor doing cleanup if needed */
- ~SlaveProc();
+ ~ChildProcess();
/**
* @return process id
diff --git a/vespamalloc/src/tests/doubledelete/expectsignal.cpp b/vespamalloc/src/tests/doubledelete/expectsignal.cpp
index 9ac93da014d..a0f6561889a 100644
--- a/vespamalloc/src/tests/doubledelete/expectsignal.cpp
+++ b/vespamalloc/src/tests/doubledelete/expectsignal.cpp
@@ -1,6 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/testapp.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <sys/wait.h>
using namespace vespalib;
@@ -25,7 +25,7 @@ int Test::Main()
fprintf(stderr, "argc=%d : Running '%s' expecting signal %d\n", _argc, _argv[2], retval);
- SlaveProc cmd(_argv[2]);
+ ChildProcess cmd(_argv[2]);
for(std::string line; cmd.readLine(line, 60000);) {
fprintf(stdout, "%s\n", line.c_str());
}
diff --git a/vespamalloc/src/tests/overwrite/expectsignal.cpp b/vespamalloc/src/tests/overwrite/expectsignal.cpp
index e78f1b7b181..2b37499f85d 100644
--- a/vespamalloc/src/tests/overwrite/expectsignal.cpp
+++ b/vespamalloc/src/tests/overwrite/expectsignal.cpp
@@ -1,6 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/testapp.h>
-#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/child_process.h>
#include <sys/wait.h>
using namespace vespalib;
@@ -24,7 +24,7 @@ int Test::Main()
fprintf(stderr, "argc=%d : Running '%s' expecting signal %d\n", _argc, _argv[2], retval);
- SlaveProc cmd(_argv[2]);
+ ChildProcess cmd(_argv[2]);
for(std::string line; cmd.readLine(line, 60000);) {
fprintf(stdout, "%s\n", line.c_str());
}
diff --git a/vsm/src/vespa/vsm/vsm/docsumconfig.cpp b/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
index 2512bea26df..fb25df83ded 100644
--- a/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
+++ b/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
@@ -26,6 +26,9 @@ void populate_fields(MatchingElementsFields& fields, VsmfieldsConfig& fields_con
if (spec.name.substr(0, prefix.size()) == prefix) {
fields.add_mapping(field_name, spec.name);
}
+ if (spec.name == field_name) {
+ fields.add_field(field_name);
+ }
}
}
diff --git a/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp b/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp
index 5d8c7735c0e..8307954faae 100644
--- a/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp
+++ b/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp
@@ -38,11 +38,6 @@ void GetDocsumsStateCallback::FillRankFeatures(GetDocsumsState * state, IDocsumE
}
}
-void GetDocsumsStateCallback::ParseLocation(GetDocsumsState *state)
-{
- (void) state;
-}
-
void GetDocsumsStateCallback::FillDocumentLocations(GetDocsumsState *state, IDocsumEnvironment * env)
{
(void) state;
diff --git a/vsm/src/vespa/vsm/vsm/vsm-adapter.h b/vsm/src/vespa/vsm/vsm/vsm-adapter.h
index cffae318586..31e472713de 100644
--- a/vsm/src/vespa/vsm/vsm/vsm-adapter.h
+++ b/vsm/src/vespa/vsm/vsm/vsm-adapter.h
@@ -40,7 +40,6 @@ public:
GetDocsumsStateCallback();
void FillSummaryFeatures(GetDocsumsState * state, IDocsumEnvironment * env) override;
void FillRankFeatures(GetDocsumsState * state, IDocsumEnvironment * env) override;
- void ParseLocation(GetDocsumsState * state) override;
virtual void FillDocumentLocations(GetDocsumsState * state, IDocsumEnvironment * env);
virtual std::unique_ptr<search::MatchingElements> fill_matching_elements(const search::MatchingElementsFields& fields) override;
void setSummaryFeatures(const search::FeatureSet::SP & sf) { _summaryFeatures = sf; }