aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2017-12-14 18:47:08 +0100
committerJon Bratseth <bratseth@yahoo-inc.com>2017-12-14 18:47:08 +0100
commitff0b4082e7d328b5cf5dfe0e79d27471c3afdb50 (patch)
tree865e46af8d4c406418050383a4e25c761b2dce3d
parent597f59619a0e08f75a0e60ac09da7e5aea538404 (diff)
parentb27bada7c6b22d83557c6592d8c00fea2a6c35fe (diff)
Merge with master
-rw-r--r--annotations/pom.xml1
-rw-r--r--application-deploy-plugin/pom.xml1
-rw-r--r--application-model/pom.xml1
-rw-r--r--application-preprocessor/pom.xml1
-rw-r--r--application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java6
-rw-r--r--application/pom.xml1
-rw-r--r--athenz-identity-provider-service/pom.xml1
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java69
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java110
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java5
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java13
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java3
-rw-r--r--athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def3
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java3
-rwxr-xr-xbootstrap-cpp.sh2
-rwxr-xr-xbootstrap.sh10
-rw-r--r--bundle-plugin-test/pom.xml1
-rw-r--r--bundle-plugin/pom.xml1
-rwxr-xr-xchain/pom.xml1
-rw-r--r--clustercontroller-apps/pom.xml1
-rw-r--r--clustercontroller-apputil/pom.xml1
-rw-r--r--clustercontroller-core/pom.xml1
-rw-r--r--clustercontroller-standalone/pom.xml1
-rw-r--r--clustercontroller-utils/pom.xml1
-rwxr-xr-xcomponent/pom.xml1
-rw-r--r--config-application-package/pom.xml1
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java5
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java11
-rw-r--r--config-bundle/pom.xml1
-rw-r--r--config-class-plugin/pom.xml1
-rw-r--r--config-lib/pom.xml1
-rw-r--r--config-model-api/pom.xml1
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java5
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java13
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java10
-rw-r--r--config-model-fat/pom.xml1
-rw-r--r--config-model/pom.xml1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java5
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java12
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java19
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java4
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java7
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java63
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java16
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java46
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java24
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java7
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java15
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/Container.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java44
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java20
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java42
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java22
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java59
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java57
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java12
-rw-r--r--config-model/src/main/resources/schema/admin.rnc2
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc1
-rw-r--r--config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg5
-rw-r--r--config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml10
-rw-r--r--config-model/src/test/derived/importedfields/attributes.cfg20
-rw-r--r--config-model/src/test/derived/importedfields/child.sd1
-rw-r--r--config-model/src/test/derived/importedfields/imported-fields.cfg5
-rw-r--r--config-model/src/test/derived/importedfields/index-info.cfg6
-rw-r--r--config-model/src/test/derived/importedfields/parent_a.sd8
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java39
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java15
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java25
-rwxr-xr-xconfig-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java65
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java38
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java55
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java34
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java79
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java27
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java6
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java54
-rw-r--r--config-model/src/test/schema-test-files/services.xml1
-rw-r--r--config-provisioning/pom.xml1
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java14
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java1
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java56
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java70
-rw-r--r--config-proxy/pom.xml1
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java52
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java1
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java5
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java7
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java15
-rwxr-xr-xconfig/pom.xml1
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java28
-rw-r--r--config/src/tests/configagent/configagent.cpp53
-rw-r--r--config/src/tests/configholder/configholder.cpp2
-rw-r--r--config/src/tests/frt/frt.cpp190
-rw-r--r--config/src/tests/subscriber/subscriber.cpp20
-rw-r--r--config/src/tests/subscription/subscription.cpp4
-rw-r--r--config/src/vespa/config/common/configholder.cpp8
-rw-r--r--config/src/vespa/config/common/configrequest.h6
-rw-r--r--config/src/vespa/config/common/configupdate.h1
-rw-r--r--config/src/vespa/config/frt/CMakeLists.txt2
-rw-r--r--config/src/vespa/config/frt/frtconfigagent.cpp27
-rw-r--r--config/src/vespa/config/frt/frtconfigagent.h2
-rw-r--r--config/src/vespa/config/frt/frtconfigrequest.cpp52
-rw-r--r--config/src/vespa/config/frt/frtconfigrequest.h24
-rw-r--r--config/src/vespa/config/frt/frtconfigrequestfactory.cpp18
-rw-r--r--config/src/vespa/config/frt/frtconfigrequestv2.cpp32
-rw-r--r--config/src/vespa/config/frt/frtconfigrequestv2.h29
-rw-r--r--config/src/vespa/config/frt/frtconfigresponse.cpp50
-rw-r--r--config/src/vespa/config/frt/frtconfigresponse.h28
-rw-r--r--config/src/vespa/config/frt/frtconfigresponsev2.cpp44
-rw-r--r--config/src/vespa/config/frt/frtconfigresponsev2.h28
-rw-r--r--config/src/vespa/config/frt/slimeconfigrequest.cpp13
-rw-r--r--config/src/vespa/config/frt/slimeconfigrequest.h1
-rw-r--r--config/src/vespa/config/subscription/configsubscription.cpp23
-rw-r--r--config/src/vespa/config/subscription/configsubscription.h3
-rw-r--r--config/src/vespa/config/subscription/configsubscriptionset.cpp10
-rw-r--r--configd/src/apps/sentinel/config-handler.cpp56
-rw-r--r--configd/src/apps/sentinel/metrics.cpp35
-rw-r--r--configd/src/apps/sentinel/metrics.h18
-rw-r--r--configd/src/apps/sentinel/service.cpp3
-rw-r--r--configd/src/apps/sentinel/state-api.h7
-rw-r--r--configdefinitions/pom.xml1
-rw-r--r--configdefinitions/src/vespa/configserver.def7
-rw-r--r--configgen/pom.xml1
-rw-r--r--configserver/pom.xml1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java14
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java39
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java10
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java51
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java145
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java150
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java29
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java17
-rw-r--r--configserver/src/main/resources/configserver-app/services.xml39
-rwxr-xr-xconfigserver/src/main/sh/start-filedistribution2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java8
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java6
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java69
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java62
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java19
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java7
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java3
-rw-r--r--container-accesslogging/pom.xml1
-rw-r--r--container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java15
-rw-r--r--container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java10
-rw-r--r--container-core/pom.xml1
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java6
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java23
-rw-r--r--container-core/src/main/resources/configdefinitions/health-monitor.def3
-rw-r--r--container-dependencies-enforcer/pom.xml48
-rw-r--r--container-dependency-versions/pom.xml441
-rw-r--r--container-dev-builder/OWNERS1
-rw-r--r--container-dev-builder/README.md4
-rw-r--r--container-dev-builder/dependency_blacklist2
-rwxr-xr-xcontainer-dev-builder/make.sh75
-rw-r--r--container-dev-builder/tools/pom.xml51
-rw-r--r--container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java58
-rw-r--r--container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java50
-rw-r--r--container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java79
-rw-r--r--container-dev/pom.xml1
-rw-r--r--container-di/pom.xml1
-rw-r--r--container-disc/pom.xml1
-rw-r--r--container-jersey2/pom.xml1
-rw-r--r--container-messagebus/pom.xml1
-rw-r--r--container-search-and-docproc/pom.xml1
-rw-r--r--container-search/pom.xml1
-rw-r--r--container-test-jars/pom.xml1
-rw-r--r--container-test/pom.xml1
-rw-r--r--container/pom.xml1
-rw-r--r--controller-api/pom.xml1
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java50
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java1
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java)2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java)2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java59
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPublicKey.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java)2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzService.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java)15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java14
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUser.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java)38
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUtils.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java)16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/InvalidTokenException.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java)2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java36
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java36
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java)8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java24
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsKeystore.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java)2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsException.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java)11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java39
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java50
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java55
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java47
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java36
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java41
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java14
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java4
-rw-r--r--controller-server/pom.xml1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java232
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java90
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java148
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java45
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java19
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java24
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java87
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java65
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java87
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java35
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java61
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java67
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java41
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java39
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java136
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java32
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java49
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java117
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java148
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java54
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java48
-rw-r--r--controller-server/src/main/resources/configdefinitions/athenz.def9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java37
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java137
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java61
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java67
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java41
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java77
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java37
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java106
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java282
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java175
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java205
-rw-r--r--defaults/pom.xml7
-rwxr-xr-xdist/build-rpm.sh124
-rw-r--r--dist/vespa.spec19
-rw-r--r--docker-api/pom.xml1
-rw-r--r--docproc/pom.xml1
-rw-r--r--docprocs/pom.xml1
-rw-r--r--document/pom.xml1
-rw-r--r--document/src/main/java/com/yahoo/document/idstring/IdIdString.java6
-rw-r--r--document/src/test/java/com/yahoo/document/DocumentIdTestCase.java45
-rw-r--r--document/src/tests/bucketselectortest.cpp2
-rw-r--r--document/src/tests/documentselectparsertest.cpp510
-rw-r--r--document/src/vespa/document/bucket/bucketselector.cpp26
-rw-r--r--document/src/vespa/document/select/.gitignore3
-rw-r--r--document/src/vespa/document/select/CMakeLists.txt19
-rw-r--r--document/src/vespa/document/select/branch.cpp6
-rw-r--r--document/src/vespa/document/select/cloningvisitor.cpp12
-rw-r--r--document/src/vespa/document/select/cloningvisitor.h1
-rw-r--r--document/src/vespa/document/select/constant.cpp24
-rw-r--r--document/src/vespa/document/select/constant.h9
-rw-r--r--document/src/vespa/document/select/gid_filter.cpp1
-rw-r--r--document/src/vespa/document/select/grammar/lexer.ll182
-rw-r--r--document/src/vespa/document/select/grammar/parser.yy374
-rw-r--r--document/src/vespa/document/select/node.h3
-rw-r--r--document/src/vespa/document/select/orderingselector.cpp1
-rw-r--r--document/src/vespa/document/select/parse_utils.cpp37
-rw-r--r--document/src/vespa/document/select/parse_utils.h17
-rw-r--r--document/src/vespa/document/select/parser.cpp1498
-rw-r--r--document/src/vespa/document/select/parser.h45
-rw-r--r--document/src/vespa/document/select/parsing_failed_exception.cpp9
-rw-r--r--document/src/vespa/document/select/parsing_failed_exception.h10
-rw-r--r--document/src/vespa/document/select/scanner.h21
-rw-r--r--document/src/vespa/document/select/traversingvisitor.cpp6
-rw-r--r--document/src/vespa/document/select/traversingvisitor.h1
-rw-r--r--document/src/vespa/document/select/valuenodes.cpp130
-rw-r--r--document/src/vespa/document/select/valuenodes.h90
-rw-r--r--document/src/vespa/document/select/visitor.h3
-rw-r--r--documentapi/pom.xml1
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp2
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h2
-rw-r--r--documentgen-test/pom.xml1
-rw-r--r--eval/CMakeLists.txt1
-rw-r--r--eval/src/apps/tensor_conformance/generate.cpp11
-rw-r--r--eval/src/apps/tensor_conformance/test_spec.json4
-rw-r--r--eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp201
-rw-r--r--eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp2
-rw-r--r--eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp87
-rw-r--r--eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt8
-rw-r--r--eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp102
-rw-r--r--eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp2
-rw-r--r--eval/src/vespa/eval/eval/interpreted_function.cpp29
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.h14
-rw-r--r--eval/src/vespa/eval/eval/value_type.h4
-rw-r--r--eval/src/vespa/eval/tensor/default_tensor_engine.cpp76
-rw-r--r--eval/src/vespa/eval/tensor/dense/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp5
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp68
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp106
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_tensor_view.h13
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp91
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h53
-rw-r--r--eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp2
-rw-r--r--eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp2
-rw-r--r--eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp132
-rw-r--r--eval/src/vespa/eval/tensor/sparse/sparse_tensor.h13
-rw-r--r--eval/src/vespa/eval/tensor/tensor.cpp2
-rw-r--r--eval/src/vespa/eval/tensor/tensor.h24
-rw-r--r--eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp69
-rw-r--r--eval/src/vespa/eval/tensor/wrapped_simple_tensor.h13
-rw-r--r--fileacquirer/pom.xml1
-rw-r--r--filedistribution/OWNERS3
-rw-r--r--filedistribution/pom.xml12
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java118
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java2
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java101
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java282
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java67
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java44
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java4
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java157
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java52
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java98
-rw-r--r--filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java66
-rw-r--r--filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java71
-rw-r--r--filedistribution/src/vespa/filedistribution/model/zkfacade.cpp2
-rw-r--r--filedistribution_test/pom.xml1
-rw-r--r--filedistributionmanager/pom.xml1
-rw-r--r--fsa/pom.xml1
-rw-r--r--indexinglanguage/pom.xml1
-rw-r--r--jaxrs_client_utils/pom.xml1
-rw-r--r--jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java29
-rw-r--r--jaxrs_utils/pom.xml1
-rw-r--r--jdisc_core/pom.xml1
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java2
-rw-r--r--jdisc_core_test/pom.xml1
-rw-r--r--jdisc_http_service/pom.xml1
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java5
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java89
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java3
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java3
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java1
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java40
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java54
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java14
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java16
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java5
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java5
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java8
-rw-r--r--jdisc_jetty/pom.xml1
-rw-r--r--jdisc_messagebus_service/pom.xml1
-rw-r--r--jrt/pom.xml1
-rw-r--r--linguistics/pom.xml2
-rw-r--r--logd/pom.xml1
-rw-r--r--logd/src/apps/logd/main.cpp6
-rw-r--r--logd/src/logd/CMakeLists.txt10
-rw-r--r--logd/src/logd/conf.cpp28
-rw-r--r--logd/src/logd/conf.h4
-rw-r--r--logd/src/logd/forward.cpp5
-rw-r--r--logd/src/logd/forward.h4
-rw-r--r--logd/src/logd/metrics.cpp3
-rw-r--r--logd/src/logd/metrics.h39
-rw-r--r--logd/src/logd/state.cpp12
-rw-r--r--logd/src/logd/state.h8
-rw-r--r--logd/src/logd/watch.cpp5
-rw-r--r--logd/src/tests/forward/forward.cpp8
-rw-r--r--logserver/pom.xml1
-rw-r--r--maven-plugins/pom.xml1
-rw-r--r--memfilepersistence/src/tests/spi/memcachetest.cpp11
-rw-r--r--memfilepersistence/src/tests/spi/memfiletestutils.cpp7
-rw-r--r--memfilepersistence/src/tests/spi/memfiletestutils.h4
-rw-r--r--memfilepersistence/src/tests/spi/providerconformancetest.cpp9
-rw-r--r--memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp9
-rw-r--r--memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h6
-rw-r--r--memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp3
-rw-r--r--messagebus-disc/pom.xml1
-rw-r--r--messagebus/pom.xml1
-rw-r--r--messagebus/src/vespa/messagebus/messagebus.cpp1
-rw-r--r--messagebus/src/vespa/messagebus/messenger.cpp1
-rw-r--r--metrics/pom.xml1
-rw-r--r--node-admin/pom.xml1
-rw-r--r--node-admin/src/main/application/services.xml23
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java14
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java57
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java12
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java93
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java41
-rwxr-xr-xnode-admin/src/main/sh/node-admin.sh82
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java70
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java24
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java15
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java86
-rw-r--r--node-admin/src/test/resources/docker.stats.json2
-rw-r--r--node-admin/src/test/resources/expected.container.system.metrics.txt1
-rw-r--r--node-admin/vespa-node-admin.spec49
-rw-r--r--node-maintainer/pom.xml1
-rw-r--r--node-repository/pom.xml1
-rw-r--r--node-repository/src/main/config/node-repository.xml1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java88
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java18
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java13
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java373
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java84
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json8
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json56
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json70
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json70
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json70
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json70
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json70
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json45
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json39
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json15
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json35
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json13
-rw-r--r--orchestrator-restapi/pom.xml1
-rw-r--r--orchestrator/pom.xml1
-rw-r--r--parent/pom.xml718
-rw-r--r--pom.xml942
-rw-r--r--predicate-search-core/pom.xml1
-rw-r--r--predicate-search/pom.xml1
-rw-r--r--processing/pom.xml1
-rwxr-xr-xprovided-dependencies/pom.xml1
-rw-r--r--searchcore/pom.xml1
-rw-r--r--searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp2
-rw-r--r--searchcore/src/tests/proton/common/selectpruner_test.cpp2
-rw-r--r--searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp36
-rw-r--r--searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp2
-rw-r--r--searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp15
-rw-r--r--searchcore/src/tests/proton/flushengine/flushengine.cpp74
-rw-r--r--searchcore/src/tests/proton/index/indexmanager_test.cpp4
-rw-r--r--searchcore/src/tests/proton/matchengine/matchengine.cpp22
-rw-r--r--searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp3
-rw-r--r--searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp56
-rw-r--r--searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp17
-rw-r--r--searchcore/src/tests/proton/summaryengine/summaryengine.cpp24
-rw-r--r--searchcore/src/vespa/searchcore/config/proton.def10
-rw-r--r--searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h4
-rw-r--r--searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp41
-rw-r--r--searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp14
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/eventlogger.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/feedtoken.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/handlermap.hpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp7
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp73
-rw-r--r--searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h17
-rw-r--r--searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/index/indexmanager.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/matcher.cpp32
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/matcher.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp12
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/querylimiter.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp28
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp13
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp24
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h21
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp12
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp10
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h24
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp5
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp19
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/feedhandler.h7
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp22
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp28
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp23
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/memoryflush.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/packetwrapper.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.cpp13
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp25
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp16
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp11
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h5
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp28
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h5
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/simpleflush.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp19
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h5
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h7
-rw-r--r--searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp10
-rw-r--r--searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h2
-rw-r--r--searchcorespi/src/tests/plugin/plugin.cpp1
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h7
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp9
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h1
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp1
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp4
-rw-r--r--searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h2
-rw-r--r--searchlib/pom.xml1
-rw-r--r--searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp13
-rw-r--r--searchlib/src/tests/attribute/benchmark/attributesearcher.h (renamed from searchlib/src/tests/attribute/attributesearcher.h)18
-rw-r--r--searchlib/src/tests/attribute/benchmark/attributeupdater.h (renamed from searchlib/src/tests/attribute/attributeupdater.h)4
-rw-r--r--searchlib/src/tests/attribute/runnable.h43
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp6
-rw-r--r--searchlib/src/tests/features/constant/constant_test.cpp2
-rw-r--r--searchlib/src/tests/transactionlog/translogclient_test.cpp23
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributecontext.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributecontext.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributemanager.cpp14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributemanager.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.h3
-rw-r--r--searchlib/src/vespa/searchlib/common/bitvectorcache.cpp16
-rw-r--r--searchlib/src/vespa/searchlib/common/bitvectorcache.h6
-rw-r--r--searchlib/src/vespa/searchlib/common/gatecallback.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/common/sortspec.cpp1
-rw-r--r--searchlib/src/vespa/searchlib/features/queryfeature.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/domain.cpp12
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/domain.h12
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/session.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/session.h7
-rw-r--r--searchlib/src/vespa/searchlib/uca/ucaconverter.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/util/runnable.h24
-rw-r--r--searchsummary/pom.xml1
-rw-r--r--service-monitor/pom.xml2
-rw-r--r--serviceview/pom.xml1
-rw-r--r--simplemetrics/pom.xml1
-rw-r--r--socket_test/pom.xml1
-rw-r--r--staging_vespalib/CMakeLists.txt2
-rw-r--r--staging_vespalib/src/testlist.txt27
-rw-r--r--staging_vespalib/src/tests/metrics/CMakeLists.txt17
-rw-r--r--staging_vespalib/src/tests/metrics/mock_tick.cpp6
-rw-r--r--staging_vespalib/src/tests/metrics/mock_tick.h91
-rw-r--r--staging_vespalib/src/tests/metrics/simple_metrics_test.cpp215
-rw-r--r--staging_vespalib/src/tests/metrics/stable_store_test.cpp65
-rw-r--r--staging_vespalib/src/vespa/vespalib/CMakeLists.txt1
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt33
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp143
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/bucket.h43
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/clock.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/clock.h30
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/counter.cpp19
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/counter.h56
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp21
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h20
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp32
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/current_samples.h26
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/dimension.h18
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp24
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h62
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp18
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/gauge.h46
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp30
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h24
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/handle.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/handle.h52
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp75
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h37
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/label.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/label.h18
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h38
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_name.h15
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp44
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metric_types.h33
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp8
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h91
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp38
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/name_collection.h29
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point.cpp10
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point.h17
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp52
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_builder.h55
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp49
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_map.h27
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp39
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h30
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/producer.cpp33
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/producer.h25
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp8
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h23
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp224
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h97
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp62
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h26
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp8
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/snapshots.h106
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp4
-rw-r--r--staging_vespalib/src/vespa/vespalib/metrics/stable_store.h88
-rw-r--r--standalone-container/pom.xml1
-rw-r--r--standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala5
-rw-r--r--standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala2
-rwxr-xr-xstandalone-container/src/main/sh/standalone-container.sh309
-rw-r--r--standalone-container/vespa-standalone-container.spec95
-rw-r--r--statistics/pom.xml1
-rw-r--r--storage/CMakeLists.txt3
-rw-r--r--storage/pom.xml1
-rw-r--r--storage/src/tests/CMakeLists.txt2
-rw-r--r--storage/src/tests/common/CMakeLists.txt1
-rw-r--r--storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp385
-rw-r--r--storage/src/tests/common/teststorageapp.h1
-rw-r--r--storage/src/tests/distributor/twophaseupdateoperationtest.cpp8
-rw-r--r--storage/src/tests/distributor/visitoroperationtest.cpp3
-rw-r--r--storage/src/tests/frameworkimpl/memory/CMakeLists.txt8
-rw-r--r--storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp170
-rw-r--r--storage/src/tests/persistence/filestorage/filestormanagertest.cpp57
-rw-r--r--storage/src/tests/persistence/persistencetestutils.cpp1
-rw-r--r--storage/src/tests/persistence/persistencetestutils.h1
-rw-r--r--storage/src/tests/storageserver/communicationmanagertest.cpp1
-rw-r--r--storage/src/tests/storageutil/.gitignore13
-rw-r--r--storage/src/tests/storageutil/CMakeLists.txt9
-rw-r--r--storage/src/tests/storageutil/charttest.cpp61
-rw-r--r--storage/src/tests/storageutil/functortest.cpp54
-rw-r--r--storage/src/tests/storageutil/palettetest.cpp29
-rw-r--r--storage/src/vespa/storage/CMakeLists.txt1
-rw-r--r--storage/src/vespa/storage/bucketdb/bucketmanager.cpp42
-rw-r--r--storage/src/vespa/storage/bucketdb/bucketmanager.h14
-rw-r--r--storage/src/vespa/storage/bucketdb/lockablemap.h13
-rw-r--r--storage/src/vespa/storage/bucketdb/lockablemap.hpp94
-rw-r--r--storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp1
-rw-r--r--storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp23
-rw-r--r--storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h8
-rw-r--r--storage/src/vespa/storage/common/CMakeLists.txt1
-rw-r--r--storage/src/vespa/storage/common/bucketoperationlogger.cpp8
-rw-r--r--storage/src/vespa/storage/common/bucketoperationlogger.h4
-rw-r--r--storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp157
-rw-r--r--storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h16
-rw-r--r--storage/src/vespa/storage/common/statusmetricconsumer.cpp15
-rw-r--r--storage/src/vespa/storage/common/statusmetricconsumer.h1
-rw-r--r--storage/src/vespa/storage/common/storagecomponent.cpp16
-rw-r--r--storage/src/vespa/storage/common/storagecomponent.h4
-rw-r--r--storage/src/vespa/storage/common/storagelinkqueued.h10
-rw-r--r--storage/src/vespa/storage/common/storagelinkqueued.hpp35
-rw-r--r--storage/src/vespa/storage/config/.gitignore2
-rw-r--r--storage/src/vespa/storage/config/bucketspaces.def3
-rw-r--r--storage/src/vespa/storage/distributor/bucketdbupdater.h1
-rw-r--r--storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h2
-rw-r--r--storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt8
-rw-r--r--storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h20
-rw-r--r--storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp661
-rw-r--r--storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h137
-rw-r--r--storage/src/vespa/storage/persistence/fieldvisitor.h1
-rw-r--r--storage/src/vespa/storage/persistence/messages.cpp6
-rw-r--r--storage/src/vespa/storage/persistence/messages.h7
-rw-r--r--storage/src/vespa/storage/storageserver/CMakeLists.txt1
-rw-r--r--storage/src/vespa/storage/storageserver/communicationmanager.cpp45
-rw-r--r--storage/src/vespa/storage/storageserver/communicationmanager.h11
-rw-r--r--storage/src/vespa/storage/storageserver/framework.h12
-rw-r--r--storage/src/vespa/storage/storageserver/messageallocationtypes.cpp100
-rw-r--r--storage/src/vespa/storage/storageserver/messageallocationtypes.h24
-rw-r--r--storage/src/vespa/storage/storageserver/storagenode.cpp19
-rw-r--r--storage/src/vespa/storage/storageserver/storagenode.h2
-rw-r--r--storage/src/vespa/storage/storageserver/storagenodecontext.cpp18
-rw-r--r--storage/src/vespa/storage/storageserver/storagenodecontext.h12
-rw-r--r--storage/src/vespa/storage/storageutil/CMakeLists.txt3
-rw-r--r--storage/src/vespa/storage/storageutil/functor.h60
-rw-r--r--storage/src/vespa/storage/storageutil/graph.cpp206
-rw-r--r--storage/src/vespa/storage/storageutil/graph.h99
-rw-r--r--storage/src/vespa/storage/storageutil/palette.cpp110
-rw-r--r--storage/src/vespa/storage/storageutil/palette.h32
-rw-r--r--storage/src/vespa/storage/storageutil/piechart.cpp201
-rw-r--r--storage/src/vespa/storage/storageutil/piechart.h66
-rw-r--r--storage/src/vespa/storage/visiting/visitor.cpp35
-rw-r--r--storage/src/vespa/storage/visiting/visitor.h6
-rw-r--r--storage/src/vespa/storage/visiting/visitormanager.cpp5
-rw-r--r--storage/src/vespa/storage/visiting/visitorthread.cpp7
-rw-r--r--storage/src/vespa/storage/visiting/visitorthread.h1
-rw-r--r--storageapi/src/vespa/storageapi/messageapi/storagemessage.h6
-rw-r--r--storageframework/CMakeLists.txt3
-rw-r--r--storageframework/src/tests/CMakeLists.txt1
-rw-r--r--storageframework/src/tests/memory/.gitignore2
-rw-r--r--storageframework/src/tests/memory/CMakeLists.txt8
-rw-r--r--storageframework/src/tests/memory/memorymanagertest.cpp397
-rw-r--r--storageframework/src/tests/memory/memorystatetest.cpp172
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt1
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp10
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h1
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp6
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h3
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore2
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt10
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h48
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp157
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h156
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp225
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h139
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp46
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h66
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp30
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h29
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp232
-rw-r--r--storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h98
-rw-r--r--storageframework/src/vespa/storageframework/generic/CMakeLists.txt1
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/.gitignore2
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt6
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h51
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h63
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp13
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/memorytoken.h29
-rw-r--r--storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h41
-rw-r--r--storageframework/src/vespa/storageframework/generic/thread/thread.cpp11
-rw-r--r--storageframework/src/vespa/storageframework/generic/thread/thread.h4
-rw-r--r--storageframework/src/vespa/storageframework/storageframework.h1
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp15
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp59
-rw-r--r--testutil/pom.xml2
-rwxr-xr-xtravis/travis-build-full.sh2
-rw-r--r--vbench/src/vbench/core/dispatcher.h1
-rw-r--r--vdslib/pom.xml1
-rw-r--r--vdslib/src/vespa/vdslib/distribution/CMakeLists.txt1
-rw-r--r--vdslib/src/vespa/vdslib/distribution/distribution.cpp53
-rw-r--r--vdslib/src/vespa/vdslib/distribution/distribution.h19
-rw-r--r--vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp18
-rw-r--r--vdslib/src/vespa/vdslib/distribution/distribution_config_util.h15
-rw-r--r--vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp38
-rw-r--r--vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h2
-rw-r--r--vespa-application-maven-plugin/pom.xml1
-rw-r--r--vespa-documentgen-plugin/pom.xml1
-rw-r--r--vespa-hadoop/pom.xml1
-rw-r--r--vespa-http-client/pom.xml1
-rw-r--r--vespa_feed_perf/pom.xml1
-rw-r--r--vespa_jersey2/pom.xml1
-rw-r--r--vespabase/conf/default-env.txt.in1
-rw-r--r--vespaclient-container-plugin/pom.xml1
-rw-r--r--vespaclient-core/pom.xml1
-rw-r--r--vespaclient-java/pom.xml1
-rw-r--r--vespajlib/pom.xml1
-rw-r--r--vespajlib/src/main/java/com/yahoo/io/IOUtils.java27
-rw-r--r--vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java21
-rw-r--r--vespalib/src/vespa/vespalib/testkit/test_hook.h2
-rw-r--r--vespalib/src/vespa/vespalib/testkit/time_bomb.h2
-rw-r--r--vespalib/src/vespa/vespalib/util/count_down_latch.h95
-rw-r--r--vespalib/src/vespa/vespalib/util/gate.h22
-rw-r--r--vespalib/src/vespa/vespalib/util/simple_thread_bundle.h1
-rw-r--r--vespalib/src/vespa/vespalib/util/sync.h103
-rw-r--r--vespalib/src/vespa/vespalib/util/thread.h1
-rw-r--r--vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h1
-rw-r--r--vespalog/pom.xml1
-rw-r--r--vespamalloc/src/tests/test1/testatomic.cpp2
-rw-r--r--vsm/pom.xml1
-rw-r--r--yolean/pom.xml1
-rw-r--r--zkfacade/pom.xml1
878 files changed, 15329 insertions, 12677 deletions
diff --git a/annotations/pom.xml b/annotations/pom.xml
index 146eb5e9be2..3321dddaf8c 100644
--- a/annotations/pom.xml
+++ b/annotations/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>annotations</artifactId>
<packaging>jar</packaging>
diff --git a/application-deploy-plugin/pom.xml b/application-deploy-plugin/pom.xml
index 3cae7cf7831..501dee385a6 100644
--- a/application-deploy-plugin/pom.xml
+++ b/application-deploy-plugin/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-deploy-plugin</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/application-model/pom.xml b/application-model/pom.xml
index 13a2d3ea49b..801c72e9194 100644
--- a/application-model/pom.xml
+++ b/application-model/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-model</artifactId>
<packaging>container-plugin</packaging>
diff --git a/application-preprocessor/pom.xml b/application-preprocessor/pom.xml
index 4fa319a881a..6ffff09e548 100644
--- a/application-preprocessor/pom.xml
+++ b/application-preprocessor/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-preprocessor</artifactId>
<packaging>jar</packaging>
diff --git a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
index e879acef3bb..303ce3e8d29 100644
--- a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
+++ b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
@@ -39,9 +39,9 @@ public class ApplicationPreprocessor {
FilesApplicationPackage.Builder applicationPackageBuilder = new FilesApplicationPackage.Builder(applicationDir);
outputDir.ifPresent(applicationPackageBuilder::preprocessedDir);
ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess(
- new Zone(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())),
- (a, b) -> {
- }, logger);
+ ZoneId.from(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())),
+ (a, b) -> { },
+ logger);
preprocessed.validateXML();
}
diff --git a/application/pom.xml b/application/pom.xml
index 17e9ece3543..8103226e55d 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application</artifactId>
<packaging>jar</packaging>
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml
index c87589d7be2..bfd02d54d43 100644
--- a/athenz-identity-provider-service/pom.xml
+++ b/athenz-identity-provider-service/pom.xml
@@ -11,6 +11,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<dependencies>
<!-- COMPILE -->
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
index 7910650ed5e..706f797cd2c 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator;
import com.yahoo.jdisc.http.ssl.SslKeyStoreContext;
@@ -11,12 +10,15 @@ import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient;
+import java.io.IOException;
import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
-import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -35,13 +37,14 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements
// TODO Make expiry and update frequency configurable parameters
private static final Duration CERTIFICATE_EXPIRY_TIME = Duration.ofDays(30);
private static final Duration CERTIFICATE_UPDATE_PERIOD = Duration.ofDays(7);
+ private static final String DUMMY_PASSWORD = "athenz";
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AthenzCertificateClient certificateClient;
private final KeyProvider keyProvider;
private final AthenzProviderServiceConfig.Zones zoneConfig;
private final AtomicBoolean alreadyConfigured = new AtomicBoolean();
- private final Zone zone;
+ private KeyStore initialKeyStore;
@Inject
public AthenzSslKeyStoreConfigurator(KeyProvider keyProvider,
@@ -51,20 +54,20 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements
this.certificateClient = new AthenzCertificateClient(config, zoneConfig);
this.keyProvider = keyProvider;
this.zoneConfig = zoneConfig;
- this.zone = zone;
+ this.initialKeyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig);
}
@Override
public void configure(SslKeyStoreContext sslKeyStoreContext) {
- // TODO Remove this when main is ready
- if (zone.system() != SystemName.cd) {
- return;
- }
if (alreadyConfigured.getAndSet(true)) { // For debugging purpose of SslKeyStoreConfigurator interface
throw new IllegalStateException("Already configured. configure() can only be called once.");
}
- AthenzCertificateUpdater updater = new AthenzCertificateUpdater(sslKeyStoreContext);
- scheduler.scheduleAtFixedRate(updater, /*initialDelay*/0, CERTIFICATE_UPDATE_PERIOD.toMinutes(), TimeUnit.MINUTES);
+ sslKeyStoreContext.updateKeyStore(initialKeyStore, DUMMY_PASSWORD);
+ initialKeyStore = null;
+ scheduler.scheduleAtFixedRate(new AthenzCertificateUpdater(sslKeyStoreContext),
+ CERTIFICATE_UPDATE_PERIOD.toMinutes()/*initial delay*/,
+ CERTIFICATE_UPDATE_PERIOD.toMinutes(),
+ TimeUnit.MINUTES);
}
@Override
@@ -77,6 +80,32 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements
}
}
+ private static KeyStore downloadCertificate(KeyProvider keyProvider,
+ AthenzCertificateClient certificateClient,
+ AthenzProviderServiceConfig.Zones zoneConfig) {
+ try {
+ PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion());
+ X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME);
+ verifyActualExpiry(certificate);
+
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ keyStore.load(null);
+ keyStore.setKeyEntry("athenz", privateKey, DUMMY_PASSWORD.toCharArray(), new Certificate[]{certificate});
+ return keyStore;
+ } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void verifyActualExpiry(X509Certificate certificate) {
+ Duration actualExpiry =
+ Duration.between(certificate.getNotBefore().toInstant(), certificate.getNotAfter().toInstant());
+ if (CERTIFICATE_EXPIRY_TIME.compareTo(actualExpiry) > 0) {
+ log.log(LogLevel.WARNING,
+ String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry));
+ }
+ }
+
private class AthenzCertificateUpdater implements Runnable {
private final SslKeyStoreContext sslKeyStoreContext;
@@ -89,29 +118,13 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements
public void run() {
try {
log.log(LogLevel.INFO, "Updating Athenz certificate from ZTS");
- PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion());
- X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME);
- verifyActualExperiy(certificate);
-
- String dummyPassword = "athenz";
- KeyStore keyStore = KeyStore.getInstance("JKS");
- keyStore.load(null);
- keyStore.setKeyEntry("athenz", privateKey, dummyPassword.toCharArray(), new Certificate[]{certificate});
- sslKeyStoreContext.updateKeyStore(keyStore, dummyPassword);
+ KeyStore keyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig);
+ sslKeyStoreContext.updateKeyStore(keyStore, DUMMY_PASSWORD);
log.log(LogLevel.INFO, "Athenz certificate reload successfully completed");
} catch (Throwable e) {
log.log(LogLevel.ERROR, "Failed to update certificate from ZTS: " + e.getMessage(), e);
}
}
- private void verifyActualExperiy(X509Certificate certificate) {
- Instant notAfter = certificate.getNotAfter().toInstant();
- Instant notBefore = certificate.getNotBefore().toInstant();
- if (!notBefore.plus(CERTIFICATE_EXPIRY_TIME).equals(notAfter)) {
- Duration actualExpiry = Duration.between(notBefore, notAfter);
- log.log(LogLevel.WARNING,
- String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry));
- }
- }
}
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java
new file mode 100644
index 00000000000..8c8b5de2a30
--- /dev/null
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java
@@ -0,0 +1,110 @@
+// 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.athenz.instanceproviderservice;
+
+import com.google.inject.Inject;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.SslTrustStoreContext;
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.Provider;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.logging.Logger;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurator {
+
+ private static final Logger log = Logger.getLogger(AthenzSslTrustStoreConfigurator.class.getName());
+
+ private static final Provider provider = new BouncyCastleProvider();
+ private final KeyStore trustStore;
+
+ @Inject
+ public AthenzSslTrustStoreConfigurator(KeyProvider keyProvider,
+ ConfigserverConfig configserverConfig,
+ AthenzProviderServiceConfig athenzProviderServiceConfig) {
+ this.trustStore = createTrustStore(keyProvider, configserverConfig, athenzProviderServiceConfig);
+ }
+
+ @Override
+ public void configure(SslTrustStoreContext sslTrustStoreContext) {
+ sslTrustStoreContext.updateTrustStore(trustStore);
+ log.log(LogLevel.INFO, "Configured JDisc trust store with self-signed certificate");
+ }
+
+ private static KeyStore createTrustStore(KeyProvider keyProvider,
+ ConfigserverConfig configserverConfig,
+ AthenzProviderServiceConfig athenzProviderServiceConfig) {
+ try {
+ KeyPair keyPair = getKeyPair(keyProvider, configserverConfig, athenzProviderServiceConfig);
+ X509Certificate selfSignedCertificate = createSelfSignedCertificate(keyPair, configserverConfig);
+ log.log(LogLevel.FINE, "Generated self-signed certificate: " + selfSignedCertificate);
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream in = new FileInputStream(athenzProviderServiceConfig.athenzCaTrustStore())) {
+ trustStore.load(in, "changeit".toCharArray());
+ }
+ trustStore.setCertificateEntry("cfgselfsigned", selfSignedCertificate);
+ return trustStore;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static KeyPair getKeyPair(KeyProvider keyProvider,
+ ConfigserverConfig configserverConfig,
+ AthenzProviderServiceConfig athenzProviderServiceConfig) {
+ String key = configserverConfig.environment() + "." + configserverConfig.region();
+ AthenzProviderServiceConfig.Zones zoneConfig = athenzProviderServiceConfig.zones(key);
+ return keyProvider.getKeyPair(zoneConfig.secretVersion());
+ }
+
+ private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config)
+ throws IOException, CertificateException, OperatorCreationException {
+ ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
+ X500Name x500Name = new X500Name("CN="+ config.loadBalancerAddress());
+ Instant now = Instant.now();
+ Date notBefore = Date.from(now);
+ Date notAfter = Date.from(now.plus(Duration.ofDays(30)));
+
+ GeneralNames generalNames = new GeneralNames(
+ config.zookeeperserver().stream()
+ .map(server -> new GeneralName(GeneralName.dNSName, server.hostname()))
+ .toArray(GeneralName[]::new));
+
+ X509v3CertificateBuilder certificateBuilder =
+ new JcaX509v3CertificateBuilder(
+ x500Name, BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, x500Name, keyPair.getPublic()
+ )
+ .addExtension(Extension.basicConstraints, true, new BasicConstraints(true))
+ .addExtension(Extension.subjectAlternativeName, false, generalNames);
+
+ return new JcaX509CertificateConverter()
+ .setProvider(provider)
+ .getCertificate(certificateBuilder.build(contentSigner));
+ }
+
+}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java
index a72a2fcbc6c..1d141099428 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.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.athenz.instanceproviderservice;
+import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -11,4 +12,8 @@ public interface KeyProvider {
PrivateKey getPrivateKey(int version);
PublicKey getPublicKey(int version);
+
+ default KeyPair getKeyPair(int version) {
+ return new KeyPair(getPublicKey(version), getPrivateKey(version));
+ }
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java
index 2dc3f24664c..1014fc4afdf 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java
@@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.provision.Zone;
import com.yahoo.log.LogLevel;
-import com.yahoo.net.HostName;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -68,13 +68,16 @@ public class CertificateSigner {
private final Clock clock;
@Inject
- public CertificateSigner(KeyProvider keyProvider, AthenzProviderServiceConfig config, Zone zone) {
- this(getPrivateKey(keyProvider, config, zone), HostName.getLocalhost(), Clock.systemUTC());
+ public CertificateSigner(KeyProvider keyProvider,
+ ConfigserverConfig configserverConfig,
+ AthenzProviderServiceConfig config,
+ Zone zone) {
+ this(getPrivateKey(keyProvider, config, zone), configserverConfig.loadBalancerAddress(), Clock.systemUTC());
}
- CertificateSigner(PrivateKey caPrivateKey, String configServerHostname, Clock clock) {
+ CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) {
this.caPrivateKey = caPrivateKey;
- this.issuer = new X500Name("CN=" + configServerHostname);
+ this.issuer = new X500Name("CN=" + loadBalancerAddress);
this.clock = clock;
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java
index e66131b6cf7..ac8c0eabf31 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java
@@ -45,7 +45,8 @@ public class SecretStoreKeyProvider implements KeyProvider {
return getKeyPair(version).getPublic();
}
- private KeyPair getKeyPair(int version) {
+ @Override
+ public KeyPair getKeyPair(int version) {
synchronized (secrets) {
KeyPair keyPair = secrets.get(version);
if (keyPair == null) {
diff --git a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def
index 13cc78b0bd0..21f2aea6ab0 100644
--- a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def
+++ b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def
@@ -21,3 +21,6 @@ ztsUrl string
# Certificate DNS suffix
certDnsSuffix string
+
+# Path to Athenz CA JKS trust store
+athenzCaTrustStore string
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java
index c09a9fb1740..da2bf929e82 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java
@@ -25,7 +25,8 @@ public class TestUtils {
.zones(ImmutableMap.of(zone.environment().value() + "." + zone.region().value(), zoneConfig))
.certDnsSuffix(dnsSuffix)
.ztsUrl("localhost/zts")
- .athenzPrincipalHeaderName("Athenz-Principal-Auth"));
+ .athenzPrincipalHeaderName("Athenz-Principal-Auth")
+ .athenzCaTrustStore("/dummy/path/to/athenz-ca.jks"));
}
}
diff --git a/bootstrap-cpp.sh b/bootstrap-cpp.sh
index 47d2a82622a..e6b4c816065 100755
--- a/bootstrap-cpp.sh
+++ b/bootstrap-cpp.sh
@@ -33,7 +33,7 @@ mkdir -p "${BUILD_DIR}" || {
BUILD_DIR=$(realpath "${BUILD_DIR}")
# Build it
-source /opt/rh/devtoolset-6/enable || true
+source /opt/rh/devtoolset-7/enable || true
cd "${SOURCE_DIR}"
bash ./bootstrap.sh full
cd "${BUILD_DIR}"
diff --git a/bootstrap.sh b/bootstrap.sh
index 075da74b7c7..b71af73c529 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -44,8 +44,16 @@ $top/dist/getversion.pl -M $top > $top/dist/vtag.map
# The 'default' mode also builds some modules needed by C++ code.
# The 'full' mode also builds modules needed by C++ tests.
-# must install parent pom first:
+# must install parent poms first:
echo "Downloading all dependencies. This may take a few minutes with an empty Maven cache."
+(
+ cd container-dependency-versions
+ mvn_install
+)
+(
+ cd parent
+ mvn_install
+)
mvn_install -N
# and build plugins first:
diff --git a/bundle-plugin-test/pom.xml b/bundle-plugin-test/pom.xml
index 8d8bf48cc07..3dd5be00add 100644
--- a/bundle-plugin-test/pom.xml
+++ b/bundle-plugin-test/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugin-test</artifactId>
diff --git a/bundle-plugin/pom.xml b/bundle-plugin/pom.xml
index 02f0ed2ccf5..47a06a1ee76 100644
--- a/bundle-plugin/pom.xml
+++ b/bundle-plugin/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>bundle-plugin</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/chain/pom.xml b/chain/pom.xml
index a3096d37748..01a64a93609 100755
--- a/chain/pom.xml
+++ b/chain/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>chain</artifactId>
<packaging>jar</packaging>
diff --git a/clustercontroller-apps/pom.xml b/clustercontroller-apps/pom.xml
index 26f466eb208..bdc6059a816 100644
--- a/clustercontroller-apps/pom.xml
+++ b/clustercontroller-apps/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>clustercontroller-apps</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/clustercontroller-apputil/pom.xml b/clustercontroller-apputil/pom.xml
index 68adc82596f..42c7690eab6 100644
--- a/clustercontroller-apputil/pom.xml
+++ b/clustercontroller-apputil/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>clustercontroller-apputil</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/clustercontroller-core/pom.xml b/clustercontroller-core/pom.xml
index 499dee0d1de..99cf4df643b 100644
--- a/clustercontroller-core/pom.xml
+++ b/clustercontroller-core/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>clustercontroller-core</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/clustercontroller-standalone/pom.xml b/clustercontroller-standalone/pom.xml
index f51555a92b6..f435cd9d7c4 100644
--- a/clustercontroller-standalone/pom.xml
+++ b/clustercontroller-standalone/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>clustercontroller-standalone</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/clustercontroller-utils/pom.xml b/clustercontroller-utils/pom.xml
index f151a623ff9..287a85089ca 100644
--- a/clustercontroller-utils/pom.xml
+++ b/clustercontroller-utils/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>clustercontroller-utils</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/component/pom.xml b/component/pom.xml
index 92f852e4ba0..18bfd000a84 100755
--- a/component/pom.xml
+++ b/component/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>component</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config-application-package/pom.xml b/config-application-package/pom.xml
index 90d4a2d9af6..629c02de1e8 100644
--- a/config-application-package/pom.xml
+++ b/config-application-package/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-application-package</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
index 97322fc1c55..330692abb1e 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
@@ -15,6 +15,7 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.provision.Version;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.path.Path;
import com.yahoo.io.HexDump;
import com.yahoo.io.IOUtils;
@@ -649,7 +650,7 @@ public class FilesApplicationPackage implements ApplicationPackage {
return searchDefinitionContents();
}
- private void preprocessXML(File destination, File inputXml, Zone zone) throws ParserConfigurationException, TransformerException, SAXException, IOException {
+ private void preprocessXML(File destination, File inputXml, ZoneId zone) throws ParserConfigurationException, TransformerException, SAXException, IOException {
Document document = new XmlPreProcessor(appDir, inputXml, zone.environment(), zone.region()).run();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
try (FileOutputStream outputStream = new FileOutputStream(destination)) {
@@ -658,7 +659,7 @@ public class FilesApplicationPackage implements ApplicationPackage {
}
@Override
- public ApplicationPackage preprocess(Zone zone, RuleConfigDeriver ignored, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException {
+ public ApplicationPackage preprocess(ZoneId zone, RuleConfigDeriver ignored, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException {
IOUtils.recursiveDeleteDir(preprocessedDir);
IOUtils.copyDirectory(appDir, preprocessedDir, -1, (dir, name) -> ! name.equals(".preprocessed") &&
! name.equals(SERVICES) &&
diff --git a/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java b/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java
index c12a9ad0d06..2dfaf440084 100644
--- a/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.io.IOUtils;
import org.junit.Rule;
import org.junit.Test;
@@ -41,13 +42,9 @@ public class FilesApplicationPackageTest {
assertTrue(new File(appDir, "hosts.xml").exists());
FilesApplicationPackage app = FilesApplicationPackage.fromFile(appDir);
- ApplicationPackage processed = app.preprocess(new Zone(Environment.dev, RegionName.defaultName()),
- new RuleConfigDeriver() {
- @Override
- public void derive(String ruleBaseDir, String outputDir) throws Exception {
- }
- },
- new BaseDeployLogger());
+ ApplicationPackage processed = app.preprocess(ZoneId.from(Environment.dev, RegionName.defaultName()),
+ (ruleBaseDir, outputDir) -> {},
+ new BaseDeployLogger());
assertTrue(new File(appDir, ".preprocessed").exists());
String expectedServices = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><services xmlns:deploy=\"vespa\" xmlns:preprocess=\"properties\" version=\"1.0\">\n" +
" <admin version=\"2.0\">\n" +
diff --git a/config-bundle/pom.xml b/config-bundle/pom.xml
index 7121756ce68..07262141801 100644
--- a/config-bundle/pom.xml
+++ b/config-bundle/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-bundle</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config-class-plugin/pom.xml b/config-class-plugin/pom.xml
index 5b3149a13da..71a20782d85 100644
--- a/config-class-plugin/pom.xml
+++ b/config-class-plugin/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-class-plugin</artifactId>
<packaging>maven-plugin</packaging>
diff --git a/config-lib/pom.xml b/config-lib/pom.xml
index 2ed993dd052..31648958fa6 100644
--- a/config-lib/pom.xml
+++ b/config-lib/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-lib</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config-model-api/pom.xml b/config-model-api/pom.xml
index 6618612df31..743164afb16 100644
--- a/config-model-api/pom.xml
+++ b/config-model-api/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-model-api</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java
index c1a786194a2..480d4d05451 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application.api;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.Version;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.path.Path;
import com.yahoo.io.IOUtils;
import com.yahoo.io.reader.NamedReader;
@@ -255,13 +256,13 @@ public interface ApplicationPackage {
* application package. This is the entry point for the multi environment application package support. This method
* will not mutate the existing application package.
*
- * @param zone A valid {@link Zone} instance, used to decide which parts of services to keep and remove
+ * @param zone A valid {@link ZoneId} instance, used to decide which parts of services to keep and remove
* @param ruleConfigDeriver ignored
* @param logger A {@link DeployLogger} to add output that will be returned to the user
*
* @return A new application package instance pointing to a new location
*/
- default ApplicationPackage preprocess(Zone zone, RuleConfigDeriver ruleConfigDeriver, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException {
+ default ApplicationPackage preprocess(ZoneId zone, RuleConfigDeriver ruleConfigDeriver, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException {
throw new UnsupportedOperationException("This application package does not support preprocessing");
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java b/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java
index 990bce539ba..3f1c0046a85 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java
@@ -11,12 +11,21 @@ import java.util.Set;
/**
* Interface for models towards filedistribution.
*
- * @author lulf
- * @since 5.1
+ * @author Ulf Lilleengen
*/
public interface FileDistribution {
void sendDeployedFiles(String hostName, Set<FileReference> fileReferences);
+
+ /**
+ * Notifies client which file references to download. Used to start downloading early (while
+ * preparing application package).
+ *
+ * @param hostName host which should be notified about file references to download
+ * @param fileReferences set of file references to start downloading
+ */
+ void startDownload(String hostName, Set<FileReference> fileReferences);
+
void reloadDeployFileDistributor();
void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames);
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 521e72ae580..a69835626ea 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -11,7 +11,6 @@ import com.yahoo.config.provision.Rotation;
import com.yahoo.config.provision.Zone;
import java.io.File;
-import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -47,14 +46,7 @@ public interface ModelContext {
boolean hostedVespa();
Zone zone();
Set<Rotation> rotations();
-
- /*
- * DEPRECATED
- * TODO: Remove when 6.172 and earlier are no longer in use
- */
- default URI loadBalancerAddress() {
- return URI.create("http://localhost");
- }
+ default boolean disableFileDistributor() { return false; }
}
}
diff --git a/config-model-fat/pom.xml b/config-model-fat/pom.xml
index a4bbba0e6e8..0526c2f6581 100644
--- a/config-model-fat/pom.xml
+++ b/config-model-fat/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-model-fat</artifactId>
<packaging>bundle</packaging>
diff --git a/config-model/pom.xml b/config-model/pom.xml
index 0fdc09e1a61..729d1a2ef9b 100644
--- a/config-model/pom.xml
+++ b/config-model/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-model</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java
index 8b3d983d6c1..78a8c161c3b 100644
--- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java
+++ b/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java
@@ -5,17 +5,14 @@ import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import org.w3c.dom.Element;
-import java.util.Optional;
import java.util.stream.Stream;
/**
* This class contains a context that is passed to a model builder, and can be used to retrieve the application package,
* logger etc.
*
- * @author lulf
- * @since 5.1
+ * @author Ulf Lilleengen
*/
public final class ConfigModelContext {
diff --git a/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java b/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java
index 32cb1840e7c..5eb4afcc241 100644
--- a/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java
+++ b/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java
@@ -75,7 +75,11 @@ public class AdminModel extends ConfigModel {
public void doBuild(AdminModel model, Element adminElement, ConfigModelContext modelContext) {
AbstractConfigProducer parent = modelContext.getParentProducer();
DeployProperties properties = modelContext.getDeployState().getProperties();
- DomAdminV2Builder domBuilder = new DomAdminV2Builder(modelContext.getApplicationType(), modelContext.getDeployState().getFileRegistry(), properties.multitenant(), properties.configServerSpecs());
+ DomAdminV2Builder domBuilder = new DomAdminV2Builder(modelContext.getApplicationType(),
+ modelContext.getDeployState().getFileRegistry(),
+ properties.multitenant(),
+ properties.configServerSpecs(),
+ modelContext.getDeployState().disableFiledistributor());
model.admin = domBuilder.build(parent, adminElement);
// TODO: Is required since other models depend on admin.
if (parent instanceof ApplicationConfigProducerRoot) {
@@ -101,7 +105,11 @@ public class AdminModel extends ConfigModel {
public void doBuild(AdminModel model, Element adminElement, ConfigModelContext modelContext) {
AbstractConfigProducer parent = modelContext.getParentProducer();
DeployProperties properties = modelContext.getDeployState().getProperties();
- DomAdminV4Builder domBuilder = new DomAdminV4Builder(modelContext, properties.multitenant(), properties.configServerSpecs(), model.getContainerModels());
+ DomAdminV4Builder domBuilder = new DomAdminV4Builder(modelContext,
+ properties.multitenant(),
+ properties.configServerSpecs(),
+ model.getContainerModels(),
+ modelContext.getDeployState().disableFiledistributor());
model.admin = domBuilder.build(parent, adminElement);
// TODO: Is required since other models depend on admin.
if (parent instanceof ApplicationConfigProducerRoot) {
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
index 25d45dee234..43d3fafdb78 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
@@ -68,8 +68,8 @@ public class DeployState implements ConfigDefinitionStore {
private final ValidationOverrides validationOverrides;
private final Version wantedNodeVespaVersion;
private final Instant now;
-
private final HostProvisioner provisioner;
+ private final boolean disableFiledistributor;
public static DeployState createTestState() {
return new Builder().build();
@@ -82,8 +82,8 @@ public class DeployState implements ConfigDefinitionStore {
private DeployState(ApplicationPackage applicationPackage, SearchDocumentModel searchDocumentModel, RankProfileRegistry rankProfileRegistry,
FileRegistry fileRegistry, DeployLogger deployLogger, Optional<HostProvisioner> hostProvisioner, DeployProperties properties,
Optional<ApplicationPackage> permanentApplicationPackage, Optional<ConfigDefinitionRepo> configDefinitionRepo,
- java.util.Optional<Model> previousModel, Set<Rotation> rotations, Zone zone, QueryProfiles queryProfiles,
- SemanticRules semanticRules, Instant now, Version wantedNodeVespaVersion) {
+ java.util.Optional<Model> previousModel, Set<Rotation> rotations, Zone zone, QueryProfiles queryProfiles,
+ SemanticRules semanticRules, Instant now, Version wantedNodeVespaVersion, boolean disableFiledistributor) {
this.logger = deployLogger;
this.fileRegistry = fileRegistry;
this.rankProfileRegistry = rankProfileRegistry;
@@ -102,6 +102,7 @@ public class DeployState implements ConfigDefinitionStore {
this.validationOverrides = applicationPackage.getValidationOverrides().map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty);
this.wantedNodeVespaVersion = wantedNodeVespaVersion;
this.now = now;
+ this.disableFiledistributor = disableFiledistributor;
}
public static HostProvisioner getDefaultModelHostProvisioner(ApplicationPackage applicationPackage) {
@@ -169,6 +170,7 @@ public class DeployState implements ConfigDefinitionStore {
public ApplicationPackage getApplicationPackage() {
return applicationPackage;
}
+
public List<SearchDefinition> getSearchDefinitions() {
return searchDefinitions;
}
@@ -214,6 +216,8 @@ public class DeployState implements ConfigDefinitionStore {
public Instant now() { return now; }
+ public boolean disableFiledistributor() { return disableFiledistributor; }
+
public static class Builder {
private ApplicationPackage applicationPackage = MockApplicationPackage.createEmpty();
@@ -228,6 +232,7 @@ public class DeployState implements ConfigDefinitionStore {
private Zone zone = Zone.defaultZone();
private Instant now = Instant.now();
private Version wantedNodeVespaVersion = Vtag.currentVersion;
+ private boolean disableFiledistributor = false;
public Builder applicationPackage(ApplicationPackage applicationPackage) {
this.applicationPackage = applicationPackage;
@@ -289,13 +294,19 @@ public class DeployState implements ConfigDefinitionStore {
return this;
}
+ public Builder disableFiledistributor(boolean disableFiledistributor) {
+ this.disableFiledistributor = disableFiledistributor;
+ return this;
+ }
+
public DeployState build() {
RankProfileRegistry rankProfileRegistry = new RankProfileRegistry();
QueryProfiles queryProfiles = new QueryProfilesBuilder().build(applicationPackage);
SemanticRules semanticRules = new SemanticRuleBuilder().build(applicationPackage);
SearchDocumentModel searchDocumentModel = createSearchDocumentModel(rankProfileRegistry, logger, queryProfiles);
return new DeployState(applicationPackage, searchDocumentModel, rankProfileRegistry, fileRegistry, logger, hostProvisioner,
- properties, permanentApplicationPackage, configDefinitionRepo, previousModel, rotations, zone, queryProfiles, semanticRules, now, wantedNodeVespaVersion);
+ properties, permanentApplicationPackage, configDefinitionRepo, previousModel, rotations,
+ zone, queryProfiles, semanticRules, now, wantedNodeVespaVersion, disableFiledistributor);
}
private SearchDocumentModel createSearchDocumentModel(RankProfileRegistry rankProfileRegistry, DeployLogger logger, QueryProfiles queryProfiles) {
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java
index 277e02e43e2..2a3ff1ca905 100644
--- a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java
+++ b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java
@@ -11,6 +11,7 @@ import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.config.model.producer.AbstractConfigProducerRoot;
+import com.yahoo.config.provision.Zone;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.ConfigProducer;
import com.yahoo.vespa.model.HostSystem;
@@ -147,7 +148,8 @@ public class MockRoot extends AbstractConfigProducerRoot {
try {
Document doc = XmlHelper.getDocumentBuilder().parse(new InputSource(new StringReader(servicesXml)));
- setAdmin(new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, deployState.getFileRegistry(), false, new ArrayList<>()).
+ setAdmin(new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, deployState.getFileRegistry(),
+ false, new ArrayList<>(), deployState.disableFiledistributor()).
build(this, XML.getChildren(doc.getDocumentElement(), "admin").get(0)));
} catch (SAXException | IOException e) {
throw new RuntimeException(e);
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java
index a70e77a17a2..c8918f39834 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java
@@ -79,7 +79,12 @@ public class ImmutableImportedSDField implements ImmutableSDField {
@Override
public Index getIndex(String name) {
- throw createUnsupportedException();
+ if (!importedField.fieldName().equals(name)) {
+ throw new IllegalArgumentException("Getting an index (" + name + ") with different name than the imported field ("
+ + importedField.fieldName() + ") is not supported");
+ }
+ String targetIndexName = importedField.targetField().getName();
+ return importedField.targetField().getIndex(targetIndexName);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java
new file mode 100644
index 00000000000..eaa85815736
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java
@@ -0,0 +1,63 @@
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.ImportedField;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Map;
+
+/**
+ * Validates that imported fields in document summaries are of supported types.
+ * Currently, predicate fields are NOT supported.
+ *
+ * @author geirst
+ */
+public class ImportedFieldsInSummayValidator extends Processor {
+
+
+ public ImportedFieldsInSummayValidator(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ if (search.importedFields().isPresent()) {
+ validateDocumentSummaries(search.getSummaries());
+ }
+ }
+
+ private void validateDocumentSummaries(Map<String, DocumentSummary> summaries) {
+ for (DocumentSummary summary : summaries.values()) {
+ for (SummaryField field : summary.getSummaryFields()) {
+ ImportedField importedField = getImportedField(field);
+ if (importedField != null) {
+ validateImportedSummaryField(summary, field, importedField);
+ }
+ }
+ }
+ }
+
+ private ImportedField getImportedField(SummaryField field) {
+ return search.importedFields().get().fields().get(field.getName());
+ }
+
+ private void validateImportedSummaryField(DocumentSummary summary, SummaryField field, ImportedField importedField) {
+ if (field.getDataType().equals(DataType.PREDICATE) &&
+ importedField.targetField().getDataType().equals(DataType.PREDICATE)) {
+ fail(summary, field, "Is of type predicate. Not supported in document summaries");
+ }
+ }
+
+ private void fail(DocumentSummary summary, SummaryField importedField, String msg) {
+ throw new IllegalArgumentException("For search '" + search.getName() + "', document summary '" + summary.getName() +
+ "', imported summary field '" + importedField.getName() + "': " + msg);
+ }
+}
+
+
+
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java
index 26f98026d4f..90183848094 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java
@@ -73,6 +73,7 @@ public class Processing {
TensorFieldProcessor::new,
RankProfileTypeSettingsProcessor::new,
ReferenceFieldsProcessor::new,
+ ImportedFieldsInSummayValidator::new,
FastAccessValidator::new,
ReservedMacroNames::new,
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java
index dfa2d2cefb0..3b75be5167d 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java
@@ -510,22 +510,6 @@ public abstract class AbstractService extends AbstractConfigProducer<AbstractCon
}
/**
- * Sets up this service to be included when generating monitoring config.
- * The ymon service name used will be {@link #getServiceType()}
- */
- public void monitorService() {
- monitorService(getServiceType());
- }
-
- /**
- * Sets up this service to be included when generating ymon config.
- * @param ymonServiceName the ymon service name to be used
- */
- public void monitorService(String ymonServiceName) {
- setProp("ymonService", ymonServiceName);
- }
-
- /**
*
* The service HTTP port for health status
* @return portnumber
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java
index d751e332ff3..c540a5f62d2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java
@@ -16,6 +16,8 @@ package com.yahoo.vespa.model;
*/
public class ConfigProxy extends AbstractService {
+ public final static int BASEPORT = 19090;
+
/**
* Creates a new ConfigProxy instance.
*
@@ -31,7 +33,7 @@ public class ConfigProxy extends AbstractService {
/**
* Returns the desired base port for this service.
*/
- public int getWantedPort() { return 19090; }
+ public int getWantedPort() { return BASEPORT; }
/**
* The desired base port is the only allowed base port.
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
index 74512e70ebe..7a1fab8dbd0 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java
@@ -138,7 +138,8 @@ public class VespaModelFactory implements ModelFactory {
.rotations(modelContext.properties().rotations())
.zone(zone)
.now(clock.instant())
- .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion());
+ .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion())
+ .disableFiledistributor(modelContext.properties().disableFileDistributor());
modelContext.previousModel().ifPresent(builder::previousModel);
return builder.build();
}
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 60d00ce5487..59b7388f5bb 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
@@ -1,20 +1,26 @@
// 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.admin;
-import com.yahoo.cloud.config.log.LogdConfig;
+import com.yahoo.cloud.config.SlobroksConfig;
import com.yahoo.cloud.config.ZookeepersConfig;
+import com.yahoo.cloud.config.log.LogdConfig;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.deploy.DeployProperties;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import com.yahoo.cloud.config.SlobroksConfig;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Zone;
-import com.yahoo.vespa.model.*;
+import com.yahoo.vespa.model.AbstractService;
+import com.yahoo.vespa.model.ConfigProxy;
+import com.yahoo.vespa.model.ConfigSentinel;
+import com.yahoo.vespa.model.HostResource;
+import com.yahoo.vespa.model.Logd;
import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer;
import com.yahoo.vespa.model.admin.monitoring.Monitoring;
import com.yahoo.vespa.model.admin.monitoring.builder.Metrics;
import com.yahoo.vespa.model.container.ContainerCluster;
+import com.yahoo.vespa.model.filedistribution.DummyFileDistributionConfigProducer;
import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer;
+import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProvider;
import com.yahoo.vespa.model.filedistribution.FileDistributor;
import com.yahoo.vespa.model.filedistribution.FileDistributorService;
@@ -24,8 +30,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
-import com.yahoo.vespa.model.HostResource;
-
/**
* This is the admin pseudo-plugin of the Vespa model, responsible for
* creating all admin services.
@@ -59,12 +63,14 @@ public class Admin extends AbstractConfigProducer implements Serializable {
Monitoring monitoring,
Metrics metrics,
Map<String, MetricsConsumer> legacyMetricsConsumers,
- boolean multitenant) {
+ boolean multitenant,
+ FileDistributionConfigProducer fileDistributionConfigProducer) {
super(parent, "admin");
this.monitoring = monitoring;
this.metrics = metrics;
this.legacyMetricsConsumers = legacyMetricsConsumers;
this.multitenant = multitenant;
+ this.fileDistribution = fileDistributionConfigProducer;
}
public Configserver getConfigserver() {
@@ -148,10 +154,6 @@ public class Admin extends AbstractConfigProducer implements Serializable {
zooKeepersConfigProvider.getConfig(builder);
}
- public void setFileDistribution(FileDistributionConfigProducer fileDistribution) {
- this.fileDistribution = fileDistribution;
- }
-
public FileDistributionConfigProducer getFileDistributionConfigProducer() {
return fileDistribution;
}
@@ -215,11 +217,25 @@ public class Admin extends AbstractConfigProducer implements Serializable {
fileDistributor.fileSourceHost() + "'. Hostsystem=" + getHostSystem());
}
- FileDistributorService fds = new FileDistributorService(fileDistribution, host.getHost().getHostname(),
- fileDistribution.getFileDistributor(), fileDistribution.getOptions(), host == deployHost);
- fds.setHostResource(host);
- fds.initService();
- fileDistribution.addFileDistributionService(host.getHost(), fds);
+ FileDistributionConfigProvider configProvider =
+ new FileDistributionConfigProvider(fileDistributor,
+ fileDistribution.getOptions(),
+ host == deployHost,
+ host.getHost());
+ if (fileDistribution.getOptions().disableFiledistributor()) {
+ DummyFileDistributionConfigProducer dummyFileDistributionConfigProducer =
+ new DummyFileDistributionConfigProducer(fileDistribution,
+ host.getHost().getHostname(),
+ configProvider);
+ fileDistribution.addFileDistributionConfigProducer(host.getHost(), dummyFileDistributionConfigProducer);
+ } else {
+ FileDistributorService fds = new FileDistributorService(fileDistribution,
+ host.getHost().getHostname(),
+ configProvider);
+ fds.setHostResource(host);
+ fds.initService();
+ fileDistribution.addFileDistributionConfigProducer(host.getHost(), fds);
+ }
}
private boolean deployHostIsMissing(HostResource deployHost) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java
index 942d563d850..e8f1f59310b 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java
@@ -35,7 +35,6 @@ public class Configserver extends AbstractService {
portsMeta.on(1).tag("http").tag("config").tag("state");
setProp("clustertype", "admin");
setProp("clustername", "admin");
- monitorService();
}
/**
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java
index 0160978773d..8b39a1ecb94 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java
@@ -21,7 +21,7 @@ public class FileDistributionOptions implements FiledistributorConfig.Producer {
private BinaryScaledAmount uploadBitRate = new BinaryScaledAmount();
private BinaryScaledAmount downloadBitRate = new BinaryScaledAmount();
- private boolean disabled = false;
+ private boolean disableFiledistributor = false;
public void downloadBitRate(BinaryScaledAmount amount) {
@@ -34,12 +34,12 @@ public class FileDistributionOptions implements FiledistributorConfig.Producer {
uploadBitRate = amount;
}
- public void disabled(boolean value) {
- disabled = value;
+ public void disableFiledistributor(boolean value) {
+ disableFiledistributor = value;
}
- public boolean disabled() {
- return disabled;
+ public boolean disableFiledistributor() {
+ return disableFiledistributor;
}
private void ensureNonNegative(BinaryScaledAmount amount) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java
index 28cf18802cf..8b98dc9d06a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java
@@ -23,7 +23,6 @@ public class Logserver extends AbstractService {
portsMeta.on(3).tag("logtp").tag("telnet").tag("replicator");
setProp("clustertype", "admin");
setProp("clustername", "admin");
- monitorService();
}
/**
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java
index cefc08981a4..093694f41b3 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java
@@ -23,7 +23,6 @@ public class Slobrok extends AbstractService {
setProp("index", index);
setProp("clustertype", "slobrok");
setProp("clustername", "admin");
- monitorService();
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java
index cafdf83608f..3db09f6b566 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java
@@ -11,6 +11,7 @@ import java.util.Set;
@SuppressWarnings("UnusedDeclaration") // Used by model amenders
public class SystemMetrics {
public static final String CPU_UTIL = "cpu.util";
+ public static final String CPU_SYS_UTIL = "cpu.sys.util";
public static final String DISK_LIMIT = "disk.limit";
public static final String DISK_USED = "disk.used";
public static final String DISK_UTIL = "disk.util";
@@ -23,6 +24,7 @@ public class SystemMetrics {
private static MetricSet createSystemMetricSet() {
Set<Metric> dockerNodeMetrics =
ImmutableSet.of(new Metric(CPU_UTIL),
+ new Metric(CPU_SYS_UTIL),
new Metric(DISK_LIMIT),
new Metric(DISK_USED),
new Metric(DISK_UTIL),
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 2dcf874a66e..2243dc1e682 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
@@ -52,6 +52,7 @@ public class VespaMetricSet {
private static Set<Metric> getOtherMetrics() {
Set<Metric> metrics = new LinkedHashSet<>();
metrics.add(new Metric("slobrok.heartbeats.failed.count", "slobrok.heartbeats.failed"));
+ metrics.add(new Metric("logd.processed.lines.count", "logd.processed.lines"));
return metrics;
}
@@ -220,7 +221,9 @@ public class VespaMetricSet {
// resource usage
metrics.add(new Metric("content.proton.resource_usage.disk.average"));
+ metrics.add(new Metric("content.proton.resource_usage.disk_utilization.average"));
metrics.add(new Metric("content.proton.resource_usage.memory.average"));
+ metrics.add(new Metric("content.proton.resource_usage.memory_utilization.average"));
metrics.add(new Metric("content.proton.resource_usage.memory_mappings.max"));
metrics.add(new Metric("content.proton.resource_usage.open_file_descriptors.max"));
metrics.add(new Metric("content.proton.documentdb.attribute.resource_usage.enum_store.average"));
@@ -230,6 +233,7 @@ public class VespaMetricSet {
// transaction log
metrics.add(new Metric("content.proton.transactionlog.entries.average"));
metrics.add(new Metric("content.proton.transactionlog.disk_usage.average"));
+ metrics.add(new Metric("content.proton.transactionlog.replay_time.last"));
// document store
metrics.add(new Metric("content.proton.documentdb.ready.document_store.disk_usage.average"));
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
index a3a1a0fbebc..24e56472688 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
@@ -37,16 +37,19 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu
private final ApplicationType applicationType;
private final List<ConfigServerSpec> configServerSpecs;
private final FileRegistry fileRegistry;
+ private final boolean disableFiledistributor;
protected final boolean multitenant;
- public DomAdminBuilderBase(ApplicationType applicationType, FileRegistry fileRegistry, boolean multitenant, List<ConfigServerSpec> configServerSpecs) {
+ DomAdminBuilderBase(ApplicationType applicationType, FileRegistry fileRegistry, boolean multitenant,
+ List<ConfigServerSpec> configServerSpecs, boolean disableFiledistributor) {
this.applicationType = applicationType;
this.fileRegistry = fileRegistry;
this.multitenant = multitenant;
this.configServerSpecs = configServerSpecs;
+ this.disableFiledistributor = disableFiledistributor;
}
- protected List<Configserver> getConfigServersFromSpec(AbstractConfigProducer parent) {
+ List<Configserver> getConfigServersFromSpec(AbstractConfigProducer parent) {
List<Configserver> configservers = new ArrayList<>();
for (ConfigServerSpec spec : configServerSpecs) {
HostSystem hostSystem = parent.getHostSystem();
@@ -64,23 +67,26 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu
@Override
protected Admin doBuild(AbstractConfigProducer parent, Element adminElement) {
Monitoring monitoring = getMonitoring(getChildWithFallback(adminElement, "monitoring", "yamas"));
-
Metrics metrics = new MetricsBuilder(applicationType, predefinedMetricSets)
.buildMetrics(XML.getChild(adminElement, "metrics"));
Map<String, MetricsConsumer> legacyMetricsConsumers = DomMetricBuilderHelper
.buildMetricsConsumers(XML.getChild(adminElement, "metric-consumers"));
+ FileDistributionConfigProducer fileDistributionConfigProducer = getFileDistributionConfigProducer(parent, adminElement);
- Admin admin = new Admin(parent, monitoring, metrics, legacyMetricsConsumers, multitenant);
-
+ Admin admin = new Admin(parent, monitoring, metrics, legacyMetricsConsumers, multitenant, fileDistributionConfigProducer);
doBuildAdmin(admin, adminElement);
-
new ModelConfigProvider(admin);
- FileDistributionOptions fileDistributionOptions = new DomFileDistributionOptionsBuilder().build(XML.getChild(adminElement, "filedistribution"));
- admin.setFileDistribution(new FileDistributionConfigProducer.Builder(fileDistributionOptions).build(parent, fileRegistry));
return admin;
}
-
+
+ private FileDistributionConfigProducer getFileDistributionConfigProducer(AbstractConfigProducer parent, Element adminElement) {
+ FileDistributionOptions fileDistributionOptions = FileDistributionOptions.defaultOptions();
+ fileDistributionOptions.disableFiledistributor(disableFiledistributor);
+ fileDistributionOptions = new DomFileDistributionOptionsBuilder(fileDistributionOptions).build(XML.getChild(adminElement, "filedistribution"));
+ return new FileDistributionConfigProducer.Builder(fileDistributionOptions).build(parent, fileRegistry);
+ }
+
private Element getChildWithFallback(Element parent, String childName, String alternativeChildName) {
Element child = XML.getChild(parent, childName);
if (child != null) return child;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
index dd1d4e36255..d966f3b49f6 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
@@ -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.model.builder.xml.dom;
+import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.producer.AbstractConfigProducer;
@@ -18,7 +19,6 @@ import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder.DomConfigProducerBu
import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.xml.ContainerModelBuilder;
-import com.yahoo.config.application.api.FileRegistry;
import org.w3c.dom.Element;
import java.util.List;
@@ -37,8 +37,9 @@ public class DomAdminV2Builder extends DomAdminBuilderBase {
public DomAdminV2Builder(ConfigModelContext.ApplicationType applicationType,
FileRegistry fileRegistry,
boolean multitenant,
- List<ConfigServerSpec> configServerSpecs) {
- super(applicationType, fileRegistry, multitenant, configServerSpecs);
+ List<ConfigServerSpec> configServerSpecs,
+ boolean disableFiledistributor) {
+ super(applicationType, fileRegistry, multitenant, configServerSpecs, disableFiledistributor);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
index f33c86134cb..1f7c1ba676c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
@@ -31,8 +31,10 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
private final Collection<ContainerModel> containerModels;
private final ConfigModelContext context;
- public DomAdminV4Builder(ConfigModelContext context, boolean multitenant, List<ConfigServerSpec> configServerSpecs, Collection<ContainerModel> containerModels) {
- super(context.getApplicationType(), context.getDeployState().getFileRegistry(), multitenant, configServerSpecs);
+ public DomAdminV4Builder(ConfigModelContext context, boolean multitenant, List<ConfigServerSpec> configServerSpecs,
+ Collection<ContainerModel> containerModels, boolean disableFiledistributor) {
+ super(context.getApplicationType(), context.getDeployState().getFileRegistry(), multitenant,
+ configServerSpecs, disableFiledistributor);
this.containerModels = containerModels;
this.context = context;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java
index 8a5d6846a64..9cbdd00899b 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java
@@ -15,6 +15,11 @@ import java.util.Optional;
* @author hmusum
*/
public class DomFileDistributionOptionsBuilder {
+ private final FileDistributionOptions fileDistributionOptions;
+
+ public DomFileDistributionOptionsBuilder(FileDistributionOptions fileDistributionOptions) {
+ this.fileDistributionOptions = fileDistributionOptions;
+ }
private static void throwExceptionForElementInFileDistribution(String subElement, String reason) {
throw new RuntimeException("In element '" + subElement + "' contained in 'filedistribution': " + reason);
@@ -34,15 +39,15 @@ public class DomFileDistributionOptionsBuilder {
}
public FileDistributionOptions build(Element fileDistributionElement) {
- FileDistributionOptions options = FileDistributionOptions.defaultOptions();
if (fileDistributionElement != null) {
- getAmount("uploadbitrate", fileDistributionElement).ifPresent(options::uploadBitRate);
- getAmount("downloadbitrate", fileDistributionElement).ifPresent(options::downloadBitRate);
+ getAmount("uploadbitrate", fileDistributionElement).ifPresent(fileDistributionOptions::uploadBitRate);
+ getAmount("downloadbitrate", fileDistributionElement).ifPresent(fileDistributionOptions::downloadBitRate);
Element disable = XML.getChild(fileDistributionElement, "disabled");
+ if (disable == null) disable = XML.getChild(fileDistributionElement, "disableFiledistributor");
if (disable != null) {
- options.disabled(Boolean.valueOf(XML.getValue(disable)));
+ fileDistributionOptions.disableFiledistributor(Boolean.valueOf(XML.getValue(disable)));
}
}
- return options;
+ return fileDistributionOptions;
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java
index 4969132ccfb..f4287cf982e 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java
@@ -19,7 +19,6 @@ public class VespaSpoolerService extends AbstractService implements SpoolerConfi
public VespaSpoolerService(AbstractConfigProducer parent, int index, VespaSpooler spooler) {
super(parent, "spooler." + index);
this.spooler = spooler;
- monitorService("spooler");
}
public int getPortCount() {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
index 8991bfa6215..5925ec978bb 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
@@ -165,7 +165,6 @@ public class Container extends AbstractService implements
}
tagServers();
- monitorService();
}
private void tagServers() {
@@ -331,7 +330,7 @@ public class Container extends AbstractService implements
FileDistributionConfigProducer fileDistribution = getRoot().getFileDistributionConfigProducer();
if (fileDistribution != null) {
- builder.configid(fileDistribution.getFileDistributorService(getHost()).getConfigId());
+ builder.configid(fileDistribution.getConfigProducer(getHost()).getConfigId());
}
return builder;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
index 62828b314d0..bce78017bdd 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
@@ -144,6 +144,9 @@ public class ConfigserverCluster extends AbstractConfigProducer
if (options.loadBalancerAddress().isPresent()) {
builder.loadBalancerAddress(options.loadBalancerAddress().get());
}
+ if (options.disableFiledistributor().isPresent()) {
+ builder.disableFiledistributor(options.disableFiledistributor().get());
+ }
}
private String[] getConfigModelPluginDirs() {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
index aeb86ae9d59..866bae6666a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.model.container.configserver.option;
import java.util.Optional;
/**
- * @author tonytv
+ * @author Tony Vaagenes
*/
public interface CloudConfigOptions {
@@ -44,4 +44,5 @@ public interface CloudConfigOptions {
Optional<String> dockerRegistry();
Optional<String> dockerVespaBaseImage();
Optional<String> loadBalancerAddress();
+ Optional<Boolean> disableFiledistributor();
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
index 22c42056b3f..33f5edded3c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
@@ -5,6 +5,7 @@ import com.yahoo.component.ComponentId;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.container.component.SimpleComponent;
@@ -24,10 +25,14 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig
private final Element legacyConfig;
public ConnectorFactory(String name, int listenPort) {
- this(name, listenPort, null, null);
+ this(name, listenPort, null, null, null);
}
- public ConnectorFactory(String name, int listenPort, Element legacyConfig, Element sslKeystoreConfigurator) {
+ public ConnectorFactory(String name,
+ int listenPort,
+ Element legacyConfig,
+ Element sslKeystoreConfigurator,
+ Element sslTruststoreConfigurator) {
super(new ComponentModel(
new BundleInstantiationSpecification(new ComponentId(name),
fromString("com.yahoo.jdisc.http.server.jetty.ConnectorFactory"),
@@ -35,9 +40,8 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig
this.name = name;
this.listenPort = listenPort;
this.legacyConfig = legacyConfig;
- SimpleComponent sslKeyStoreConfigurator = getSslKeyStoreConfigurator(name, sslKeystoreConfigurator);
- addChild(sslKeyStoreConfigurator);
- inject(sslKeyStoreConfigurator);
+ addSslKeyStoreConfigurator(name, sslKeystoreConfigurator);
+ addSslTrustStoreConfigurator(name, sslTruststoreConfigurator);
}
@Override
@@ -149,16 +153,30 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig
}
}
- private static SimpleComponent getSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) {
- String idSpec = "ssl-keystore-configurator@" + name;
- if (sslKeystoreConfigurator != null) {
- String className = sslKeystoreConfigurator.getAttribute("class");
- String bundleName = sslKeystoreConfigurator.getAttribute("bundle");
- return new SimpleComponent(new ComponentModel(idSpec, className, bundleName));
+ private void addSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) {
+ addSslConfigurator("ssl-keystore-configurator@" + name,
+ DefaultSslKeyStoreConfigurator.class,
+ sslKeystoreConfigurator);
+ }
+
+ private void addSslTrustStoreConfigurator(String name, Element sslKeystoreConfigurator) {
+ addSslConfigurator("ssl-truststore-configurator@" + name,
+ DefaultSslTrustStoreConfigurator.class,
+ sslKeystoreConfigurator);
+ }
+
+ private void addSslConfigurator(String idSpec, Class<?> defaultImplementation, Element configuratorElement) {
+ SimpleComponent configuratorComponent;
+ if (configuratorElement != null) {
+ String className = configuratorElement.getAttribute("class");
+ String bundleName = configuratorElement.getAttribute("bundle");
+ configuratorComponent = new SimpleComponent(new ComponentModel(idSpec, className, bundleName));
} else {
- return new SimpleComponent(
- new ComponentModel(idSpec, DefaultSslKeyStoreConfigurator.class.getName(), "jdisc_http_service"));
+ configuratorComponent =
+ new SimpleComponent(new ComponentModel(idSpec, defaultImplementation.getName(), "jdisc_http_service"));
}
+ addChild(configuratorComponent);
+ inject(configuratorComponent);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
index f2012a609a7..f88c091cd37 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
@@ -12,8 +12,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
- * @since 5.21.0
+ * @author Einar M R Rosenvinge
*/
public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuilder<ConnectorFactory> {
private static final Logger log = Logger.getLogger(JettyConnectorBuilder.class.getName());
@@ -34,7 +33,8 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil
}
}
Element sslKeystoreConfigurator = XML.getChild(serverSpec, "ssl-keystore-configurator");
- return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator);
+ Element sslTruststoreConfigurator = XML.getChild(serverSpec, "ssl-truststore-configurator");
+ return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator, sslTruststoreConfigurator);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java
index 7ef54360f00..4257e47218e 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java
@@ -50,8 +50,6 @@ public abstract class ContentNode extends AbstractService
portsMeta.on(0).tag("messaging");
portsMeta.on(1).tag("rpc").tag("status");
portsMeta.on(2).tag("http").tag("status").tag("state");
-
- monitorService();
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
index 81aca977400..0405f96cd89 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
@@ -1,8 +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.model.content;
-import com.yahoo.documentmodel.NewDocumentType;
-import com.yahoo.vespa.config.content.core.BucketspacesConfig;
import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig;
import com.yahoo.vespa.config.content.core.StorServerConfig;
import com.yahoo.document.select.DocumentSelector;
@@ -16,15 +14,13 @@ import org.w3c.dom.Element;
import java.util.logging.Logger;
-
/**
* Generates distributor-specific configuration.
*/
public class DistributorCluster extends AbstractConfigProducer<Distributor> implements
StorDistributormanagerConfig.Producer,
StorServerConfig.Producer,
- MetricsmanagerConfig.Producer,
- BucketspacesConfig.Producer {
+ MetricsmanagerConfig.Producer {
public static final Logger log = Logger.getLogger(DistributorCluster.class.getPackage().toString());
@@ -151,20 +147,6 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl
builder.is_distributor(true);
}
- private static final String DEFAULT_BUCKET_SPACE = "default";
- private static final String GLOBAL_BUCKET_SPACE = "global";
-
- @Override
- public void getConfig(BucketspacesConfig.Builder builder) {
- for (NewDocumentType docType : parent.getDocumentDefinitions().values()) {
- BucketspacesConfig.Documenttype.Builder docTypeBuilder = new BucketspacesConfig.Documenttype.Builder();
- docTypeBuilder.name(docType.getName());
- String bucketSpace = (parent.isGloballyDistributed(docType) ? GLOBAL_BUCKET_SPACE : DEFAULT_BUCKET_SPACE);
- docTypeBuilder.bucketspace(bucketSpace);
- builder.documenttype(docTypeBuilder);
- }
- }
-
public String getClusterName() {
return parent.getName();
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
index fc267133c9f..db849d0ae50 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.config.content.MessagetyperouteselectorpolicyConfig;
import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.config.content.StorDistributionConfig;
+import com.yahoo.vespa.config.content.core.BucketspacesConfig;
import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig;
import com.yahoo.documentmodel.NewDocumentType;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
@@ -57,7 +58,8 @@ public class ContentCluster extends AbstractConfigProducer implements StorDistri
StorDistributormanagerConfig.Producer,
FleetcontrollerConfig.Producer,
MetricsmanagerConfig.Producer,
- MessagetyperouteselectorpolicyConfig.Producer {
+ MessagetyperouteselectorpolicyConfig.Producer,
+ BucketspacesConfig.Producer {
// TODO: Make private
private String documentSelection;
@@ -698,4 +700,18 @@ public class ContentCluster extends AbstractConfigProducer implements StorDistri
}
}
+
+ private static final String DEFAULT_BUCKET_SPACE = "default";
+ private static final String GLOBAL_BUCKET_SPACE = "global";
+
+ @Override
+ public void getConfig(BucketspacesConfig.Builder builder) {
+ for (NewDocumentType docType : getDocumentDefinitions().values()) {
+ BucketspacesConfig.Documenttype.Builder docTypeBuilder = new BucketspacesConfig.Documenttype.Builder();
+ docTypeBuilder.name(docType.getName());
+ String bucketSpace = (isGloballyDistributed(docType) ? GLOBAL_BUCKET_SPACE : DEFAULT_BUCKET_SPACE);
+ docTypeBuilder.bucketspace(bucketSpace);
+ builder.documenttype(docTypeBuilder);
+ }
+ }
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java
new file mode 100644
index 00000000000..1b1e66594d1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java
@@ -0,0 +1,42 @@
+// 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.filedistribution;
+
+import com.yahoo.cloud.config.filedistribution.FiledistributorConfig;
+import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig;
+import com.yahoo.cloud.config.filedistribution.FilereferencesConfig;
+import com.yahoo.config.model.producer.AbstractConfigProducer;
+
+/**
+ * @author hmusum
+ * <p>
+ * Dummy file distribution config producer, needed for serving file distribution config when there is no FiledistributorService.
+ */
+public class DummyFileDistributionConfigProducer extends AbstractConfigProducer implements
+ FiledistributorConfig.Producer,
+ FiledistributorrpcConfig.Producer,
+ FilereferencesConfig.Producer {
+
+ private final FileDistributionConfigProvider configProvider;
+
+ public DummyFileDistributionConfigProducer(AbstractConfigProducer parent,
+ String hostname,
+ FileDistributionConfigProvider configProvider) {
+ super(parent, hostname);
+ this.configProvider = configProvider;
+ }
+
+ @Override
+ public void getConfig(FiledistributorConfig.Builder builder) {
+ configProvider.getConfig(builder);
+ }
+
+ @Override
+ public void getConfig(FiledistributorrpcConfig.Builder builder) {
+ configProvider.getConfig(builder);
+ }
+
+ @Override
+ public void getConfig(FilereferencesConfig.Builder builder) {
+ configProvider.getConfig(builder);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java
index 9e5e3074c5e..c863b180b21 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java
@@ -10,11 +10,13 @@ import java.util.IdentityHashMap;
import java.util.Map;
/**
- * @author tonytv
+ * @author hmusum
+ * <p>
+ * File distribution config producer, delegates getting config to {@link DummyFileDistributionConfigProducer} (one per host)
*/
public class FileDistributionConfigProducer extends AbstractConfigProducer {
- private final Map<Host, FileDistributorService> fileDistributorServices = new IdentityHashMap<>();
+ private final Map<Host, AbstractConfigProducer> fileDistributionConfigProducers = new IdentityHashMap<>();
private final FileDistributor fileDistributor;
private final FileDistributionOptions options;
@@ -24,14 +26,6 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer {
this.options = options;
}
- public FileDistributorService getFileDistributorService(Host host) {
- FileDistributorService service = fileDistributorServices.get(host);
- if (service == null) {
- throw new IllegalStateException("No file distribution service for host " + host);
- }
- return service;
- }
-
public FileDistributor getFileDistributor() {
return fileDistributor;
}
@@ -40,8 +34,8 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer {
return options;
}
- public void addFileDistributionService(Host host, FileDistributorService fds) {
- fileDistributorServices.put(host, fds);
+ public void addFileDistributionConfigProducer(Host host, AbstractConfigProducer fileDistributionConfigProducer) {
+ fileDistributionConfigProducers.put(host, fileDistributionConfigProducer);
}
public static class Builder {
@@ -58,4 +52,8 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer {
}
}
+ public AbstractConfigProducer getConfigProducer(Host host) {
+ return fileDistributionConfigProducers.get(host);
+ }
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java
new file mode 100644
index 00000000000..a78eb1f74f9
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java
@@ -0,0 +1,59 @@
+// 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.filedistribution;
+
+import com.yahoo.cloud.config.filedistribution.FiledistributorConfig;
+import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig;
+import com.yahoo.cloud.config.filedistribution.FilereferencesConfig;
+import com.yahoo.config.FileReference;
+import com.yahoo.config.model.api.FileDistribution;
+import com.yahoo.vespa.model.ConfigProxy;
+import com.yahoo.vespa.model.Host;
+import com.yahoo.vespa.model.admin.FileDistributionOptions;
+
+import java.util.Collection;
+
+public class FileDistributionConfigProvider {
+
+ private final FileDistributor fileDistributor;
+ private final FileDistributionOptions fileDistributionOptions;
+ private final boolean sendAllFiles;
+ private final Host host;
+
+ public FileDistributionConfigProvider(FileDistributor fileDistributor,
+ FileDistributionOptions fileDistributionOptions,
+ boolean sendAllFiles,
+ Host host) {
+ this.fileDistributor = fileDistributor;
+ this.fileDistributionOptions = fileDistributionOptions;
+ this.sendAllFiles = sendAllFiles;
+ this.host = host;
+ }
+
+ public void getConfig(FiledistributorConfig.Builder builder) {
+ fileDistributionOptions.getConfig(builder);
+ builder.torrentport(FileDistributorService.BASEPORT + 1);
+ builder.stateport(FileDistributorService.BASEPORT + 2);
+ builder.hostname(host.getHostname());
+ builder.filedbpath(FileDistribution.getDefaultFileDBPath().toString());
+ }
+
+ public void getConfig(FiledistributorrpcConfig.Builder builder) {
+ // If disabled config proxy should act as file distributor, so use config proxy port
+ int port = (fileDistributionOptions.disableFiledistributor()) ? ConfigProxy.BASEPORT : FileDistributorService.BASEPORT;
+ builder.connectionspec("tcp/" + host.getHostname() + ":" + port);
+ }
+
+ public void getConfig(FilereferencesConfig.Builder builder) {
+ for (FileReference reference : getFileReferences()) {
+ builder.filereferences(reference.value());
+ }
+ }
+
+ private Collection<FileReference> getFileReferences() {
+ if (sendAllFiles) {
+ return fileDistributor.allFilesToSend();
+ } else {
+ return fileDistributor.filesToSendToHost(host);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java
index 443c88355ed..f3aad88a446 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java
@@ -9,11 +9,10 @@ import com.yahoo.vespa.model.Host;
import java.util.*;
import java.util.stream.Collectors;
-
/**
* Responsible for directing distribution of files to hosts.
*
- * @author tonytv
+ * @author Tony Vaagenes
*/
public class FileDistributor {
@@ -97,6 +96,7 @@ public class FileDistributor {
for (Host host : getTargetHosts()) {
if ( ! host.getHostname().equals(fileSourceHost)) {
dbHandler.sendDeployedFiles(host.getHostname(), filesToSendToHost(host));
+ dbHandler.startDownload(host.getHostname(), filesToSendToHost(host));
}
}
dbHandler.sendDeployedFiles(fileSourceHost, allFilesToSend());
@@ -108,5 +108,5 @@ public class FileDistributor {
public void reloadDeployFileDistributor(FileDistribution dbHandler) {
dbHandler.reloadDeployFileDistributor();
}
-
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java
index dd9e057e2fa..986334125d2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java
@@ -4,60 +4,36 @@ package com.yahoo.vespa.model.filedistribution;
import com.yahoo.cloud.config.filedistribution.FiledistributorConfig;
import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig;
import com.yahoo.cloud.config.filedistribution.FilereferencesConfig;
-import com.yahoo.config.FileReference;
-
-import com.yahoo.config.model.api.FileDistribution;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.vespa.model.AbstractService;
-import com.yahoo.vespa.model.admin.FileDistributionOptions;
-
-import java.util.Collection;
/**
- * @author tonytv
+ * @author Tony Vaagenes
+ *
+ * Config is produced by {@link FileDistributionConfigProvider}
*/
public class FileDistributorService extends AbstractService implements
FiledistributorConfig.Producer,
FiledistributorrpcConfig.Producer,
FilereferencesConfig.Producer {
- private final static int BASEPORT = 19092;
- private final FileDistributor fileDistributor;
- private final FileDistributionOptions fileDistributionOptions;
- private final boolean sendAllFiles;
+ final static int BASEPORT = 19092;
- private Collection<FileReference> getFileReferences() {
- if (sendAllFiles) {
- return fileDistributor.allFilesToSend();
- } else {
- return fileDistributor.filesToSendToHost(getHost());
- }
- }
+ private final FileDistributionConfigProvider configProvider;
- public FileDistributorService(AbstractConfigProducer parent,
- String name,
- FileDistributor fileDistributor,
- FileDistributionOptions fileDistributionOptions,
- boolean sendAllFiles) {
- super(parent, name);
+ public FileDistributorService(AbstractConfigProducer parent, String hostname, FileDistributionConfigProvider configProvider) {
+ super(parent, hostname);
+ this.configProvider = configProvider;
portsMeta.on(0).tag("rpc");
portsMeta.on(1).tag("torrent");
portsMeta.on(2).tag("http").tag("state");
setProp("clustertype", "filedistribution");
setProp("clustername", "admin");
-
- this.fileDistributor = fileDistributor;
- this.fileDistributionOptions = fileDistributionOptions;
- this.sendAllFiles = sendAllFiles;
- monitorService();
}
@Override
public String getStartupCommand() {
- // If disabled config proxy should act as file distributor, so don't start this service
- return (fileDistributionOptions.disabled())
- ? null
- : "exec $ROOT/sbin/vespa-filedistributor" + " --configid " + getConfigId();
+ return "exec $ROOT/sbin/vespa-filedistributor" + " --configid " + getConfigId();
}
@Override
@@ -70,6 +46,7 @@ public class FileDistributorService extends AbstractService implements
return true;
}
+ @Override
public int getPortCount() {
return 3;
}
@@ -81,24 +58,16 @@ public class FileDistributorService extends AbstractService implements
@Override
public void getConfig(FiledistributorConfig.Builder builder) {
- fileDistributionOptions.getConfig(builder);
- builder.torrentport(getRelativePort(1));
- builder.stateport(getRelativePort(2));
- builder.hostname(getHostName());
- builder.filedbpath(FileDistribution.getDefaultFileDBPath().toString());
+ configProvider.getConfig(builder);
}
@Override
public void getConfig(FiledistributorrpcConfig.Builder builder) {
- // If disabled config proxy should act as file distributor, so use config proxy port
- int port = (fileDistributionOptions.disabled()) ? 19090 : getRelativePort(0);
- builder.connectionspec("tcp/" + getHostName() + ":" + port);
+ configProvider.getConfig(builder);
}
@Override
public void getConfig(FilereferencesConfig.Builder builder) {
- for (FileReference reference : getFileReferences()) {
- builder.filereferences(reference.value());
- }
+ configProvider.getConfig(builder);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java
index c7610e784ec..87146447dd1 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java
@@ -46,7 +46,6 @@ public class Dispatch extends AbstractService implements SearchInterface,
setProp("clustertype", "search")
.setProp("clustername", dispatchGroup.getClusterName())
.setProp("index", nodeSpec.groupIndex());
- monitorService();
}
public static Dispatch createTld(DispatchGroup dispatchGroup, AbstractConfigProducer parent, int rowId) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java
index 05b0b382814..8420a1f4b91 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java
@@ -19,6 +19,7 @@ import com.yahoo.vespa.model.admin.monitoring.Monitoring;
import com.yahoo.vespa.model.application.validation.RestartConfigs;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.content.ContentNode;
+import com.yahoo.vespa.model.filedistribution.DummyFileDistributionConfigProducer;
import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer;
import com.yahoo.vespa.model.filedistribution.FileDistributorService;
import org.w3c.dom.Element;
@@ -112,7 +113,6 @@ public class SearchNode extends AbstractService implements
portsMeta.on(UNUSED_3).tag("unused");
portsMeta.on(HEALTH_PORT).tag("http").tag("json").tag("health").tag("state");
// Properties are set in DomSearchBuilder
- monitorService();
this.tuning = tuning;
}
@@ -229,10 +229,12 @@ public class SearchNode extends AbstractService implements
public void getConfig(FiledistributorrpcConfig.Builder builder) {
FileDistributionConfigProducer fileDistribution = getRoot().getFileDistributionConfigProducer();
if (fileDistribution != null) {
- FileDistributorService fds = fileDistribution.getFileDistributorService(getHost());
- if (fds != null) {
- fds.getConfig(builder);
- }
+ AbstractConfigProducer configProducer = fileDistribution.getConfigProducer(getHost());
+ // TODO: Hack, will be fixed when FileDistributorService is gone
+ if (configProducer instanceof DummyFileDistributionConfigProducer)
+ ((DummyFileDistributionConfigProducer) configProducer).getConfig(builder);
+ else
+ ((FileDistributorService) configProducer).getConfig(builder);
}
}
diff --git a/config-model/src/main/resources/schema/admin.rnc b/config-model/src/main/resources/schema/admin.rnc
index b7ec252c4d7..486f1e367e9 100644
--- a/config-model/src/main/resources/schema/admin.rnc
+++ b/config-model/src/main/resources/schema/admin.rnc
@@ -81,7 +81,7 @@ LogServer = element logserver {
FileDistribution = element filedistribution {
element uploadbitrate { xsd:string { pattern = "\d+(\.\d*)?\s*[kmgKMG]?" } }? &
element downloadbitrate { xsd:string { pattern = "\d+(\.\d*)?\s*[kmgKMG]?" } }? &
- element disabled { xsd:boolean }? # Nov. 2017: Temporary, should not be documented
+ element disableFiledistributor { xsd:boolean }? # Nov. 2017: Temporary, should not be documented
}
Metrics = element metrics {
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 6a90bef7bb2..95ac198adc4 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -62,6 +62,7 @@ HttpServer = element server {
attribute port { xsd:nonNegativeInteger } &
ComponentId &
element ssl-keystore-configurator { BundleSpec }? & # FOR INTERNAL USE ONLY - SUBJECT TO CHANGE
+ element ssl-truststore-configurator { BundleSpec }? & # FOR INTERNAL USE ONLY - SUBJECT TO CHANGE
GenericConfig*
}
diff --git a/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg b/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg
deleted file mode 100644
index 07538a1dce2..00000000000
--- a/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-builtin[StemmingSearcher].enabled false
-builtin[NoRankingSearcher].enabled false
-tag.bold.open "^_"
-tag.bold.close "^_"
-tag.separator " ... "
diff --git a/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml b/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml
deleted file mode 100644
index 9a13d936e46..00000000000
--- a/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
-<services version="1.0">
-
-
- <admin version="2.0">
- <adminserver hostalias="node1" />
- </admin>
-
-</services>
diff --git a/config-model/src/test/derived/importedfields/attributes.cfg b/config-model/src/test/derived/importedfields/attributes.cfg
index 62fe0052bc3..f51bbc012b9 100644
--- a/config-model/src/test/derived/importedfields/attributes.cfg
+++ b/config-model/src/test/derived/importedfields/attributes.cfg
@@ -138,3 +138,23 @@ attribute[].upperbound 9223372036854775807
attribute[].densepostinglistthreshold 0.4
attribute[].tensortype ""
attribute[].imported true
+attribute[].name "my_predicate_field"
+attribute[].datatype PREDICATE
+attribute[].collectiontype SINGLE
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge 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 5
+attribute[].upperbound 300
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported true
diff --git a/config-model/src/test/derived/importedfields/child.sd b/config-model/src/test/derived/importedfields/child.sd
index 703451c1293..07fa7d15719 100644
--- a/config-model/src/test/derived/importedfields/child.sd
+++ b/config-model/src/test/derived/importedfields/child.sd
@@ -9,6 +9,7 @@ search child {
import field b_ref.string_field as my_string_field {}
import field a_ref.int_array_field as my_int_array_field {}
import field a_ref.int_wset_field as my_int_wset_field {}
+ import field a_ref.predicate_field as my_predicate_field {}
fieldset myfieldset {
fields: my_int_field, my_string_field
diff --git a/config-model/src/test/derived/importedfields/imported-fields.cfg b/config-model/src/test/derived/importedfields/imported-fields.cfg
index 3f2a083bdc2..59413ca5eb7 100644
--- a/config-model/src/test/derived/importedfields/imported-fields.cfg
+++ b/config-model/src/test/derived/importedfields/imported-fields.cfg
@@ -18,3 +18,8 @@ attribute[].referencefield "a_ref"
attribute[].targetfield "int_wset_field"
attribute[].datatype NONE
attribute[].collectiontype SINGLE
+attribute[].name "my_predicate_field"
+attribute[].referencefield "a_ref"
+attribute[].targetfield "predicate_field"
+attribute[].datatype NONE
+attribute[].collectiontype SINGLE
diff --git a/config-model/src/test/derived/importedfields/index-info.cfg b/config-model/src/test/derived/importedfields/index-info.cfg
index 7cb53c480ce..adba0036409 100644
--- a/config-model/src/test/derived/importedfields/index-info.cfg
+++ b/config-model/src/test/derived/importedfields/index-info.cfg
@@ -49,6 +49,12 @@ indexinfo[].command[].indexname "my_int_wset_field"
indexinfo[].command[].command "multivalue"
indexinfo[].command[].indexname "my_int_wset_field"
indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_predicate_field"
+indexinfo[].command[].command "predicate-bounds [5..300]"
+indexinfo[].command[].indexname "my_predicate_field"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_predicate_field"
+indexinfo[].command[].command "attribute"
indexinfo[].command[].indexname "myfieldset"
indexinfo[].command[].command "attribute"
indexinfo[].command[].indexname "myfieldset"
diff --git a/config-model/src/test/derived/importedfields/parent_a.sd b/config-model/src/test/derived/importedfields/parent_a.sd
index eea12375daf..edc81df5609 100644
--- a/config-model/src/test/derived/importedfields/parent_a.sd
+++ b/config-model/src/test/derived/importedfields/parent_a.sd
@@ -10,5 +10,13 @@ search parent_a {
field int_wset_field type weightedset<int> {
indexing: attribute
}
+ field predicate_field type predicate {
+ indexing: attribute
+ index {
+ arity: 8
+ lower-bound: 5
+ upper-bound: 300
+ }
+ }
}
}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java
new file mode 100644
index 00000000000..4e1f8f1edd7
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java
@@ -0,0 +1,39 @@
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.document.DataType;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * @author geirst
+ */
+public class ImportedFieldsInSummaryValidatorTestCase {
+
+ @Rule
+ public final ExpectedException exceptionRule = ExpectedException.none();
+
+ @Test
+ public void validator_fails_if_imported_predicate_field_is_used_in_document_summary() {
+ exceptionRule.expect(IllegalArgumentException.class);
+ exceptionRule.expectMessage("For search 'child', document summary 'my_summary', " +
+ "imported summary field 'my_predicate_field': Is of type predicate. Not supported in document summaries");
+ new SearchModel()
+ .addImportedField("my_predicate_field", "ref", "predicate_field")
+ .addSummaryField("my_predicate_field", DataType.PREDICATE)
+ .resolve();
+ }
+
+ private static class SearchModel extends ImportedFieldsResolverTestCase.SearchModel {
+
+ public SearchModel() {
+ super();
+ }
+
+ public void resolve() {
+ super.resolve();
+ new ImportedFieldsInSummayValidator(childSearch, null, null, null).process();
+ }
+ }
+}
+
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java
index 5d270bc085a..9f7c7458738 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java
@@ -18,6 +18,8 @@ import com.yahoo.searchdefinition.document.SDField;
import com.yahoo.searchdefinition.document.TemporaryImportedField;
import com.yahoo.searchdefinition.document.TemporarySDField;
import com.yahoo.tensor.TensorType;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -107,7 +109,7 @@ public class ImportedFieldsResolverTestCase {
.resolve();
}
- private static class SearchModel {
+ static class SearchModel {
private final ApplicationPackage app = MockApplicationPackage.createEmpty();
public final Search grandParentSearch;
@@ -124,6 +126,7 @@ public class ImportedFieldsResolverTestCase {
parentSearch.getDocument().addField(createField("attribute_and_index", DataType.INT, "{ attribute | index }"));
parentSearch.getDocument().addField(new TemporarySDField("not_attribute", DataType.INT));
parentSearch.getDocument().addField(createField("tensor_field", new TensorDataType(TensorType.fromSpec("tensor(x[])")), "{ attribute }"));
+ parentSearch.getDocument().addField(createField("predicate_field", DataType.PREDICATE, "{ attribute }"));
addRefField(parentSearch, grandParentSearch, "ref");
addImportedField(parentSearch, "ancient_field", "ref", "ancient_field");
@@ -163,6 +166,16 @@ public class ImportedFieldsResolverTestCase {
return this;
}
+ public SearchModel addSummaryField(String fieldName, DataType dataType) {
+ DocumentSummary summary = childSearch.getSummary("my_summary");
+ if (summary == null) {
+ summary = new DocumentSummary("my_summary");
+ childSearch.addSummary(summary);
+ }
+ summary.add(new SummaryField(fieldName, dataType));
+ return this;
+ }
+
public void resolve() {
resolve(grandParentSearch);
resolve(parentSearch);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java
index 946624a1cdb..fed9000f72b 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java
@@ -8,7 +8,6 @@ import com.yahoo.cloud.config.SentinelConfig;
import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.deploy.DeployProperties;
import com.yahoo.config.model.deploy.DeployState;
-import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.model.test.TestDriver;
import com.yahoo.config.model.test.TestRoot;
import com.yahoo.config.provision.ApplicationId;
@@ -24,11 +23,9 @@ import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.component.StatisticsComponent;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.Set;
-import java.util.stream.IntStream;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -288,7 +285,7 @@ public class AdminTestCase {
" <admin version='2.0'>" +
" <adminserver hostalias='node0' />" +
" <filedistribution>" +
- " <disabled>true</disabled>" +
+ " <disableFiledistributor>true</disableFiledistributor>" +
" </filedistribution>" +
" </admin>" +
"</services>";
@@ -307,4 +304,24 @@ public class AdminTestCase {
assertThat(sentinelConfig.service(2).name(), is("logd"));
// No filedistributor service
}
+
+ @Test
+ public void testDisableFileDistributorForAllApps() {
+ DeployState state = new DeployState.Builder()
+ .disableFiledistributor(true)
+ .properties(
+ new DeployProperties.Builder().
+ zone(new Zone(Environment.dev, RegionName.from("baz"))).
+ applicationId(new ApplicationId.Builder().
+ tenant("quux").
+ applicationName("foo").instanceName("bim").build()).build()).build();
+ TestRoot root = new TestDriver().buildModel(state);
+ String localhost = HostName.getLocalhost();
+ SentinelConfig sentinelConfig = root.getConfig(SentinelConfig.class, "hosts/" + localhost);
+ assertThat(sentinelConfig.service().size(), is(3));
+ assertThat(sentinelConfig.service(0).name(), is("logserver"));
+ assertThat(sentinelConfig.service(1).name(), is("slobrok"));
+ assertThat(sentinelConfig.service(2).name(), is("logd"));
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java
index 6c1bf6c9748..446ceb686e8 100755
--- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java
@@ -207,7 +207,10 @@ public class DomAdminV2BuilderTest extends DomBuilderTest {
}
private Admin buildAdmin(Element xml, boolean multitenant, List<ConfigServerSpec> configServerSpecs) {
- final DomAdminV2Builder domAdminBuilder = new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, root.getDeployState().getFileRegistry(), multitenant, configServerSpecs);
+ final DomAdminV2Builder domAdminBuilder =
+ new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT,
+ root.getDeployState().getFileRegistry(), multitenant,
+ configServerSpecs, root.getDeployState().disableFiledistributor());
Admin admin = domAdminBuilder.build(root, xml);
admin.addPerHostServices(root.getHostSystem().getHosts(), new DeployProperties.Builder().build());
return admin;
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java
deleted file mode 100644
index e7df151a3c4..00000000000
--- a/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java
+++ /dev/null
@@ -1,65 +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.clients.test;
-
-import com.yahoo.container.ComponentsConfig;
-import com.yahoo.container.QrConfig;
-import com.yahoo.container.QrConfig.Builder;
-import com.yahoo.net.HostName;
-import com.yahoo.vespa.model.VespaModel;
-import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.junit.Assert.*;
-
-/**
- * @author Gunnar Gauslaa Bergem
- */
-public class Gateway20TestCase {
-
- private static String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model
-
- @Test
- public void testSimpleDocprocV3() throws Exception {
- VespaModel model = new VespaModelCreatorWithFilePkg("src/test/cfg/clients/simpleconfig.v2.docprocv3").create();
- QrConfig qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0"));
- assertEquals(qrConfig.rpc().enabled(), true);
- assertEquals("filedistribution/" + hostname, qrConfig.filedistributor().configid());
- assertEquals("container.container.0", qrConfig.discriminator());
-
- ComponentsConfig componentsConfig = new ComponentsConfig((ComponentsConfig.Builder) model.getConfig(new ComponentsConfig.Builder(), "container/container.0"));
- ArrayList<String> components = new ArrayList<>();
- for (ComponentsConfig.Components component : componentsConfig.components()) {
- components.add(component.id());
- }
- List<String> expectedComponents = Arrays.asList("com.yahoo.docproc.jdisc.DocumentProcessingHandler",
- "com.yahoo.feedhandler.VespaFeedHandler",
- "com.yahoo.feedhandler.VespaFeedHandlerCompatibility",
- "com.yahoo.feedhandler.VespaFeedHandlerGet",
- "com.yahoo.feedhandler.VespaFeedHandlerRemove",
- "com.yahoo.feedhandler.VespaFeedHandlerRemoveLocation",
- "com.yahoo.feedhandler.VespaFeedHandlerStatus",
- "com.yahoo.feedhandler.VespaFeedHandlerVisit",
- "com.yahoo.search.handler.SearchHandler",
- "com.yahoo.container.jdisc.state.StateHandler");
- assertTrue(components.containsAll(expectedComponents));
- }
-
- @Test
- public void testAdvanced() throws Exception {
- VespaModel model = new VespaModelCreatorWithFilePkg("src/test/cfg/clients/advancedconfig.v2").create();
-
- QrConfig qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0"));
- assertEquals(qrConfig.rpc().enabled(), true);
- assertEquals(qrConfig.filedistributor().configid(), "filedistribution/" + hostname);
- assertEquals("container.container.0", qrConfig.discriminator());
-
- qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0"));
- assertEquals(qrConfig.rpc().enabled(), true);
- assertEquals(qrConfig.filedistributor().configid(), "filedistribution/" + hostname);
- }
-
-}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java
index e2c8f2e2c52..1784fe0e974 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java
@@ -6,8 +6,7 @@ import com.yahoo.vespa.model.container.configserver.option.CloudConfigOptions;
import java.util.Optional;
/**
- * @author lulf
- * @since 5.
+ * @author Ulf Lilleengen
*/
public class TestOptions implements CloudConfigOptions {
private Optional<Integer> rpcPort = Optional.empty();
@@ -20,6 +19,7 @@ public class TestOptions implements CloudConfigOptions {
private Optional<Boolean> useVespaVersionInRequest = Optional.empty();
private Optional<Boolean> hostedVespa = Optional.empty();
private Optional<Integer> numParallelTenantLoaders = Optional.empty();
+ private Optional<Boolean> disableFiledistributor = Optional.empty();
@Override
public Optional<Integer> rpcPort() {
@@ -118,6 +118,9 @@ public class TestOptions implements CloudConfigOptions {
@Override
public Optional<String> loadBalancerAddress() { return Optional.empty(); }
+ @Override
+ public Optional<Boolean> disableFiledistributor() { return disableFiledistributor; }
+
public TestOptions numParallelTenantLoaders(int numLoaders) {
this.numParallelTenantLoaders = Optional.of(numLoaders);
return this;
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index d09211aea45..5e093bdb32a 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -16,6 +16,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.ComponentsConfig;
+import com.yahoo.container.QrConfig;
import com.yahoo.container.config.StatisticsRequestHandler;
import com.yahoo.container.core.ChainsConfig;
import com.yahoo.container.core.VipStatusConfig;
@@ -25,6 +26,7 @@ import com.yahoo.container.jdisc.JdiscBindingsConfig;
import com.yahoo.container.servlet.ServletConfigConfig;
import com.yahoo.container.usability.BindingsOverviewHandler;
import com.yahoo.jdisc.http.ServletPathsConfig;
+import com.yahoo.net.HostName;
import com.yahoo.prelude.cluster.QrMonitorConfig;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.VespaModel;
@@ -61,7 +63,6 @@ import static org.junit.Assert.fail;
/**
* @author gjoranv
- * @since 5.1.9
*/
public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
@@ -571,6 +572,41 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
assertEquals(ContainerModelBuilder.HOSTED_VESPA_STATUS_FILE, vipStatusConfig.statusfile());
}
+ @Test
+ public void qrconfig_is_produced() throws IOException, SAXException {
+ String servicesXml =
+ "<services>" +
+ "<admin version='3.0'>" +
+ " <nodes count='1'/>" +
+ "</admin>" +
+ "<jdisc id ='default' version='1.0'>" +
+ " <nodes>" +
+ " <node hostalias='node1' />" +
+ " </nodes>" +
+ "</jdisc>" +
+ "</services>";
+
+ ApplicationPackage applicationPackage = new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build();
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder()
+ .applicationPackage(applicationPackage)
+ .properties(new DeployProperties.Builder().build())
+ .build());
+
+ String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model
+
+ QrConfig config = model.getConfig(QrConfig.class, "default/container.0");
+ assertEquals("default.container.0", config.discriminator());
+ assertEquals(19102, config.rpc().port());
+ assertEquals("vespa/service/default/container.0", config.rpc().slobrokId());
+ assertEquals(true, config.rpc().enabled());
+ assertEquals("", config.rpc().host());
+ assertEquals(false, config.restartOnDeploy());
+ assertEquals(false, config.coveragereports());
+ assertEquals("filedistribution/" + hostname, config.filedistributor().configid());
+ }
+
private Element generateContainerElementWithRenderer(String rendererId) {
return DomBuilderTest.parse(
"<jdisc id='default' version='1.0'>",
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
index 1e24b055095..54c4aabf44c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
@@ -7,6 +7,7 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.jdisc.FilterBindingsProvider;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
@@ -16,7 +17,9 @@ import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -28,7 +31,6 @@ import static org.junit.Assert.assertThat;
/**
* @author einarmr
- * @since 5.15
*/
public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBase {
@@ -190,12 +192,13 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
}
@Test
- public void ssl_keystore_configurator_can_be_overriden() throws IOException, SAXException {
+ public void ssl_keystore_and_truststore_configurator_can_be_overriden() throws IOException, SAXException {
Element clusterElem = DomBuilderTest.parse(
"<jdisc id='default' version='1.0' jetty='true'>",
" <http>",
" <server port='9000' id='foo'>",
" <ssl-keystore-configurator class='com.yahoo.MySslKeyStoreConfigurator' bundle='mybundle'/>",
+ " <ssl-truststore-configurator class='com.yahoo.MySslTrustStoreConfigurator' bundle='mybundle'/>",
" </server>",
" <server port='9001' id='bar'/>",
" </http>",
@@ -204,27 +207,47 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
createModel(root, clusterElem);
ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default");
List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class);
-
{
ConnectorFactory firstConnector = connectorFactories.get(0);
- assertThat(firstConnector.getInjectedComponentIds(), hasItem("ssl-keystore-configurator@foo"));
- assertThat(firstConnector.getInjectedComponentIds().size(), equalTo(1));
- SimpleComponent sslKeystoreConfigurator = firstConnector.getChildrenByTypeRecursive(SimpleComponent.class).get(0);
- BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec;
- assertThat(spec.classId.toString(), is("com.yahoo.MySslKeyStoreConfigurator"));
- assertThat(spec.bundle.toString(), is("mybundle"));
+ assertConnectorHasInjectedComponents(firstConnector, "ssl-keystore-configurator@foo", "ssl-truststore-configurator@foo");
+ assertComponentHasClassNameAndBundle(getChildComponent(firstConnector, 0),
+ "com.yahoo.MySslKeyStoreConfigurator",
+ "mybundle");
+ assertComponentHasClassNameAndBundle(getChildComponent(firstConnector, 1),
+ "com.yahoo.MySslTrustStoreConfigurator",
+ "mybundle");
}
{
- ConnectorFactory secondFactory = connectorFactories.get(1);
- assertThat(secondFactory.getInjectedComponentIds(), hasItem("ssl-keystore-configurator@bar"));
- assertThat(secondFactory.getInjectedComponentIds().size(), equalTo(1));
- SimpleComponent sslKeystoreConfigurator = secondFactory.getChildrenByTypeRecursive(SimpleComponent.class).get(0);
- BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec;
- assertThat(spec.classId.toString(), is(DefaultSslKeyStoreConfigurator.class.getName()));
- assertThat(spec.bundle.toString(), is("jdisc_http_service"));
+ ConnectorFactory secondConnector = connectorFactories.get(1);
+ assertConnectorHasInjectedComponents(secondConnector, "ssl-keystore-configurator@bar", "ssl-truststore-configurator@bar");
+ assertComponentHasClassNameAndBundle(getChildComponent(secondConnector, 0),
+ DefaultSslKeyStoreConfigurator.class.getName(),
+ "jdisc_http_service");
+ assertComponentHasClassNameAndBundle(getChildComponent(secondConnector, 1),
+ DefaultSslTrustStoreConfigurator.class.getName(),
+ "jdisc_http_service");
}
}
+ private static void assertConnectorHasInjectedComponents(ConnectorFactory connectorFactory, String... componentNames) {
+ Set<String> injectedComponentIds = connectorFactory.getInjectedComponentIds();
+ assertThat(injectedComponentIds.size(), equalTo(componentNames.length));
+ Arrays.stream(componentNames)
+ .forEach(name -> assertThat(injectedComponentIds, hasItem(name)));
+ }
+
+ private static SimpleComponent getChildComponent(ConnectorFactory connectorFactory, int index) {
+ return connectorFactory.getChildrenByTypeRecursive(SimpleComponent.class).get(index);
+ }
+
+ private static void assertComponentHasClassNameAndBundle(SimpleComponent simpleComponent,
+ String className,
+ String bundleName) {
+ BundleInstantiationSpecification spec = simpleComponent.model.bundleInstantiationSpec;
+ assertThat(spec.classId.toString(), is(className));
+ assertThat(spec.bundle.toString(), is(bundleName));
+ }
+
private void assertJettyServerInConfig() {
ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default");
List<JettyHttpServer> jettyServers = cluster.getChildrenByTypeRecursive(JettyHttpServer.class);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java
index 5f18b28d6ce..41bba055a50 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.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.model.content;
+import com.yahoo.vespa.config.content.core.BucketspacesConfig;
import com.yahoo.vespa.config.search.core.ProtonConfig;
import com.yahoo.vespa.model.content.cluster.ContentCluster;
import com.yahoo.vespa.model.content.utils.ContentClusterBuilder;
+import com.yahoo.vespa.model.content.utils.DocType;
import com.yahoo.vespa.model.content.utils.SearchDefinitionBuilder;
import org.junit.Test;
@@ -15,6 +17,7 @@ import static com.yahoo.config.model.test.TestUtil.joinLines;
import static com.yahoo.vespa.model.content.utils.ContentClusterUtils.createCluster;
import static com.yahoo.vespa.model.content.utils.SearchDefinitionBuilder.createSearchDefinitions;
import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
/**
* Unit tests for content search cluster.
@@ -36,8 +39,8 @@ public class ContentSearchClusterTest {
private static ContentCluster createClusterWithGlobalType() throws Exception {
return createCluster(new ContentClusterBuilder().docTypes(Arrays.asList(
- new ContentClusterBuilder.DocType("global", true),
- new ContentClusterBuilder.DocType("regular"))).getXml(),
+ DocType.indexGlobal("global"),
+ DocType.index("regular"))).getXml(),
createSearchDefinitions("global", "regular"));
}
@@ -108,10 +111,31 @@ public class ContentSearchClusterTest {
.content("field ref_to_c type reference<c> { indexing: attribute }").build());
searchDefinitions.add(new SearchDefinitionBuilder().name("c").build());
return createCluster(new ContentClusterBuilder().docTypes(Arrays.asList(
- new ContentClusterBuilder.DocType("a"),
- new ContentClusterBuilder.DocType("b", true),
- new ContentClusterBuilder.DocType("c", true))).getXml(),
+ DocType.index("a"),
+ DocType.indexGlobal("b"),
+ DocType.indexGlobal("c"))).getXml(),
searchDefinitions);
}
+ private static BucketspacesConfig getBucketspacesConfig(ContentCluster cluster) {
+ BucketspacesConfig.Builder builder = new BucketspacesConfig.Builder();
+ cluster.getConfig(builder);
+ return new BucketspacesConfig(builder);
+ }
+
+ private static void assertDocumentType(String expName, String expBucketSpace, BucketspacesConfig.Documenttype docType) {
+ assertEquals(expName, docType.name());
+ assertEquals(expBucketSpace, docType.bucketspace());
+ }
+
+ @Test
+ public void require_that_bucket_spaces_config_is_produced_for_content_cluster() throws Exception {
+ BucketspacesConfig config = getBucketspacesConfig(createClusterWithGlobalType());
+ assertEquals(2, config.documenttype().size());
+ assertDocumentType("global", "global", config.documenttype(0));
+ assertDocumentType("regular", "default", config.documenttype(1));
+ // Safeguard against flipping the switch
+ assertFalse(config.enable_multiple_bucket_spaces());
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java
index d4e804d3f95..48b7ccdad6b 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java
@@ -1,23 +1,22 @@
// 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.content;
-import com.yahoo.vespa.config.content.core.BucketspacesConfig;
import com.yahoo.vespa.config.content.core.StorCommunicationmanagerConfig;
import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig;
import com.yahoo.vespa.config.content.core.StorServerConfig;
import com.yahoo.config.model.test.MockRoot;
import com.yahoo.vespa.model.content.cluster.ContentCluster;
import com.yahoo.vespa.model.content.utils.ContentClusterUtils;
+import com.yahoo.vespa.model.content.utils.DocType;
import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils;
import org.junit.Test;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.*;
+
/**
* Test for content DistributorCluster.
*/
@@ -303,100 +302,48 @@ public class DistributorTest {
return new StorDistributormanagerConfig(builder);
}
- private static class DocDef {
- public final String type;
- public final String mode;
- public final boolean global;
-
- private DocDef(String type, String mode, boolean global) {
- this.type = type;
- this.mode = mode;
- this.global = global;
- }
-
- public static DocDef storeOnly(String type) {
- return new DocDef(type, "store-only", false);
- }
-
- public static DocDef index(String type) {
- return new DocDef(type, "index", false);
- }
-
- public static DocDef indexGlobal(String type) {
- return new DocDef(type, "index", true);
- }
-
- public static DocDef streaming(String type) {
- return new DocDef(type, "streaming", false);
- }
- }
-
- private String generateXmlForDocDefs(DocDef... defs) {
+ private String generateXmlForDocTypes(DocType... docTypes) {
return "<content id='storage'>\n" +
- " <documents>\n" +
- Arrays.stream(defs)
- .map(def -> String.format(" <document type='%s' mode='%s' global='%s'/>",
- def.type, def.mode, (def.global ? "true" : "false")))
- .collect(Collectors.joining("\n")) +
- "\n </documents>\n" +
- "</content>";
+ DocType.listToXml(docTypes) +
+ "\n</content>";
}
@Test
public void bucket_activation_disabled_if_no_documents_in_indexed_mode() {
StorDistributormanagerConfig config = clusterXmlToConfig(
- generateXmlForDocDefs(DocDef.storeOnly("music")));
+ generateXmlForDocTypes(DocType.storeOnly("music")));
assertThat(config.disable_bucket_activation(), is(true));
}
@Test
public void bucket_activation_enabled_with_single_indexed_document() {
StorDistributormanagerConfig config = clusterXmlToConfig(
- generateXmlForDocDefs(DocDef.index("music")));
+ generateXmlForDocTypes(DocType.index("music")));
assertThat(config.disable_bucket_activation(), is(false));
}
@Test
public void bucket_activation_enabled_with_multiple_indexed_documents() {
StorDistributormanagerConfig config = clusterXmlToConfig(
- generateXmlForDocDefs(DocDef.index("music"),
- DocDef.index("movies")));
+ generateXmlForDocTypes(DocType.index("music"),
+ DocType.index("movies")));
assertThat(config.disable_bucket_activation(), is(false));
}
@Test
public void bucket_activation_enabled_if_at_least_one_document_indexed() {
StorDistributormanagerConfig config = clusterXmlToConfig(
- generateXmlForDocDefs(DocDef.storeOnly("music"),
- DocDef.streaming("bunnies"),
- DocDef.index("movies")));
+ generateXmlForDocTypes(DocType.storeOnly("music"),
+ DocType.streaming("bunnies"),
+ DocType.index("movies")));
assertThat(config.disable_bucket_activation(), is(false));
}
@Test
public void bucket_activation_disabled_for_single_streaming_type() {
StorDistributormanagerConfig config = clusterXmlToConfig(
- generateXmlForDocDefs(DocDef.streaming("music")));
+ generateXmlForDocTypes(DocType.streaming("music")));
assertThat(config.disable_bucket_activation(), is(true));
}
- private BucketspacesConfig clusterXmlToBucketspacesConfig(String xml) {
- BucketspacesConfig.Builder builder = new BucketspacesConfig.Builder();
- parse(xml).getConfig(builder);
- return new BucketspacesConfig(builder);
- }
-
- private void assertDocumentType(String expName, String expBucketSpace, BucketspacesConfig.Documenttype docType) {
- assertEquals(expName, docType.name());
- assertEquals(expBucketSpace, docType.bucketspace());
- }
-
- @Test
- public void bucket_spaces_config_is_produced_for_distributor_cluster() {
- BucketspacesConfig config = clusterXmlToBucketspacesConfig(
- generateXmlForDocDefs(DocDef.index("music"), DocDef.indexGlobal("movies")));
- assertEquals(2, config.documenttype().size());
- assertDocumentType("movies", "global", config.documenttype(0));
- assertDocumentType("music", "default", config.documenttype(1));
- }
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java
index 592e90efd22..95c57bb544c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java
@@ -18,29 +18,10 @@ import static com.yahoo.config.model.test.TestUtil.joinLines;
*/
public class ContentClusterBuilder {
- public static class DocType {
- private final String name;
- private final boolean global;
-
- public DocType(String name, boolean global) {
- this.name = name;
- this.global = global;
- }
-
- public DocType(String name) {
- this(name, false);
- }
-
- public String toXml() {
- return (global ? "<document mode='index' type='" + name + "' global='true'/>" :
- "<document mode='index' type='" + name + "'/>");
- }
- }
-
private String name = "mycluster";
private int redundancy = 1;
private int searchableCopies = 1;
- private List<DocType> docTypes = Arrays.asList(new DocType("test"));
+ private List<DocType> docTypes = Arrays.asList(DocType.index("test"));
private String groupXml = getSimpleGroupXml();
private Optional<String> dispatchXml = Optional.empty();
private Optional<Double> protonDiskLimit = Optional.empty();
@@ -66,7 +47,7 @@ public class ContentClusterBuilder {
public ContentClusterBuilder docTypes(String ... docTypes) {
this.docTypes = Arrays.asList(docTypes).stream().
- map(type -> new DocType(type)).
+ map(type -> DocType.index(type)).
collect(Collectors.toList());
return this;
}
@@ -103,9 +84,7 @@ public class ContentClusterBuilder {
public String getXml() {
String xml = joinLines("<content version='1.0' id='" + name + "'>",
" <redundancy>" + redundancy + "</redundancy>",
- " <documents>",
- docTypes.stream().map(DocType::toXml).collect(Collectors.joining("\n")),
- " </documents>",
+ DocType.listToXml(docTypes),
" <engine>",
" <proton>",
" <searchable-copies>" + searchableCopies + "</searchable-copies>",
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
index 3a2633ed7b7..e557e3674b5 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.model.content.utils;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.api.HostProvisioner;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.provision.InMemoryProvisioner;
import com.yahoo.config.model.provision.SingleNodeProvisioner;
@@ -11,9 +12,11 @@ import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.model.test.MockRoot;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.admin.Admin;
+import com.yahoo.vespa.model.admin.FileDistributionOptions;
import com.yahoo.vespa.model.admin.monitoring.DefaultMonitoring;
import com.yahoo.vespa.model.admin.monitoring.builder.Metrics;
import com.yahoo.vespa.model.content.cluster.ContentCluster;
+import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer;
import org.w3c.dom.Document;
import java.util.Collections;
@@ -57,7 +60,8 @@ public class ContentClusterUtils {
public static ContentCluster createCluster(String clusterXml, MockRoot root) throws Exception {
Document doc = XML.getDocument(clusterXml);
- Admin admin = new Admin(root, new DefaultMonitoring("vespa", 60), new Metrics(), Collections.emptyMap(), false);
+ Admin admin = new Admin(root, new DefaultMonitoring("vespa", 60), new Metrics(), Collections.emptyMap(), false,
+ new FileDistributionConfigProducer.Builder(FileDistributionOptions.defaultOptions()).build(root, new MockFileRegistry()));
ConfigModelContext context = ConfigModelContext.create(null, root.getDeployState(), null, root, null);
return new ContentCluster.Builder(admin).build(Collections.emptyList(), context, doc.getDocumentElement());
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java
new file mode 100644
index 00000000000..3a5f679509b
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java
@@ -0,0 +1,54 @@
+package com.yahoo.vespa.model.content.utils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Definition of a document type used for testing.
+ *
+ * @author geirst
+ */
+public class DocType {
+ private final String type;
+ private final String mode;
+ private final boolean global;
+
+ private DocType(String type, String mode, boolean global) {
+ this.type = type;
+ this.mode = mode;
+ this.global = global;
+ }
+
+ public String toXml() {
+ return (global ? "<document mode='" + mode + "' type='" + type + "' global='true'/>" :
+ "<document mode='" + mode + "' type='" + type + "'/>");
+ }
+
+ public static DocType storeOnly(String type) {
+ return new DocType(type, "store-only", false);
+ }
+
+ public static DocType index(String type) {
+ return new DocType(type, "index", false);
+ }
+
+ public static DocType indexGlobal(String type) {
+ return new DocType(type, "index", true);
+ }
+
+ public static DocType streaming(String type) {
+ return new DocType(type, "streaming", false);
+ }
+
+ public static String listToXml(DocType... docTypes) {
+ return listToXml(Arrays.asList(docTypes));
+ }
+
+ public static String listToXml(List<DocType> docTypes) {
+ return "<documents>\n" +
+ docTypes.stream().map(DocType::toXml).collect(Collectors.joining("\n")) + "\n" +
+ "</documents>";
+ }
+
+}
diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml
index a02346193cc..af316c2e3a7 100644
--- a/config-model/src/test/schema-test-files/services.xml
+++ b/config-model/src/test/schema-test-files/services.xml
@@ -108,6 +108,7 @@
<server port="4080" id="myServer">
<ssl-keystore-configurator class="com.yahoo.MySslKeyStoreConfigurator" bundle="mybundle" />
+ <ssl-truststore-configurator class="com.yahoo.MySslTrustStoreConfigurator" bundle="mybundle" />
</server>
<server port="4081" id="anotherServer">
<config name="container.jdisc.config.http-server">
diff --git a/config-provisioning/pom.xml b/config-provisioning/pom.xml
index ebe7d460989..05f94973017 100644
--- a/config-provisioning/pom.xml
+++ b/config-provisioning/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>config-provisioning</artifactId>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java
index 77faa45ebe5..111073aca77 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java
@@ -11,8 +11,20 @@ import java.util.Optional;
*/
public interface Deployer {
+
+ /**
+ * Creates a new deployment from the active application, if available. Will use the default timeout for deployment.
+ *
+ * @param application the active application to be redeployed
+ * @return a new deployment from the local active, or empty if a local active application
+ * was not present for this id (meaning it either is not active or active on another
+ * node in the config server cluster)
+ */
+ Optional<Deployment> deployFromLocalActive(ApplicationId application);
+
/**
- * Creates a new deployment from the active application, if available.
+ * Creates a new deployment from the active application, if available. Prefer {@link #deployFromLocalActive(ApplicationId)}
+ * if possible, this method is for testing and will override the default timeout for deployment.
*
* @param application the active application to be redeployed
* @param timeout the timeout to use for each individual deployment operation
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java
index b98020c260b..95c80be1fc1 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java
@@ -34,6 +34,7 @@ public class RegionName implements Comparable<RegionName> {
return region;
}
+ // TODO: Add verification of region name.
public static RegionName from(String region) {
return new RegionName(region);
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java
index 92e38b402e3..26c20d56d63 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java
@@ -5,10 +5,11 @@ import com.google.common.base.Strings;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
+import java.util.Objects;
import java.util.Optional;
/**
- * The zone (environment + region) of this runtime.
+ * The zone (environment + region) of this runtime, and some other information.
* An injected instance of this will return the correct current environment and region.
* Components can use this to obtain information about which zone they are running in.
*
@@ -16,11 +17,10 @@ import java.util.Optional;
*/
public class Zone {
- private final Environment environment;
- private final RegionName region;
private final SystemName systemName;
private final FlavorDefaults flavorDefaults;
private final Optional<NodeFlavors> nodeFlavors;
+ private final ZoneId id;
@Inject
public Zone(ConfigserverConfig configserverConfig, NodeFlavors nodeFlavors) {
@@ -31,19 +31,14 @@ public class Zone {
nodeFlavors);
}
- /** Create from environment and region */
+ /** Create from environment and region. Use for testing. */
public Zone(Environment environment, RegionName region) {
- this(SystemName.defaultSystem(), environment, region, "default");
+ this(SystemName.defaultSystem(), environment, region);
}
- /** Create from system, environment and region */
+ /** Create from system, environment and region. Use for testing. */
public Zone(SystemName systemName, Environment environment, RegionName region) {
- this(systemName, environment, region, "default");
- }
-
- /** Create from environment and region. Useful for testing. */
- public Zone(SystemName system, Environment environment, RegionName region, String defaultFlavor) {
- this(system, environment, region, new FlavorDefaults(defaultFlavor), null);
+ this(systemName, environment, region, new FlavorDefaults("default"), null);
}
private Zone(SystemName systemName,
@@ -51,18 +46,26 @@ public class Zone {
RegionName region,
FlavorDefaults flavorDefaults,
NodeFlavors nodeFlavors) {
- this.environment = environment;
- this.region = region;
+ this.id = ZoneId.from(environment, region);
this.flavorDefaults = flavorDefaults;
this.systemName = systemName;
this.nodeFlavors = Optional.ofNullable(nodeFlavors);
}
+ /** Returns the id of this */
+ public ZoneId id() {
+ return id;
+ }
+
/** Returns the current environment */
- public Environment environment() { return environment; }
+ public Environment environment() {
+ return id.environment();
+ }
/** Returns the current region */
- public RegionName region() { return region; }
+ public RegionName region() {
+ return id.region();
+ }
/** Returns the current system */
public SystemName system() { return systemName; }
@@ -80,21 +83,19 @@ public class Zone {
@Override
public String toString() {
- return "zone " + environment + "." + region;
+ return id.toString();
}
-
- @Override
- public int hashCode() { return environment().hashCode() + 7 * region.hashCode();}
-
+
@Override
public boolean equals(Object o) {
- if (o == this) return true;
+ if (this == o) return true;
if ( ! (o instanceof Zone)) return false;
-
- Zone other = (Zone)o;
- if ( this.environment() != other.environment()) return false;
- if ( ! this.region.equals(other.region)) return false;
- return true;
+ return Objects.equals(id, ((Zone) o).id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
}
private static class FlavorDefaults {
@@ -151,3 +152,4 @@ public class Zone {
}
}
+
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java
new file mode 100644
index 00000000000..d51a8d5e0c9
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java
@@ -0,0 +1,70 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Unique identifier for a Zone; use when referencing them.
+ *
+ * Serialised form is 'environment.region'.
+ *
+ * @author jvenstad
+ */
+public class ZoneId {
+ // TODO: Replace usages of zone + region with usages of this.
+
+ private final Environment environment;
+ private final RegionName region;
+
+ private ZoneId(Environment environment, RegionName region) {
+ this.environment = Objects.requireNonNull(environment);
+ this.region = Objects.requireNonNull(region);
+ }
+
+ public static ZoneId from(Environment environment, RegionName region) {
+ return new ZoneId(environment, region);
+ }
+
+ public static ZoneId from(String environment, String region) {
+ return from(Environment.from(environment), RegionName.from(region));
+ }
+ /** Create from a serialised ZoneId. Inverse of {@code ZoneId.value()}. */
+ public static ZoneId from(String value) {
+ String[] parts = value.split("\\.");
+ return from(parts[0], parts[1]);
+ }
+
+ public Environment environment() {
+ return environment;
+ }
+
+ public RegionName region() {
+ return region;
+ }
+
+ /** Returns the serialised value of this. Inverse of {@code ZoneId.from(String value)}. */
+ public String value() {
+ return environment + "." + region;
+ }
+
+ @Override
+ public String toString() {
+ return "zone " + value();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if ( ! (o instanceof ZoneId)) return false;
+ ZoneId id = (ZoneId) o;
+ return environment == id.environment &&
+ Objects.equals(region, id.region);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(environment, region);
+ }
+
+}
+
diff --git a/config-proxy/pom.xml b/config-proxy/pom.xml
index 0aadb1bbb12..a266f68efe2 100644
--- a/config-proxy/pom.xml
+++ b/config-proxy/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config-proxy</artifactId>
<packaging>jar</packaging>
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java
index dd1f0d36abd..0ed5d04e36e 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java
@@ -21,9 +21,7 @@ class ClientUpdater {
private final RpcServer rpcServer;
private final DelayedResponses delayedResponses;
- ClientUpdater(RpcServer rpcServer,
- ConfigProxyStatistics statistics,
- DelayedResponses delayedResponses) {
+ ClientUpdater(RpcServer rpcServer, ConfigProxyStatistics statistics, DelayedResponses delayedResponses) {
this.rpcServer = rpcServer;
this.statistics = statistics;
this.delayedResponses = delayedResponses;
@@ -37,57 +35,39 @@ class ClientUpdater {
* @param config new config
*/
void updateSubscribers(RawConfig config) {
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Config updated for " + config.getKey() + "," + config.getGeneration());
- }
+ log.log(LogLevel.DEBUG, () -> "Config updated for " + config.getKey() + "," + config.getGeneration());
sendResponse(config);
}
private void sendResponse(RawConfig config) {
if (config.isError()) { statistics.incErrorCount(); }
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Sending response for " + config.getKey() + "," + config.getGeneration());
- }
+ log.log(LogLevel.DEBUG, () -> "Sending response for " + config.getKey() + "," + config.getGeneration());
DelayQueue<DelayedResponse> responseDelayQueue = delayedResponses.responses();
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "Delayed response queue: " + responseDelayQueue);
- }
+ log.log(LogLevel.SPAM, () -> "Delayed response queue: " + responseDelayQueue);
if (responseDelayQueue.size() == 0) {
- log.log(LogLevel.DEBUG, "There exists no matching element on delayed response queue for " + config.getKey());
+ log.log(LogLevel.DEBUG, () -> "There exists no matching element on delayed response queue for " + config.getKey());
return;
} else {
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Delayed response queue has " + responseDelayQueue.size() + " elements");
- }
+ log.log(LogLevel.DEBUG, () -> "Delayed response queue has " + responseDelayQueue.size() + " elements");
}
- DelayedResponse[] responses = new DelayedResponse[1];
- responses = responseDelayQueue.toArray(responses);
+ DelayedResponse[] responses = responseDelayQueue.toArray(new DelayedResponse[0]);
boolean found = false;
- if (responses.length > 0) {
- for (DelayedResponse response : responses) {
- JRTServerConfigRequest request = response.getRequest();
- if (request.getConfigKey().equals(config.getKey())) {
- if (!delayedResponses.remove(response)) {
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Could not remove " + config.getKey() + " from delayed delayedResponses queue, already removed");
- }
- continue;
- }
+ for (DelayedResponse response : responses) {
+ JRTServerConfigRequest request = response.getRequest();
+ if (request.getConfigKey().equals(config.getKey())) {
+ if (delayedResponses.remove(response)) {
found = true;
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Call returnOkResponse for " + config.getKey() + "," + config.getGeneration());
- }
+ log.log(LogLevel.DEBUG, () -> "Call returnOkResponse for " + config.getKey() + "," + config.getGeneration());
rpcServer.returnOkResponse(request, config);
+ } else {
+ log.log(LogLevel.INFO, "Could not remove " + config.getKey() + " from delayedResponses queue, already removed");
}
}
-
}
if (!found) {
- log.log(LogLevel.DEBUG, "Found no recipient for " + config.getKey() + " in delayed response queue");
- }
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Finished updating config for " + config.getKey() + "," + config.getGeneration());
+ log.log(LogLevel.DEBUG, () -> "Found no recipient for " + config.getKey() + " in delayed response queue");
}
+ log.log(LogLevel.DEBUG, () -> "Finished updating config for " + config.getKey() + "," + config.getGeneration());
}
}
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java
index fe5976e9c1f..68985bd598a 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java
@@ -20,7 +20,6 @@ import java.util.logging.Logger;
*
* @author hmusum
*/
-// TODO: Rename now that it also support file distribution request
public class ConfigProxyRpcServer implements Runnable, TargetWatcher, RpcServer {
private final static Logger log = Logger.getLogger(ConfigProxyRpcServer.class.getName());
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java
index 7fd173ab98d..7914b7a80b6 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java
@@ -19,7 +19,6 @@ import java.util.logging.Logger;
/**
* @author hmusum
- * @since 5.1.9
*/
public class MemoryCache {
private static final Logger log = Logger.getLogger(MemoryCache.class.getName());
@@ -41,9 +40,7 @@ public class MemoryCache {
public void put(RawConfig config) {
if (config.isError()) return;
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Putting '" + config + "' into memory cache");
- }
+ log.log(LogLevel.DEBUG, () -> "Putting '" + config + "' into memory cache");
cache.put(new ConfigCacheKey(config.getKey(), config.getDefMd5()), config);
}
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java
index a1c07b91155..4b6e9172f8b 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java
@@ -10,8 +10,9 @@ import java.util.List;
import java.util.logging.Logger;
/**
+ * The client used for getting config when running in 'memorycache' mode.
+ *
* @author hmusum
- * @since 5.1.10
*/
class MemoryCacheConfigClient implements ConfigSourceClient {
@@ -30,11 +31,11 @@ class MemoryCacheConfigClient implements ConfigSourceClient {
*/
@Override
public RawConfig getConfig(RawConfig input, JRTServerConfigRequest request) {
- log.log(LogLevel.DEBUG, "Getting config from cache");
+ log.log(LogLevel.DEBUG, () -> "Getting config from cache");
ConfigKey<?> key = input.getKey();
RawConfig cached = cache.get(new ConfigCacheKey(key, input.getDefMd5()));
if (cached != null) {
- log.log(LogLevel.DEBUG, "Found config " + key + " in cache");
+ log.log(LogLevel.DEBUG, () -> "Found config " + key + " in cache");
return cached;
} else {
return null;
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java
index d0095121c8e..0f80f228b36 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java
@@ -23,7 +23,6 @@ import java.util.logging.Logger;
* An Rpc client to a config source
*
* @author hmusum
- * @since 5.1.9
*/
class RpcConfigSourceClient implements ConfigSourceClient {
@@ -83,7 +82,7 @@ class RpcConfigSourceClient implements ConfigSourceClient {
Target target = supervisor.connect(spec);
target.invokeSync(req, 30.0);
if (target.isValid()) {
- log.log(LogLevel.DEBUG, "Created connection to config source at " + spec.toString());
+ log.log(LogLevel.DEBUG, () -> "Created connection to config source at " + spec.toString());
return;
} else {
log.log(LogLevel.INFO, "Could not connect to config source at " + spec.toString());
@@ -123,13 +122,11 @@ class RpcConfigSourceClient implements ConfigSourceClient {
RawConfig ret = null;
if (cachedConfig != null) {
- log.log(LogLevel.DEBUG, "Found config " + configCacheKey + " in cache, generation=" + cachedConfig.getGeneration() +
+ log.log(LogLevel.DEBUG, () -> "Found config " + configCacheKey + " in cache, generation=" + cachedConfig.getGeneration() +
",configmd5=" + cachedConfig.getConfigMd5());
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "input config=" + input + ",cached config=" + cachedConfig);
- }
+ log.log(LogLevel.SPAM, () -> "input config=" + input + ",cached config=" + cachedConfig);
if (ProxyServer.configOrGenerationHasChanged(cachedConfig, request)) {
- log.log(LogLevel.SPAM, "Cached config is not equal to requested, will return it");
+ log.log(LogLevel.SPAM, () -> "Cached config is not equal to requested, will return it");
if (delayedResponses.remove(delayedResponse)) {
// unless another thread already did it
ret = cachedConfig;
@@ -148,9 +145,9 @@ class RpcConfigSourceClient implements ConfigSourceClient {
private void subscribeToConfig(RawConfig input, ConfigCacheKey configCacheKey) {
synchronized (activeSubscribersLock) {
if (activeSubscribers.containsKey(configCacheKey)) {
- log.log(LogLevel.DEBUG, "Already a subscriber running for: " + configCacheKey);
+ log.log(LogLevel.DEBUG, () -> "Already a subscriber running for: " + configCacheKey);
} else {
- log.log(LogLevel.DEBUG, "Could not find good config in cache, creating subscriber for: " + configCacheKey);
+ log.log(LogLevel.DEBUG, () -> "Could not find good config in cache, creating subscriber for: " + configCacheKey);
UpstreamConfigSubscriber subscriber = new UpstreamConfigSubscriber(input, clientUpdater, configSourceSet, timingValues, requesterPool, memoryCache);
try {
subscriber.subscribe();
diff --git a/config/pom.xml b/config/pom.xml
index f73c5b7bfd8..cfa3a692181 100755
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>config</artifactId>
<packaging>container-plugin</packaging>
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java
index 2aa7c66ce87..8acab56d838 100644
--- a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java
@@ -130,4 +130,32 @@ public class ConfigSetSubscriptionTest {
assertEquals(hS.getConfig().stringVal(), "new StringVal");
}
+ @Test
+ public void requireThatWeGetLatestConfigWhenTwoUpdatesBeforeClientChecks() {
+ ConfigSet myConfigs = new ConfigSet();
+ AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 1");
+ myConfigs.addBuilder("app/0", a0builder);
+ ConfigSubscriber subscriber = new ConfigSubscriber(myConfigs);
+ ConfigHandle<AppConfig> hA0 = subscriber.subscribe(AppConfig.class, "app/0");
+
+ assertTrue(subscriber.nextConfig(0));
+ assertTrue(hA0.isChanged());
+ assertEquals(hA0.getConfig().message(), "A message, 1");
+
+ assertFalse(subscriber.nextConfig(10));
+ assertFalse(hA0.isChanged());
+ assertEquals(hA0.getConfig().message(), "A message, 1");
+
+ //Reconfigure two times in a row
+ a0builder.message("A new message, 2");
+ subscriber.reload(1);
+ a0builder.message("An even newer message, 3");
+ subscriber.reload(2);
+
+ // Should pick up the last one
+ assertTrue(subscriber.nextConfig(0));
+ assertTrue(hA0.isChanged());
+ assertEquals(hA0.getConfig().message(), "An even newer message, 3");
+ }
+
}
diff --git a/config/src/tests/configagent/configagent.cpp b/config/src/tests/configagent/configagent.cpp
index 7eb9442f492..bc90cb458db 100644
--- a/config/src/tests/configagent/configagent.cpp
+++ b/config/src/tests/configagent/configagent.cpp
@@ -22,17 +22,17 @@ public:
bool abort() override { return false; }
bool isAborted() const override { return false; }
void setError(int errorCode) override { (void) errorCode; }
+ bool verifyState(const ConfigState &) const override { return false; }
const ConfigKey _key;
};
class MyConfigResponse : public ConfigResponse
{
public:
- MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool isUpdated, bool valid,
- int64_t timestamp, const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror)
+ MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool valid, int64_t timestamp,
+ const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror)
: _key(key),
_value(value),
- _isUpdated(isUpdated),
_fillCalled(false),
_valid(valid),
_state(md5, timestamp),
@@ -54,7 +54,6 @@ public:
const ConfigKey _key;
const ConfigValue _value;
- bool _isUpdated;
bool _fillCalled;
bool _valid;
const ConfigState _state;
@@ -64,24 +63,19 @@ public:
Trace _trace;
-/**
- MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool isUpdated, bool valid,
- int64_t timestamp, const vespalib::string & md5, int64_t prevTimestamp, const vespalib::string &prevMd5,
- const std::string & errorMsg, int errorC0de, bool iserror)
-*/
- static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value)
+ static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & md5 = "a")
{
- return ConfigResponse::UP(new MyConfigResponse(key, value, true, true, 10, "a", "", 0, false));
+ return ConfigResponse::UP(new MyConfigResponse(key, value, true, timestamp, md5, "", 0, false));
}
static ConfigResponse::UP createServerErrorResponse(const ConfigKey & key, const ConfigValue & value)
{
- return ConfigResponse::UP(new MyConfigResponse(key, value, false, true, 10, "a", "whinewhine", 2, true));
+ return ConfigResponse::UP(new MyConfigResponse(key, value, true, 10, "a", "whinewhine", 2, true));
}
static ConfigResponse::UP createConfigErrorResponse(const ConfigKey & key, const ConfigValue & value)
{
- return ConfigResponse::UP(new MyConfigResponse(key, value, false, false, 10, "a", "", 0, false));
+ return ConfigResponse::UP(new MyConfigResponse(key, value, false, 10, "a", "", 0, false));
}
};
@@ -106,6 +100,9 @@ public:
void handle(std::unique_ptr<ConfigUpdate> update) override
{
+ if (_update) {
+ update->merge(*_update);
+ }
_update = std::move(update);
}
@@ -154,12 +151,40 @@ TEST("require that successful request is delivered to holder") {
handler.handleResponse(MyConfigRequest(testKey), MyConfigResponse::createOKResponse(testKey, testValue));
ASSERT_TRUE(latch->poll());
ConfigUpdate::UP update(latch->provide());
- ASSERT_TRUE(update.get() != NULL);
+ ASSERT_TRUE(update);
ASSERT_TRUE(update->hasChanged());
MyConfig cfg(update->getValue());
ASSERT_EQUAL("l33t", cfg.myField);
}
+TEST("require that important(the change) request is delivered to holder even if it was not the last") {
+ const ConfigKey testKey(ConfigKey::create<MyConfig>("mykey"));
+ const ConfigValue testValue1(createValue("l33t", "a"));
+ const ConfigValue testValue2(createValue("l34t", "b"));
+ IConfigHolder::SP latch(new MyHolder());
+
+ FRTConfigAgent handler(latch, testTimingValues);
+ handler.handleResponse(MyConfigRequest(testKey),
+ MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getMd5()));
+ ASSERT_TRUE(latch->poll());
+ ConfigUpdate::UP update(latch->provide());
+ ASSERT_TRUE(update);
+ ASSERT_TRUE(update->hasChanged());
+ MyConfig cfg(update->getValue());
+ ASSERT_EQUAL("l33t", cfg.myField);
+
+ handler.handleResponse(MyConfigRequest(testKey),
+ MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getMd5()));
+ handler.handleResponse(MyConfigRequest(testKey),
+ MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getMd5()));
+ ASSERT_TRUE(latch->poll());
+ update = latch->provide();
+ ASSERT_TRUE(update);
+ ASSERT_TRUE(update->hasChanged());
+ MyConfig cfg2(update->getValue());
+ ASSERT_EQUAL("l34t", cfg2.myField);
+}
+
TEST("require that successful request sets correct wait time") {
const ConfigKey testKey(ConfigKey::create<MyConfig>("mykey"));
const ConfigValue testValue(createValue("l33t", "a"));
diff --git a/config/src/tests/configholder/configholder.cpp b/config/src/tests/configholder/configholder.cpp
index 83249da3824..2c6fa2016bf 100644
--- a/config/src/tests/configholder/configholder.cpp
+++ b/config/src/tests/configholder/configholder.cpp
@@ -46,7 +46,7 @@ TEST("Require that polling for elements work")
holder.handle(ConfigUpdate::UP(new ConfigUpdate(value, true, 0)));
ASSERT_TRUE(holder.poll());
holder.provide();
- ASSERT_TRUE(holder.poll());
+ ASSERT_FALSE(holder.poll());
}
TEST_MT_F("Require that wait is interrupted", 2, ConfigHolder)
diff --git a/config/src/tests/frt/frt.cpp b/config/src/tests/frt/frt.cpp
index 1fe91af885b..5f4b7dee215 100644
--- a/config/src/tests/frt/frt.cpp
+++ b/config/src/tests/frt/frt.cpp
@@ -6,10 +6,6 @@
#include <vespa/config/common/configdefinition.h>
#include <vespa/config/frt/connection.h>
#include <vespa/config/frt/frtsource.h>
-#include <vespa/config/frt/frtconfigresponse.h>
-#include <vespa/config/frt/frtconfigrequest.h>
-#include <vespa/config/frt/frtconfigrequestv2.h>
-#include <vespa/config/frt/frtconfigresponsev2.h>
#include <vespa/config/frt/frtconfigrequestv3.h>
#include <vespa/config/frt/frtconfigresponsev3.h>
#include <vespa/vespalib/data/slime/slime.h>
@@ -232,107 +228,27 @@ namespace {
TEST_F("require that empty config response does not validate", RPCFixture()) {
- FRTConfigResponseV1 fail1(f1.createEmptyRequest());
+ FRTConfigResponseV3 fail1(f1.createEmptyRequest());
ASSERT_FALSE(fail1.validateResponse());
ASSERT_FALSE(fail1.hasValidResponse());
ASSERT_TRUE(fail1.isError());
}
TEST_F("require that response containing errors does not validate", RPCFixture()) {
- FRTConfigResponseV1 fail1(f1.createErrorRequest());
+ FRTConfigResponseV3 fail1(f1.createErrorRequest());
ASSERT_FALSE(fail1.validateResponse());
ASSERT_FALSE(fail1.hasValidResponse());
ASSERT_TRUE(fail1.isError());
ASSERT_TRUE(fail1.errorCode() != 0);
}
-TEST_F("require that valid response validates", RPCFixture()) {
- std::vector<vespalib::string> vec;
- vec.push_back("bar \"foo\"");
- FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15, vec, "mn"));
- ASSERT_TRUE(ok.validateResponse());
- ASSERT_TRUE(ok.hasValidResponse());
-}
-
TEST_F("require that response contains all values", RPCFixture()) {
- FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15));
+ FRTConfigResponseV3 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15));
ASSERT_FALSE(ok.validateResponse());
ASSERT_FALSE(ok.hasValidResponse());
}
-TEST_F("require that valid response returns values after fill", RPCFixture()) {
- std::vector<vespalib::string> vec;
- vec.push_back("bar \"foo\"");
- FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15, vec, "mn"));
- ASSERT_TRUE(ok.validateResponse());
- ASSERT_TRUE(ok.hasValidResponse());
-
- // Should not be valid
- ASSERT_TRUE(ConfigKey() == ok.getKey());
- ASSERT_TRUE(ConfigValue() == ok.getValue());
-
- ok.fill();
- ConfigKey key(ok.getKey());
- ASSERT_EQUAL("foo", key.getDefName());
- ASSERT_EQUAL("baz", key.getDefMd5());
- ASSERT_EQUAL("bim", key.getConfigId());
- ASSERT_EQUAL("mn", key.getDefNamespace());
-
- ConfigValue value(ok.getValue());
- ASSERT_TRUE(vec == value.getLines());
-}
-
-TEST("require that request parameters are correctly initialized") {
- ConnectionMock conn;
- std::vector<vespalib::string> schema;
- schema.push_back("foo");
- schema.push_back("bar");
- ConfigKey key("foo", "bar", "bim", "boo", schema);
- FRTConfigRequestV1 frtReq(key, &conn, "mymd5", 1337, 8);
-
- FRT_RPCRequest * req = frtReq.getRequest();
- FRT_Values & params(*req->GetParams());
- ASSERT_EQUAL("bar", std::string(params[0]._string._str));
- ASSERT_EQUAL("", std::string(params[1]._string._str));
- ASSERT_EQUAL("boo", std::string(params[2]._string._str));
- ASSERT_EQUAL("foo", std::string(params[3]._string._str));
- ASSERT_EQUAL("mymd5", std::string(params[4]._string._str));
- ASSERT_EQUAL(1337u, params[5]._intval64);
- ASSERT_EQUAL(8u, params[6]._intval64);
- ASSERT_EQUAL("bim", std::string(params[7]._string._str));
- ASSERT_EQUAL(2u, params[8]._string_array._len);
- ASSERT_EQUAL("foo", std::string(params[8]._string_array._pt[0]._str));
- ASSERT_EQUAL("bar", std::string(params[8]._string_array._pt[1]._str));
- ASSERT_EQUAL(1u, params[9]._intval32);
-}
-
-TEST("require that request is aborted") {
- MyAbortHandler handler;
- ConnectionMock conn;
- ConfigKey key("foo", "bar", "bim", "boo");
- FRTConfigRequestV1 frtReq(key, &conn, "mymd5", 1337, 8);
- frtReq.getRequest()->SetAbortHandler(&handler);
- ASSERT_FALSE(frtReq.isAborted());
- ASSERT_TRUE(frtReq.abort());
-}
-
-TEST_FF("require that request is invoked", SourceFixture(),
- FRTFixture(f1))
-{
- f2.result.state = ConfigState("foo", 3);
- f2.src.getConfig();
- ASSERT_TRUE(f2.src.getCurrentRequest().verifyKey(f1.key));
- ASSERT_FALSE(f2.src.getCurrentRequest().verifyKey(ConfigKey("foo", "bal", "bim", "boo", std::vector<vespalib::string>())));
- ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 0)));
- ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 1)));
- ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("bar", 1)));
- ASSERT_TRUE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 3)));
- ASSERT_TRUE(f2.result.notified);
- f2.src.close();
-}
-
-TEST_FF("require that request is config task is scheduled", SourceFixture(),
- FRTFixture(f1))
+TEST_FF("require that request is config task is scheduled", SourceFixture(), FRTFixture(f1))
{
f2.src.getConfig();
ASSERT_TRUE(f2.result.notified);
@@ -349,96 +265,6 @@ TEST_FF("require that request is config task is scheduled", SourceFixture(),
f2.src.close();
}
-TEST("require that v2 request is correctly initialized") {
- ConnectionMock conn;
- ConfigKey key = ConfigKey::create<MyConfig>("foobi");
- vespalib::string md5 = "mymd5";
- int64_t currentGeneration = 3;
- int64_t wantedGeneration = 4;
- vespalib::string hostName = "myhost";
- int64_t timeout = 3000;
- Trace traceIn(3);
- traceIn.trace(2, "Hei");
- FRTConfigRequestV2 v2req(&conn, key, md5, currentGeneration, wantedGeneration, hostName, timeout, traceIn);
- ConfigDefinition origDef(MyConfig::CONFIG_DEF_SCHEMA);
-
- FRT_RPCRequest * req = v2req.getRequest();
- ASSERT_TRUE(req != NULL);
- FRT_Values & params(*req->GetParams());
- std::string json(params[0]._string._str);
- Slime slime;
- JsonFormat::decode(Memory(json), slime);
- Inspector & root(slime.get());
- EXPECT_EQUAL(2, root[REQUEST_VERSION].asLong());
- EXPECT_EQUAL(key.getDefName(), root[REQUEST_DEF_NAME].asString().make_string());
- EXPECT_EQUAL(key.getDefNamespace(), root[REQUEST_DEF_NAMESPACE].asString().make_string());
- EXPECT_EQUAL(key.getDefMd5(), root[REQUEST_DEF_MD5].asString().make_string());
- EXPECT_EQUAL(key.getConfigId(), root[REQUEST_CLIENT_CONFIGID].asString().make_string());
- EXPECT_EQUAL(hostName, root[REQUEST_CLIENT_HOSTNAME].asString().make_string());
- EXPECT_EQUAL(currentGeneration, root[REQUEST_CURRENT_GENERATION].asLong());
- EXPECT_EQUAL(wantedGeneration, root[REQUEST_WANTED_GENERATION].asLong());
- EXPECT_EQUAL(md5, root[REQUEST_CONFIG_MD5].asString().make_string());
- EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong());
- Trace trace;
- trace.deserialize(root[REQUEST_TRACE]);
- EXPECT_TRUE(trace.shouldTrace(2));
- EXPECT_TRUE(trace.shouldTrace(3));
- EXPECT_FALSE(trace.shouldTrace(4));
- EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong());
- ConfigDefinition def;
- def.deserialize(root[REQUEST_DEF_CONTENT]);
- EXPECT_EQUAL(origDef.asString(), def.asString());
- ConfigResponse::UP response(v2req.createResponse(req));
- req->GetReturn()->AddString("foobar");
- EXPECT_TRUE(response->validateResponse());
-}
-
-TEST("require that v2 reponse is correctly initialized") {
- ConnectionMock conn;
- Slime slime;
- ConfigKey key = ConfigKey::create<MyConfig>("foobi");
- vespalib::string md5 = "mymd5";
- int64_t generation = 3;
- vespalib::string hostname = "myhhost";
- Trace traceIn(3);
- traceIn.trace(2, "Hei!");
- Cursor & root(slime.setObject());
- root.setLong(RESPONSE_VERSION, 2ul);
- root.setString(RESPONSE_DEF_NAME, Memory(key.getDefName()));
- root.setString(RESPONSE_DEF_NAMESPACE, Memory(key.getDefNamespace()));
- root.setString(RESPONSE_DEF_MD5, Memory(key.getDefMd5()));
- root.setString(RESPONSE_CONFIGID, Memory(key.getConfigId()));
- root.setString(RESPONSE_CLIENT_HOSTNAME, Memory(hostname));
- root.setString(RESPONSE_CONFIG_MD5, Memory(md5));
- root.setLong(RESPONSE_CONFIG_GENERATION, generation);
- traceIn.serialize(root.setObject(RESPONSE_TRACE));
- Cursor & payload(root.setObject(RESPONSE_PAYLOAD));
- payload.setString("myField", "foobiar");
- SimpleBuffer buf;
- JsonFormat::encode(slime, buf, true);
- FRT_RPCRequest * req = conn.allocRPCRequest();
- req->GetReturn()->AddString(buf.get().make_string().c_str());
- FRTConfigResponseV2 response(req);
- ASSERT_TRUE(response.validateResponse());
- response.fill();
- Trace trace(response.getTrace());
- EXPECT_TRUE(trace.shouldTrace(3));
- EXPECT_FALSE(trace.shouldTrace(4));
- ConfigKey responseKey(response.getKey());
- EXPECT_EQUAL(key.getDefName(), responseKey.getDefName());
- EXPECT_EQUAL(key.getDefNamespace(), responseKey.getDefNamespace());
- EXPECT_EQUAL(key.getDefMd5(), responseKey.getDefMd5());
- EXPECT_EQUAL(key.getConfigId(), responseKey.getConfigId());
- EXPECT_EQUAL(hostname, response.getHostName());
- ConfigState state(response.getConfigState());
- EXPECT_EQUAL(md5, state.md5);
- EXPECT_EQUAL(generation, state.generation);
- ConfigValue value(response.getValue());
- MyConfig::UP config(value.newInstance<MyConfig>());
- EXPECT_EQUAL("foobiar", config->myField);
- req->SubRef();
-}
-
TEST("require that v3 request is correctly initialized") {
ConnectionMock conn;
ConfigKey key = ConfigKey::create<MyConfig>("foobi");
@@ -449,7 +275,13 @@ TEST("require that v3 request is correctly initialized") {
int64_t timeout = 3000;
Trace traceIn(3);
traceIn.trace(2, "Hei");
- FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, wantedGeneration, hostName, timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4);
+ FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, wantedGeneration, hostName,
+ timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4);
+ ASSERT_TRUE(v3req.verifyState(ConfigState(md5, 3)));
+ ASSERT_FALSE(v3req.verifyState(ConfigState(md5, 2)));
+ ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 3)));
+ ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 2)));
+
ConfigDefinition origDef(MyConfig::CONFIG_DEF_SCHEMA);
FRT_RPCRequest * req = v3req.getRequest();
diff --git a/config/src/tests/subscriber/subscriber.cpp b/config/src/tests/subscriber/subscriber.cpp
index 7613ba41234..39a537486cd 100644
--- a/config/src/tests/subscriber/subscriber.cpp
+++ b/config/src/tests/subscriber/subscriber.cpp
@@ -341,22 +341,22 @@ TEST_FF("requireThatHandlesAreMarkedAsChanged", MyManager, APIFixture(f1)) {
ConfigSubscriber s(IConfigContext::SP(new APIFixture(f2)));
ConfigHandle<FooConfig>::UP h1 = s.subscribe<FooConfig>("myid2");
ConfigHandle<BarConfig>::UP h2 = s.subscribe<BarConfig>("myid2");
- ASSERT_FALSE(s.nextConfig(0));
+ EXPECT_FALSE(s.nextConfig(0));
f1.updateValue(0, createFooValue("foo"), 1);
f1.updateValue(1, createFooValue("bar"), 1);
- ASSERT_TRUE(s.nextConfig(100));
- ASSERT_TRUE(h1->isChanged());
- ASSERT_TRUE(h2->isChanged());
+ EXPECT_TRUE(s.nextConfig(100));
+ EXPECT_TRUE(h1->isChanged());
+ EXPECT_TRUE(h2->isChanged());
- ASSERT_FALSE(s.nextConfig(100));
- ASSERT_FALSE(h1->isChanged());
- ASSERT_FALSE(h2->isChanged());
+ EXPECT_FALSE(s.nextConfig(100));
+ EXPECT_FALSE(h1->isChanged());
+ EXPECT_FALSE(h2->isChanged());
f1.updateValue(0, createFooValue("bar"), 2);
f1.updateGeneration(1, 2);
- ASSERT_TRUE(s.nextConfig(100));
- ASSERT_TRUE(h1->isChanged());
- ASSERT_FALSE(h2->isChanged());
+ EXPECT_TRUE(s.nextConfig(100));
+ EXPECT_TRUE(h1->isChanged());
+ EXPECT_FALSE(h2->isChanged());
}
TEST_FF("requireThatNextGenerationMarksChanged", MyManager, APIFixture(f1)) {
diff --git a/config/src/tests/subscription/subscription.cpp b/config/src/tests/subscription/subscription.cpp
index 5d36753813c..a9e4c923e92 100644
--- a/config/src/tests/subscription/subscription.cpp
+++ b/config/src/tests/subscription/subscription.cpp
@@ -101,11 +101,11 @@ TEST_MT_F("requireThatNextUpdateReturnsInterrupted", 2, SubscriptionFixture(Conf
TEST_F("Require that isChanged takes generation into account", SubscriptionFixture(ConfigKey::create<MyConfig>("myid")))
{
- f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(), true, 1)));
+ f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(std::vector<vespalib::string>(), "a"), true, 1)));
ASSERT_TRUE(f1.sub.nextUpdate(0, 0));
f1.sub.flip();
ASSERT_EQUAL(1, f1.sub.getLastGenerationChanged());
- f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(), true, 2)));
+ f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(std::vector<vespalib::string>(), "b"), true, 2)));
ASSERT_TRUE(f1.sub.nextUpdate(1, 0));
f1.sub.flip();
ASSERT_EQUAL(2, f1.sub.getLastGenerationChanged());
diff --git a/config/src/vespa/config/common/configholder.cpp b/config/src/vespa/config/common/configholder.cpp
index e94510dcdea..e2e6ae87688 100644
--- a/config/src/vespa/config/common/configholder.cpp
+++ b/config/src/vespa/config/common/configholder.cpp
@@ -16,14 +16,16 @@ ConfigUpdate::UP
ConfigHolder::provide()
{
vespalib::MonitorGuard guard(_monitor);
- ConfigUpdate::UP ret(new ConfigUpdate(*_current));
- return ret;
+ return std::move(_current);
}
void
ConfigHolder::handle(ConfigUpdate::UP update)
{
vespalib::MonitorGuard guard(_monitor);
+ if (_current) {
+ update->merge(*_current);
+ }
_current = std::move(update);
guard.broadcast();
}
@@ -39,7 +41,7 @@ bool
ConfigHolder::poll()
{
vespalib::MonitorGuard guard(_monitor);
- return (_current.get() != NULL);
+ return static_cast<bool>(_current);
}
void
diff --git a/config/src/vespa/config/common/configrequest.h b/config/src/vespa/config/common/configrequest.h
index 395b09abcb0..efe1e56cdd7 100644
--- a/config/src/vespa/config/common/configrequest.h
+++ b/config/src/vespa/config/common/configrequest.h
@@ -24,15 +24,13 @@ public:
ConfigRequest() { }
virtual ~ConfigRequest() { }
-
virtual const ConfigKey & getKey() const = 0;
-
/** Abort a request. */
virtual bool abort() = 0;
-
virtual bool isAborted() const = 0;
-
virtual void setError(int errorCode) = 0;
+ virtual bool verifyState(const ConfigState & state) const = 0;
+
};
}
diff --git a/config/src/vespa/config/common/configupdate.h b/config/src/vespa/config/common/configupdate.h
index 7371d525266..9afdb9a5a40 100644
--- a/config/src/vespa/config/common/configupdate.h
+++ b/config/src/vespa/config/common/configupdate.h
@@ -19,6 +19,7 @@ public:
const ConfigValue & getValue() const;
bool hasChanged() const;
int64_t getGeneration() const;
+ void merge(const ConfigUpdate & b) { _hasChanged = _hasChanged || b.hasChanged(); }
private:
ConfigValue _value;
bool _hasChanged;
diff --git a/config/src/vespa/config/frt/CMakeLists.txt b/config/src/vespa/config/frt/CMakeLists.txt
index 9d0966bb09a..879b32684a7 100644
--- a/config/src/vespa/config/frt/CMakeLists.txt
+++ b/config/src/vespa/config/frt/CMakeLists.txt
@@ -8,9 +8,7 @@ vespa_add_library(config_frt OBJECT
frtconfigresponse.cpp
frtsourcefactory.cpp
frtconfigagent.cpp
- frtconfigrequestv2.cpp
frtconfigrequestfactory.cpp
- frtconfigresponsev2.cpp
protocol.cpp
slimeconfigrequest.cpp
slimeconfigresponse.cpp
diff --git a/config/src/vespa/config/frt/frtconfigagent.cpp b/config/src/vespa/config/frt/frtconfigagent.cpp
index ff16ef77a1b..c1516a7de11 100644
--- a/config/src/vespa/config/frt/frtconfigagent.cpp
+++ b/config/src/vespa/config/frt/frtconfigagent.cpp
@@ -1,5 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "frtconfigagent.h"
+#include "frtconfigrequestv3.h"
#include <vespa/config/common/trace.h>
#include <vespa/log/log.h>
@@ -29,14 +30,14 @@ FRTConfigAgent::handleResponse(const ConfigRequest & request, ConfigResponse::UP
LOG(spam, "current state for %s: generation %ld md5 %s", key.toString().c_str(), _configState.generation, _configState.md5.c_str());
}
if (response->validateResponse() && !response->isError()) {
- handleOKResponse(std::move(response));
+ handleOKResponse(request, std::move(response));
} else {
handleErrorResponse(request, std::move(response));
}
}
void
-FRTConfigAgent::handleOKResponse(ConfigResponse::UP response)
+FRTConfigAgent::handleOKResponse(const ConfigRequest & request, ConfigResponse::UP response)
{
_failedRequests = 0;
response->fill();
@@ -45,8 +46,7 @@ FRTConfigAgent::handleOKResponse(ConfigResponse::UP response)
}
ConfigState newState = response->getConfigState();
- bool isNewGeneration = newState.isNewerGenerationThan(_configState);
- if (isNewGeneration) {
+ if ( ! request.verifyState(newState)) {
handleUpdatedGeneration(response->getKey(), newState, response->getValue());
}
setWaitTime(_timingValues.successDelay, 1);
@@ -57,22 +57,21 @@ void
FRTConfigAgent::handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue)
{
if (LOG_WOULD_LOG(spam)) {
- LOG(spam, "new generation %ld for key %s", newState.generation, key.toString().c_str());
+ LOG(spam, "new generation %ld md5:%s for key %s", newState.generation, newState.md5.c_str(), key.toString().c_str());
+ LOG(spam, "Old config: md5:%s \n%s", _latest.getMd5().c_str(), _latest.asJson().c_str());
+ LOG(spam, "New config: md5:%s \n%s", configValue.getMd5().c_str(), configValue.asJson().c_str());
}
- _configState.generation = newState.generation;
- bool hasDifferentPayload = newState.hasDifferentPayloadFrom(_configState);
- if (hasDifferentPayload) {
- if (LOG_WOULD_LOG(spam)) {
- LOG(spam, "new payload for key %s, existing md5(%s), new md5(%s)", key.toString().c_str(), _configState.md5.c_str(), newState.md5.c_str());
- }
- _configState.md5 = newState.md5;
+ bool changed = false;
+ if (_latest.getMd5() != configValue.getMd5()) {
_latest = configValue;
+ changed = true;
}
+ _configState = newState;
if (LOG_WOULD_LOG(spam)) {
- LOG(spam, "updating holder for key %s, payload changed: %d", key.toString().c_str(), hasDifferentPayload ? 1 : 0);
+ LOG(spam, "updating holder for key %s,", key.toString().c_str());
}
- _holder->handle(ConfigUpdate::UP(new ConfigUpdate(_latest, hasDifferentPayload, _configState.generation)));
+ _holder->handle(ConfigUpdate::UP(new ConfigUpdate(_latest, changed, newState.generation)));
_numConfigured++;
}
diff --git a/config/src/vespa/config/frt/frtconfigagent.h b/config/src/vespa/config/frt/frtconfigagent.h
index e3c362b5278..97edefbded7 100644
--- a/config/src/vespa/config/frt/frtconfigagent.h
+++ b/config/src/vespa/config/frt/frtconfigagent.h
@@ -33,7 +33,7 @@ public:
const ConfigState & getConfigState() const override;
private:
void handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue);
- void handleOKResponse(ConfigResponse::UP response);
+ void handleOKResponse(const ConfigRequest & request, ConfigResponse::UP response);
void handleErrorResponse(const ConfigRequest & request, ConfigResponse::UP response);
void setWaitTime(uint64_t delay, int multiplier);
diff --git a/config/src/vespa/config/frt/frtconfigrequest.cpp b/config/src/vespa/config/frt/frtconfigrequest.cpp
index aa792126aca..0845f7408de 100644
--- a/config/src/vespa/config/frt/frtconfigrequest.cpp
+++ b/config/src/vespa/config/frt/frtconfigrequest.cpp
@@ -2,9 +2,7 @@
#include "frtconfigrequest.h"
#include "frtconfigresponse.h"
#include "connection.h"
-#include <vespa/fnet/frt/frt.h>
-#include <vespa/config/common/configkey.h>
-#include <vespa/config/common/configstate.h>
+#include <vespa/fnet/frt/rpcrequest.h>
namespace config {
@@ -45,52 +43,4 @@ FRTConfigRequest::isAborted() const
return (_request->GetErrorCode() == FRTE_RPC_ABORT);
}
-const vespalib::string FRTConfigRequestV1::REQUEST_TYPES = "sssssllsSi";
-
-FRTConfigRequestV1::FRTConfigRequestV1(const ConfigKey & key,
- Connection * connection,
- const vespalib::string & configMd5,
- int64_t generation,
- int64_t serverTimeout)
- : FRTConfigRequest(connection, key)
-{
- _request->SetMethodName("config.v1.getConfig");
- _parameters.AddString(key.getDefName().c_str());
- _parameters.AddString("");
- _parameters.AddString(key.getDefMd5().c_str());
- _parameters.AddString(key.getConfigId().c_str());
- _parameters.AddString(configMd5.c_str());
- _parameters.AddInt64(generation);
- _parameters.AddInt64(serverTimeout);
- _parameters.AddString(key.getDefNamespace().c_str());
- const std::vector<vespalib::string> & schema(key.getDefSchema());
- FRT_StringValue * schemaValue = _parameters.AddStringArray(schema.size());
- for (size_t i = 0; i < schema.size(); i++) {
- _parameters.SetString(&schemaValue[i], schema[i].c_str());
- }
- _parameters.AddInt32(1);
-}
-
-bool
-FRTConfigRequestV1::verifyKey(const ConfigKey & key) const
-{
- return (key.getDefName().compare(_parameters[0]._string._str) == 0 &&
- key.getDefNamespace().compare(_parameters[7]._string._str) == 0 &&
- key.getConfigId().compare(_parameters[3]._string._str) == 0 &&
- key.getDefMd5().compare(_parameters[2]._string._str) == 0);
-}
-
-bool
-FRTConfigRequestV1::verifyState(const ConfigState & state) const
-{
- return (state.md5.compare(_parameters[4]._string._str) == 0 &&
- state.generation == static_cast<int64_t>(_parameters[5]._intval64));
-}
-
-ConfigResponse::UP
-FRTConfigRequestV1::createResponse(FRT_RPCRequest * request) const
-{
- return ConfigResponse::UP(new FRTConfigResponseV1(request));
-}
-
} // namespace config
diff --git a/config/src/vespa/config/frt/frtconfigrequest.h b/config/src/vespa/config/frt/frtconfigrequest.h
index e1d6b5590bd..061151d5f39 100644
--- a/config/src/vespa/config/frt/frtconfigrequest.h
+++ b/config/src/vespa/config/frt/frtconfigrequest.h
@@ -22,8 +22,6 @@ public:
typedef std::unique_ptr<FRTConfigRequest> UP;
FRTConfigRequest(Connection * connection, const ConfigKey & key);
~FRTConfigRequest();
- virtual bool verifyKey(const ConfigKey & key) const = 0;
- virtual bool verifyState(const ConfigState & state) const = 0;
bool abort() override;
bool isAborted() const override;
@@ -33,25 +31,11 @@ public:
FRT_RPCRequest* getRequest() { return _request; }
virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const = 0;
protected:
- FRT_RPCRequest *_request;
- FRT_Values & _parameters;
+ FRT_RPCRequest * _request;
+ FRT_Values & _parameters;
private:
- Connection * _connection;
- const ConfigKey _key;
-};
-
-class FRTConfigRequestV1 : public FRTConfigRequest {
-public:
- FRTConfigRequestV1(const ConfigKey & key,
- Connection * connection,
- const vespalib::string & configMd5,
- int64_t generation,
- int64_t serverTimeout);
- bool verifyKey(const ConfigKey & key) const override;
- bool verifyState(const ConfigState & state) const override;
- ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override;
-private:
- static const vespalib::string REQUEST_TYPES;
+ Connection * _connection;
+ const ConfigKey _key;
};
}
diff --git a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp
index 9fe3e073b65..1f1ddb196b0 100644
--- a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp
+++ b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp
@@ -1,14 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "frtconfigrequestfactory.h"
-#include "frtconfigrequest.h"
-#include "frtconfigrequestv2.h"
#include "frtconfigrequestv3.h"
-#include <vespa/config/common/trace.h>
-#include <vespa/config/common/compressiontype.h>
#include <vespa/vespalib/util/host_name.h>
-#include <unistd.h>
-#include <limits.h>
+using std::make_unique;
namespace config {
@@ -28,14 +23,11 @@ FRTConfigRequestFactory::~FRTConfigRequestFactory() {
}
FRTConfigRequest::UP
-FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection, const ConfigState & state, int64_t serverTimeout) const
+FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection,
+ const ConfigState & state, int64_t serverTimeout) const
{
- if (1 == _protocolVersion) {
- return FRTConfigRequest::UP(new FRTConfigRequestV1(key, connection, state.md5, state.generation, serverTimeout));
- } else if (2 == _protocolVersion) {
- return FRTConfigRequest::UP(new FRTConfigRequestV2(connection, key, state.md5, state.generation, 0u, _hostName, serverTimeout, Trace(_traceLevel)));
- }
- return FRTConfigRequest::UP(new FRTConfigRequestV3(connection, key, state.md5, state.generation, 0u, _hostName, serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType));
+ return make_unique<FRTConfigRequestV3>(connection, key, state.md5, state.generation, 0u, _hostName,
+ serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType);
}
} // namespace config
diff --git a/config/src/vespa/config/frt/frtconfigrequestv2.cpp b/config/src/vespa/config/frt/frtconfigrequestv2.cpp
deleted file mode 100644
index ddc79b830b3..00000000000
--- a/config/src/vespa/config/frt/frtconfigrequestv2.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "frtconfigrequestv2.h"
-#include "frtconfigresponsev2.h"
-#include "connection.h"
-#include <vespa/config/common/trace.h>
-#include <vespa/config/common/vespa_version.h>
-
-using namespace config::protocol;
-
-namespace config {
-
-FRTConfigRequestV2::FRTConfigRequestV2(Connection * connection,
- const ConfigKey & key,
- const vespalib::string & configMd5,
- int64_t currentGeneration,
- int64_t wantedGeneration,
- const vespalib::string & hostName,
- int64_t serverTimeout,
- const Trace & trace)
- : SlimeConfigRequest(connection, key, configMd5, currentGeneration, wantedGeneration, hostName, serverTimeout, trace, VespaVersion::getCurrentVersion(), 2, CompressionType::UNCOMPRESSED, "config.v2.getConfig")
-{
-}
-
-
-
-ConfigResponse::UP
-FRTConfigRequestV2::createResponse(FRT_RPCRequest * request) const
-{
- return ConfigResponse::UP(new FRTConfigResponseV2(request));
-}
-
-}
diff --git a/config/src/vespa/config/frt/frtconfigrequestv2.h b/config/src/vespa/config/frt/frtconfigrequestv2.h
deleted file mode 100644
index 5f055153b8c..00000000000
--- a/config/src/vespa/config/frt/frtconfigrequestv2.h
+++ /dev/null
@@ -1,29 +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 "slimeconfigrequest.h"
-
-class FRT_Values;
-class FRT_RPCRequest;
-
-namespace config {
-
-class ConfigKey;
-class Connection;
-class Trace;
-
-class FRTConfigRequestV2 : public SlimeConfigRequest {
-public:
- FRTConfigRequestV2(Connection * connection,
- const ConfigKey & key,
- const vespalib::string & configMd5,
- int64_t currentGeneration,
- int64_t wantedGeneration,
- const vespalib::string & hostName,
- int64_t serverTimeout,
- const Trace & trace);
- ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override;
-};
-
-}
-
diff --git a/config/src/vespa/config/frt/frtconfigresponse.cpp b/config/src/vespa/config/frt/frtconfigresponse.cpp
index 98922ad4729..046416bcfd6 100644
--- a/config/src/vespa/config/frt/frtconfigresponse.cpp
+++ b/config/src/vespa/config/frt/frtconfigresponse.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 "frtconfigresponse.h"
-#include <vespa/fnet/frt/frt.h>
+#include <vespa/fnet/frt/rpcrequest.h>
namespace config {
@@ -42,52 +42,4 @@ vespalib::string FRTConfigResponse::errorMessage() const { return _request->GetE
int FRTConfigResponse::errorCode() const { return _request->GetErrorCode(); }
bool FRTConfigResponse::isError() const { return _request->IsError(); }
-//
-// V1 Implementation
-//
-const vespalib::string FRTConfigResponseV1::RESPONSE_TYPES = "sssssilSs";
-
-FRTConfigResponseV1::FRTConfigResponseV1(FRT_RPCRequest * request)
- : FRTConfigResponse(request),
- _key(),
- _value()
-{
-}
-
-FRTConfigResponseV1::~FRTConfigResponseV1() {}
-
-const vespalib::string &
-FRTConfigResponseV1::getResponseTypes() const
-{
- return RESPONSE_TYPES;
-}
-
-void
-FRTConfigResponseV1::fill()
-{
- const std::vector<vespalib::string> payload(getPayLoad());
- _value = ConfigValue(payload, calculateContentMd5(payload));
- _key = readKey();
- _state = ConfigState(vespalib::string((*_returnValues)[4]._string._str), (*_returnValues)[6]._intval64);
-}
-
-const ConfigKey
-FRTConfigResponseV1::readKey() const
-{
- return ConfigKey((*_returnValues)[3]._string._str, (*_returnValues)[0]._string._str, (*_returnValues)[8]._string._str, (*_returnValues)[2]._string._str);
-}
-
-const std::vector<vespalib::string>
-FRTConfigResponseV1::getPayLoad() const
-{
- uint32_t numStrings = (*_returnValues)[7]._string_array._len;
- FRT_StringValue *s = (*_returnValues)[7]._string_array._pt;
- std::vector<vespalib::string> payload;
- payload.reserve(numStrings);
- for (uint32_t i = 0; i < numStrings; i++) {
- payload.push_back(vespalib::string(s[i]._str));
- }
- return payload;
-}
-
} // namespace config
diff --git a/config/src/vespa/config/frt/frtconfigresponse.h b/config/src/vespa/config/frt/frtconfigresponse.h
index f829044e698..31811c6a38f 100644
--- a/config/src/vespa/config/frt/frtconfigresponse.h
+++ b/config/src/vespa/config/frt/frtconfigresponse.h
@@ -39,33 +39,5 @@ protected:
FRT_Values * _returnValues;
};
-class FRTConfigResponseV1 : public FRTConfigResponse {
-private:
- FRTConfigResponseV1& operator=(const FRTConfigResponseV1&);
-public:
- FRTConfigResponseV1(FRT_RPCRequest * request);
- ~FRTConfigResponseV1();
-
- const ConfigKey & getKey() const override { return _key; }
- const ConfigValue & getValue() const override { return _value; }
- const Trace & getTrace() const override { return _trace; }
-
- const ConfigState & getConfigState() const override { return _state; }
-
- void fill() override;
-
-private:
- static const vespalib::string RESPONSE_TYPES;
-
- const std::vector<vespalib::string> getPayLoad() const;
- const ConfigKey readKey() const;
- const vespalib::string & getResponseTypes() const override;
-
- ConfigKey _key;
- ConfigValue _value;
- ConfigState _state;
- Trace _trace;
-};
-
} // namespace config
diff --git a/config/src/vespa/config/frt/frtconfigresponsev2.cpp b/config/src/vespa/config/frt/frtconfigresponsev2.cpp
deleted file mode 100644
index 1527700f99f..00000000000
--- a/config/src/vespa/config/frt/frtconfigresponsev2.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "frtconfigresponsev2.h"
-#include <vespa/fnet/frt/frt.h>
-
-using namespace vespalib;
-using namespace vespalib::slime;
-using namespace vespalib::slime::convenience;
-using namespace config::protocol::v2;
-
-namespace config {
-
-class V2Payload : public protocol::Payload {
-public:
- V2Payload(const SlimePtr & data)
- : _data(data)
- {}
- const Inspector & getSlimePayload() const override {
- return extractPayload(*_data);
- }
-private:
- SlimePtr _data;
-};
-
-const vespalib::string FRTConfigResponseV2::RESPONSE_TYPES = "s";
-
-FRTConfigResponseV2::FRTConfigResponseV2(FRT_RPCRequest * request)
- : SlimeConfigResponse(request)
-{
-}
-
-const vespalib::string &
-FRTConfigResponseV2::getResponseTypes() const
-{
- return RESPONSE_TYPES;
-}
-
-const ConfigValue
-FRTConfigResponseV2::readConfigValue() const
-{
- vespalib::string md5(_data->get()[RESPONSE_CONFIG_MD5].asString().make_string());
- return ConfigValue(PayloadPtr(new V2Payload(_data)), md5);
-}
-
-} // namespace config
diff --git a/config/src/vespa/config/frt/frtconfigresponsev2.h b/config/src/vespa/config/frt/frtconfigresponsev2.h
deleted file mode 100644
index 89d1a9157e3..00000000000
--- a/config/src/vespa/config/frt/frtconfigresponsev2.h
+++ /dev/null
@@ -1,28 +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 "slimeconfigresponse.h"
-#include <vespa/config/common/configvalue.h>
-
-class FRT_RPCRequest;
-class FRT_Values;
-
-namespace config {
-
-/**
- * Baseclass for config responses.
- */
-class FRTConfigResponseV2 : public SlimeConfigResponse {
-private:
- FRTConfigResponseV2& operator=(const FRTConfigResponseV2&);
-public:
- FRTConfigResponseV2(FRT_RPCRequest * request);
-
-private:
- static const vespalib::string RESPONSE_TYPES;
- const vespalib::string & getResponseTypes() const override;
- const ConfigValue readConfigValue() const override;
-};
-
-} // namespace config
-
diff --git a/config/src/vespa/config/frt/slimeconfigrequest.cpp b/config/src/vespa/config/frt/slimeconfigrequest.cpp
index 12dbbab2eb7..27ac39ae56a 100644
--- a/config/src/vespa/config/frt/slimeconfigrequest.cpp
+++ b/config/src/vespa/config/frt/slimeconfigrequest.cpp
@@ -40,19 +40,10 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection,
}
bool
-SlimeConfigRequest::verifyKey(const ConfigKey & key) const
-{
- return (key.getDefName().compare(_parameters[0]._string._str) == 0 &&
- key.getDefNamespace().compare(_parameters[7]._string._str) == 0 &&
- key.getConfigId().compare(_parameters[3]._string._str) == 0 &&
- key.getDefMd5().compare(_parameters[2]._string._str) == 0);
-}
-
-bool
SlimeConfigRequest::verifyState(const ConfigState & state) const
{
- return (state.md5.compare(_parameters[4]._string._str) == 0 &&
- state.generation == static_cast<int64_t>(_parameters[5]._intval64));
+ return (state.md5.compare(_data[REQUEST_CONFIG_MD5].asString().make_stringref()) == 0 &&
+ state.generation == _data[REQUEST_CURRENT_GENERATION].asLong());
}
void
diff --git a/config/src/vespa/config/frt/slimeconfigrequest.h b/config/src/vespa/config/frt/slimeconfigrequest.h
index d616876780e..625d70c094b 100644
--- a/config/src/vespa/config/frt/slimeconfigrequest.h
+++ b/config/src/vespa/config/frt/slimeconfigrequest.h
@@ -30,7 +30,6 @@ public:
const CompressionType & compressionType,
const vespalib::string & methodName);
~SlimeConfigRequest() {}
- bool verifyKey(const ConfigKey & key) const override;
bool verifyState(const ConfigState & state) const override;
virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override = 0;
private:
diff --git a/config/src/vespa/config/subscription/configsubscription.cpp b/config/src/vespa/config/subscription/configsubscription.cpp
index d97ddfd57fb..efa3f5a02e3 100644
--- a/config/src/vespa/config/subscription/configsubscription.cpp
+++ b/config/src/vespa/config/subscription/configsubscription.cpp
@@ -28,9 +28,10 @@ ConfigSubscription::~ConfigSubscription()
bool
ConfigSubscription::nextUpdate(int64_t generation, uint64_t timeoutInMillis)
{
- if (_closed || !_holder->poll())
+ if (_closed || !_holder->poll()) {
return false;
- _next.reset(_holder->provide().release());
+ }
+ _next = _holder->provide();
if (isGenerationNewer(_next->getGeneration(), generation)) {
return true;
}
@@ -38,9 +39,15 @@ ConfigSubscription::nextUpdate(int64_t generation, uint64_t timeoutInMillis)
}
bool
+ConfigSubscription::hasGenerationChanged() const
+{
+ return (!_closed && _next && ((_current && (_current->getGeneration() != _next->getGeneration())) || ! _current));
+}
+
+bool
ConfigSubscription::hasChanged() const
{
- return (!_closed && (_next->hasChanged() || _current.get() == NULL));
+ return (!_closed && _next && ((_next->hasChanged() && _current && (_current->getValue() != _next->getValue())) || ! _current));
}
int64_t
@@ -88,7 +95,7 @@ ConfigSubscription::flip()
{
bool change = hasChanged();
if (change) {
- _current.reset(_next.release());
+ _current = std::move(_next);
_lastGenerationChanged = _current->getGeneration();
} else {
_current.reset(new ConfigUpdate(_current->getValue(), false, _next->getGeneration()));
@@ -96,13 +103,15 @@ ConfigSubscription::flip()
_isChanged = change;
}
-ConfigValue
+const ConfigValue &
ConfigSubscription::getConfig() const
{
- if (_closed)
+ if (_closed) {
throw ConfigRuntimeException("Subscription is closed, config no longer available");
- if (_current.get() == NULL)
+ }
+ if ( ! _current) {
throw ConfigRuntimeException("No configuration available");
+ }
return _current->getValue();
}
diff --git a/config/src/vespa/config/subscription/configsubscription.h b/config/src/vespa/config/subscription/configsubscription.h
index 04c3da114fd..56b65922a61 100644
--- a/config/src/vespa/config/subscription/configsubscription.h
+++ b/config/src/vespa/config/subscription/configsubscription.h
@@ -28,7 +28,7 @@ public:
*
* @return the current ConfigValue.
*/
- ConfigValue getConfig() const;
+ const ConfigValue & getConfig() const;
/**
* Checks whether or not the config has changed.
@@ -48,6 +48,7 @@ public:
bool nextUpdate(int64_t generation, uint64_t timeoutInMillis);
int64_t getGeneration() const;
bool hasChanged() const;
+ bool hasGenerationChanged() const;
void flip();
void reset();
void close();
diff --git a/config/src/vespa/config/subscription/configsubscriptionset.cpp b/config/src/vespa/config/subscription/configsubscriptionset.cpp
index f6268c8a84a..ab38ba18351 100644
--- a/config/src/vespa/config/subscription/configsubscriptionset.cpp
+++ b/config/src/vespa/config/subscription/configsubscriptionset.cpp
@@ -38,10 +38,6 @@ ConfigSubscriptionSet::acquireSnapshot(uint64_t timeoutInMillis, bool ignoreChan
int64_t lastGeneration = _currentGeneration;
bool inSync = false;
- for (const auto & subscription : _subscriptionList) {
- subscription->reset();
- }
-
LOG(debug, "Going into nextConfig loop, time left is %d", timeLeft);
while (_state != CLOSED && timeLeft >= 0 && !inSync) {
size_t numChanged = 0;
@@ -52,8 +48,10 @@ ConfigSubscriptionSet::acquireSnapshot(uint64_t timeoutInMillis, bool ignoreChan
// Run nextUpdate on all subscribers to get them in sync.
for (const auto & subscription : _subscriptionList) {
- if (!subscription->nextUpdate(_currentGeneration, timeLeft))
- break;
+ if (!subscription->nextUpdate(_currentGeneration, timeLeft) && !subscription->hasGenerationChanged()) {
+ subscription->reset();
+ continue;
+ }
const ConfigKey & key(subscription->getKey());
if (subscription->hasChanged()) {
diff --git a/configd/src/apps/sentinel/config-handler.cpp b/configd/src/apps/sentinel/config-handler.cpp
index e5222b0350b..67216954f51 100644
--- a/configd/src/apps/sentinel/config-handler.cpp
+++ b/configd/src/apps/sentinel/config-handler.cpp
@@ -56,7 +56,8 @@ ConfigHandler::ConfigHandler()
_outputConnections(),
_boundPort(0),
_commandSocket(listen(0)),
- _startMetrics()
+ _startMetrics(),
+ _stateApi(_startMetrics.producer)
{
_startMetrics.startedTime = time(nullptr);
}
@@ -395,61 +396,10 @@ ConfigHandler::handleCommand(CommandConnection *c)
}
}
-namespace {
-
-void
-fillRestarts(vespalib::SimpleMetricSnapshot &snapshot, unsigned long restarts)
-{
- snapshot.addCount("sentinel.restarts",
- "how many times sentinel restarted a service",
- restarts);
-}
-
-void
-fillTotalRestarts(vespalib::SimpleMetricSnapshot &snapshot,
- unsigned long totalRestarts)
-{
- snapshot.addGauge("sentinel.totalRestarts",
- "how many times sentinel restarted a service since "
- "sentinel start",
- totalRestarts);
-}
-
-void
-fillRunning(vespalib::SimpleMetricSnapshot &snapshot, unsigned long running)
-{
- snapshot.addGauge("sentinel.running",
- "how many services the sentinel has running currently",
- running);
-}
-
-void
-fillUptime(vespalib::SimpleMetricSnapshot &snapshot, long uptime)
-{
- snapshot.addGauge("sentinel.uptime",
- "how many seconds has the sentinel been running",
- uptime);
-}
-
-} // namespace <unnamed>
-
void
ConfigHandler::updateMetrics()
{
- time_t now = time(nullptr);
- vespalib::SimpleMetricSnapshot snapshot(_startMetrics.snapshotStart, _startMetrics.snapshotEnd);
- fillRestarts(snapshot, _startMetrics.totalRestartsLastSnapshot);
- fillTotalRestarts(snapshot, _startMetrics.totalRestartsCounter);
- fillRunning(snapshot, _startMetrics.currentlyRunningServices);
- fillUptime(snapshot, now - _startMetrics.startedTime);
- _stateApi.myMetrics.setMetrics(snapshot.asString());
-
- vespalib::SimpleMetricSnapshot totals(_startMetrics.startedTime, now);
- fillRestarts(totals, _startMetrics.totalRestartsCounter);
- fillRunning(totals, _startMetrics.currentlyRunningServices);
- fillUptime(totals, now - _startMetrics.startedTime);
- _stateApi.myMetrics.setTotalMetrics(totals.asString());
-
+ _startMetrics.maybeLog();
}
void
diff --git a/configd/src/apps/sentinel/metrics.cpp b/configd/src/apps/sentinel/metrics.cpp
index 42c61c06b50..df1b0076001 100644
--- a/configd/src/apps/sentinel/metrics.cpp
+++ b/configd/src/apps/sentinel/metrics.cpp
@@ -4,18 +4,33 @@
#include <vespa/log/log.h>
LOG_SETUP(".metrics");
+#include <vespa/vespalib/metrics/simple_metrics.h>
+
namespace config {
namespace sentinel {
+using vespalib::metrics::SimpleMetricsManager;
+using vespalib::metrics::SimpleManagerConfig;
+
StartMetrics::StartMetrics()
- : currentlyRunningServices(0), totalRestartsCounter(0), totalRestartsLastPeriod(1),
- lastLoggedTime(0),
- totalRestartsLastSnapshot(0),
- snapshotStart(0),
- snapshotEnd(0)
+ : metrics(SimpleMetricsManager::create(SimpleManagerConfig())),
+ producer(metrics),
+ currentlyRunningServices(0),
+ totalRestartsCounter(0),
+ totalRestartsLastPeriod(1),
+ startedTime(time(nullptr)),
+ lastLoggedTime(startedTime - 55),
+ sentinel_restarts(metrics->counter("sentinel.restarts",
+ "how many times sentinel restarted a service")),
+ sentinel_totalRestarts(metrics->gauge("sentinel.totalRestarts",
+ "how many times sentinel restarted a service since sentinel start")),
+ sentinel_running(metrics->gauge("sentinel.running",
+ "how many services the sentinel has running currently")),
+ sentinel_uptime(metrics->gauge("sentinel.uptime",
+ "how many seconds has the sentinel been running"))
{
- snapshotEnd = time(nullptr);
- lastLoggedTime = snapshotEnd - 55;
+ // account for the sentinel itself restarting
+ sentinel_restarts.add();
}
void
@@ -29,9 +44,6 @@ StartMetrics::output()
void
StartMetrics::reset(unsigned long curTime)
{
- totalRestartsLastSnapshot = totalRestartsLastPeriod;
- snapshotStart = snapshotEnd;
- snapshotEnd = curTime;
totalRestartsLastPeriod = 0;
lastLoggedTime = curTime;
}
@@ -40,6 +52,9 @@ void
StartMetrics::maybeLog()
{
uint32_t curTime = time(nullptr);
+ sentinel_totalRestarts.sample(totalRestartsCounter);
+ sentinel_running.sample(currentlyRunningServices);
+ sentinel_uptime.sample(curTime - startedTime);
if (curTime > lastLoggedTime + 59) {
output();
reset(curTime);
diff --git a/configd/src/apps/sentinel/metrics.h b/configd/src/apps/sentinel/metrics.h
index dd24bb2280d..2378a055663 100644
--- a/configd/src/apps/sentinel/metrics.h
+++ b/configd/src/apps/sentinel/metrics.h
@@ -2,20 +2,29 @@
#pragma once
#include <sys/time.h>
+#include <vespa/vespalib/metrics/simple_metrics.h>
namespace config::sentinel {
+using vespalib::metrics::Counter;
+using vespalib::metrics::Gauge;
+using vespalib::metrics::MetricsManager;
+
struct StartMetrics {
+ std::shared_ptr<MetricsManager> metrics;
+ vespalib::metrics::Producer producer;
unsigned long currentlyRunningServices;
unsigned long totalRestartsCounter;
unsigned long totalRestartsLastPeriod;
- long lastLoggedTime;
- unsigned long totalRestartsLastSnapshot;
- long snapshotStart;
- long snapshotEnd;
long startedTime;
+ long lastLoggedTime;
+ Counter sentinel_restarts;
+ Gauge sentinel_totalRestarts;
+ Gauge sentinel_running;
+ Gauge sentinel_uptime;
StartMetrics();
+ ~StartMetrics() {}
void output();
void reset(unsigned long curTime);
@@ -23,4 +32,3 @@ struct StartMetrics {
};
}
-
diff --git a/configd/src/apps/sentinel/service.cpp b/configd/src/apps/sentinel/service.cpp
index 2f13a05eb4f..3c762a957ec 100644
--- a/configd/src/apps/sentinel/service.cpp
+++ b/configd/src/apps/sentinel/service.cpp
@@ -239,6 +239,7 @@ Service::start()
// ensureChildRuns(pipes[0]); // This will wait until the execl goes through
setState(RUNNING);
_metrics.currentlyRunningServices++;
+ _metrics.sentinel_running.sample(_metrics.currentlyRunningServices);
close(pipes[0]); // close reading end
using ns_log::LLParser;
@@ -314,6 +315,7 @@ Service::youExited(int status)
setState(FAILED);
}
_metrics.currentlyRunningServices--;
+ _metrics.sentinel_running.sample(_metrics.currentlyRunningServices);
if (_state == TERMINATING) {
setState(TERMINATED);
@@ -326,6 +328,7 @@ Service::youExited(int status)
setState(READY);
_metrics.totalRestartsCounter++;
_metrics.totalRestartsLastPeriod++;
+ _metrics.sentinel_restarts.add();
start();
}
}
diff --git a/configd/src/apps/sentinel/state-api.h b/configd/src/apps/sentinel/state-api.h
index 879563b79a5..ca080a35865 100644
--- a/configd/src/apps/sentinel/state-api.h
+++ b/configd/src/apps/sentinel/state-api.h
@@ -3,9 +3,9 @@
#pragma once
#include <vespa/vespalib/net/state_api.h>
-#include <vespa/vespalib/net/simple_metrics_producer.h>
#include <vespa/vespalib/net/simple_health_producer.h>
#include <vespa/vespalib/net/simple_component_config_producer.h>
+#include <vespa/vespalib/metrics/simple_metrics.h>
namespace config {
namespace sentinel {
@@ -13,11 +13,12 @@ namespace sentinel {
struct StateApi {
vespalib::string host_and_port;
vespalib::SimpleHealthProducer myHealth;
- vespalib::SimpleMetricsProducer myMetrics;
vespalib::SimpleComponentConfigProducer myComponents;
vespalib::StateApi myStateApi;
- StateApi() : myStateApi(myHealth, myMetrics, myComponents) {}
+ StateApi(vespalib::metrics::Producer &myMetrics)
+ : myStateApi(myHealth, myMetrics, myComponents)
+ {}
vespalib::string get(const char *path) const;
void bound(int port);
diff --git a/configdefinitions/pom.xml b/configdefinitions/pom.xml
index 92980a18899..487c43ed3b3 100644
--- a/configdefinitions/pom.xml
+++ b/configdefinitions/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>configdefinitions</artifactId>
<packaging>container-plugin</packaging>
diff --git a/configdefinitions/src/vespa/configserver.def b/configdefinitions/src/vespa/configserver.def
index c13665342ef..cbc2317da2d 100644
--- a/configdefinitions/src/vespa/configserver.def
+++ b/configdefinitions/src/vespa/configserver.def
@@ -1,5 +1,6 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=cloud.config
+
rpcport int default=19070
httpport int default=19071
numthreads int default=16
@@ -43,4 +44,8 @@ dockerRegistry string default=""
dockerVespaBaseImage string default=""
# Athenz config
-loadBalancerAddress string default="" \ No newline at end of file
+loadBalancerAddress string default=""
+
+# File distribution
+disableFiledistributor bool default=false
+usechunkedtransfer bool default=true
diff --git a/configgen/pom.xml b/configgen/pom.xml
index 6e550ec7321..e8de114f8d5 100644
--- a/configgen/pom.xml
+++ b/configgen/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>configgen</artifactId>
<packaging>jar</packaging>
diff --git a/configserver/pom.xml b/configserver/pom.xml
index 30d92dc7650..f5aa8ea3118 100644
--- a/configserver/pom.xml
+++ b/configserver/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>configserver</artifactId>
<packaging>container-plugin</packaging>
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 13401d4a4a6..a4dd943aa47 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
@@ -112,6 +112,18 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
* Creates a new deployment from the active application, if available.
*
* @param application the active application to be redeployed
+ * @return a new deployment from the local active, or empty if a local active application
+ * was not present for this id (meaning it either is not active or active on another
+ * node in the config server cluster)
+ */
+ public Optional<com.yahoo.config.provision.Deployment> deployFromLocalActive(ApplicationId application) {
+ return deployFromLocalActive(application, Duration.ofSeconds(configserverConfig.zookeeper().barrierTimeout()).plus(Duration.ofSeconds(5)));
+ }
+
+ /**
+ * Creates a new deployment from the active application, if available.
+ *
+ * @param application the active application to be redeployed
* @param timeout the timeout to use for each individual deployment operation
* @return a new deployment from the local active, or empty if a local active application
* was not present for this id (meaning it either is not active or active on another
@@ -352,7 +364,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private void redeployApplication(ApplicationId applicationId, Deployer deployer, ExecutorService deploymentExecutor) {
log.log(LogLevel.DEBUG, () -> "Redeploying " + applicationId);
- deployer.deployFromLocalActive(applicationId, Duration.ofMinutes(30))
+ deployer.deployFromLocalActive(applicationId)
.ifPresent(deployment -> deploymentExecutor.execute(() -> {
try {
deployment.activate();
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
index 58c61134bc4..6ab98f5af1c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.Deployer;
+import com.yahoo.container.jdisc.state.StateMonitor;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.config.server.rpc.RpcServer;
import com.yahoo.vespa.config.server.version.VersionState;
@@ -23,17 +24,19 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable
private final Thread serverThread;
private final Deployer deployer;
private final VersionState versionState;
+ private final StateMonitor stateMonitor;
// The tenants object is injected so that all initial requests handlers are
// added to the rpc server before it starts answering rpc requests.
- @SuppressWarnings("UnusedParameters")
+ @SuppressWarnings("WeakerAccess")
@Inject
public ConfigServerBootstrap(ApplicationRepository applicationRepository, RpcServer server,
- Deployer deployer, VersionState versionState) {
+ Deployer deployer, VersionState versionState, StateMonitor stateMonitor) {
this.applicationRepository = applicationRepository;
this.server = server;
this.deployer = deployer;
this.versionState = versionState;
+ this.stateMonitor = stateMonitor;
this.serverThread = new Thread(this, "configserver main");
serverThread.start();
}
@@ -62,9 +65,11 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable
log.log(LogLevel.INFO, "All applications redeployed");
}
versionState.saveNewVersion();
+ stateMonitor.status(StateMonitor.Status.up);
log.log(LogLevel.DEBUG, "Starting RPC server");
server.run();
log.log(LogLevel.DEBUG, "RPC server stopped");
+ stateMonitor.status(StateMonitor.Status.down);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index c0c9c309576..4502cc7e223 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -130,8 +130,8 @@ public class ModelContextImpl implements ModelContext {
public Version wantedNodeVespaVersion() { return wantedNodeVespaVersion; }
/**
- * @author lulf
- */
+ * @author Ulf Lilleengen
+ */
public static class Properties implements ModelContext.Properties {
private final ApplicationId applicationId;
@@ -141,6 +141,7 @@ public class ModelContextImpl implements ModelContext {
private final boolean hostedVespa;
private final Zone zone;
private final Set<Rotation> rotations;
+ private final boolean disableFileDistributor;
public Properties(ApplicationId applicationId,
boolean multitenant,
@@ -148,7 +149,8 @@ public class ModelContextImpl implements ModelContext {
HostName loadBalancerName,
boolean hostedVespa,
Zone zone,
- Set<Rotation> rotations) {
+ Set<Rotation> rotations,
+ boolean disableFileDistributor) {
this.applicationId = applicationId;
this.multitenant = multitenant;
this.configServerSpecs = configServerSpecs;
@@ -156,6 +158,7 @@ public class ModelContextImpl implements ModelContext {
this.hostedVespa = hostedVespa;
this.zone = zone;
this.rotations = rotations;
+ this.disableFileDistributor = disableFileDistributor;
}
@Override
@@ -189,9 +192,10 @@ public class ModelContextImpl implements ModelContext {
}
@Override
- public Set<Rotation> rotations() {
- return rotations;
- }
+ public Set<Rotation> rotations() { return rotations; }
+
+ @Override
+ public boolean disableFileDistributor() { return disableFileDistributor; }
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java
index 588f2d1d63f..1046ed93491 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java
@@ -3,22 +3,45 @@ package com.yahoo.vespa.config.server.filedistribution;
import com.yahoo.config.FileReference;
import com.yahoo.config.model.api.FileDistribution;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.StringArray;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Target;
+import com.yahoo.jrt.Transport;
+import com.yahoo.log.LogLevel;
import java.util.Collection;
import java.util.Set;
+import java.util.logging.Logger;
+/**
+ * @author baldersheim
+ */
public class CombinedLegacyDistribution implements FileDistribution {
+ private final static Logger log = Logger.getLogger(CombinedLegacyDistribution.class.getName());
+
+ private final Supervisor supervisor = new Supervisor(new Transport());
private final FileDistribution legacy;
+ private final boolean disableFileDistributor;
- CombinedLegacyDistribution(FileDBHandler legacy) {
+ CombinedLegacyDistribution(FileDBHandler legacy, boolean disableFileDistributor) {
this.legacy = legacy;
+ this.disableFileDistributor = disableFileDistributor;
}
+
@Override
public void sendDeployedFiles(String hostName, Set<FileReference> fileReferences) {
legacy.sendDeployedFiles(hostName, fileReferences);
}
@Override
+ public void startDownload(String hostName, Set<FileReference> fileReferences) {
+ if (disableFileDistributor)
+ startDownloadingFileReferences(hostName, fileReferences);
+ }
+
+ @Override
public void reloadDeployFileDistributor() {
legacy.reloadDeployFileDistributor();
}
@@ -27,4 +50,18 @@ public class CombinedLegacyDistribution implements FileDistribution {
public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) {
legacy.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames);
}
+
+ // Notifies config proxy which file references it should start downloading. It's OK if the call does not succeed,
+ // as downloading will then start synchronously when a service requests a file reference instead
+ private void startDownloadingFileReferences(String hostName, Set<FileReference> fileReferences) {
+ Target target = supervisor.connect(new Spec(hostName, 19090));
+ double timeout = 0.1;
+ Request request = new Request("filedistribution.setFileReferencesToDownload");
+ request.parameters().add(new StringArray(fileReferences.stream().map(FileReference::value).toArray(String[]::new)));
+ log.log(LogLevel.DEBUG, "Executing " + request.methodName() + " against " + target.toString());
+ target.invokeSync(request, timeout);
+ if (request.isError()) {
+ log.log(LogLevel.INFO, request.methodName() + " failed: " + request.errorCode() + " (" + request.errorMessage() + ")");
+ }
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java
index f0ce6104496..9b3f4c39a45 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java
@@ -10,9 +10,8 @@ import java.util.*;
/**
* Implements invoker of filedistribution using manager with JNI.
*
- * @author tonytv
- * @author lulf
- * @since 5.1.14
+ * @author Tony Vaagenes
+ * @author Ulf Lilleengen
*/
public class FileDBHandler implements FileDistribution {
private final FileDistributionManager manager;
@@ -31,6 +30,11 @@ public class FileDBHandler implements FileDistribution {
}
@Override
+ public void startDownload(String hostName, Set<FileReference> fileReferences) {
+ throw new UnsupportedOperationException("Not valid for this Filedistribution implementation");
+ }
+
+ @Override
public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) {
manager.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
index 7d0ba6cd9bd..e991341b616 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
@@ -5,16 +5,21 @@ package com.yahoo.vespa.config.server.filedistribution;
import com.yahoo.config.FileReference;
import com.yahoo.config.model.api.FileDistribution;
import com.yahoo.io.IOUtils;
+import com.yahoo.log.LogLevel;
import com.yahoo.text.Utf8;
import net.jpountz.xxhash.XXHash64;
import net.jpountz.xxhash.XXHashFactory;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.logging.Logger;
public class FileDirectory {
@@ -30,7 +35,7 @@ public class FileDirectory {
try {
ensureRootExist();
} catch (IllegalArgumentException e) {
- log.warning("Failed creating directory in constructor, will retry on demand : " + e.toString());
+ log.log(LogLevel.WARNING, "Failed creating directory in constructor, will retry on demand : " + e.toString());
}
}
@@ -65,12 +70,8 @@ public class FileDirectory {
throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + "' is not a directory.");
}
File [] files = dir.listFiles(new Filter());
- if (files.length != 1) {
- StringBuilder msg = new StringBuilder();
- for (File f: files) {
- msg.append(f.getName()).append("\n");
- }
- throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain exactly one file, but [" + msg.toString() + "]");
+ if (files == null || files.length == 0) {
+ throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain any files");
}
return files[0];
}
@@ -91,24 +92,28 @@ public class FileDirectory {
}
}
- public FileReference addFile(File source, FileReference reference) {
+ FileReference addFile(File source, FileReference reference) {
ensureRootExist();
try {
+ logfileInfo(source);
File destinationDir = new File(root, reference.value());
+ Path tempDestinationDir = Files.createTempDirectory(root.toPath(), "writing");
+ File destination = new File(tempDestinationDir.toFile(), source.getName());
if (!destinationDir.exists()) {
destinationDir.mkdir();
- Path tempDestinationDir = Files.createTempDirectory(root.toPath(), "writing");
- File destination = new File(tempDestinationDir.toFile(), source.getName());
- if (source.isDirectory())
- IOUtils.copyDirectory(source, destination);
- else
- IOUtils.copy(source, destination);
+ log.log(LogLevel.DEBUG, "file reference ' " + reference.value() + "', source: " + source.getAbsolutePath() );
+ if (source.isDirectory()) {
+ log.log(LogLevel.DEBUG, "Copying source " + source.getAbsolutePath() + " to " + destination.getAbsolutePath());
+ IOUtils.copyDirectory(source, destination, -1);
+ } else
+ copyFile(source, destination);
if (!destinationDir.exists()) {
+ log.log(LogLevel.DEBUG, "Moving from " + tempDestinationDir + " to " + destinationDir.getAbsolutePath());
if ( ! tempDestinationDir.toFile().renameTo(destinationDir)) {
- log.warning("Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'.");
+ log.log(LogLevel.WARNING, "Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'.");
}
} else {
- IOUtils.copyDirectory(tempDestinationDir.toFile(), destinationDir, 1);
+ IOUtils.copyDirectory(tempDestinationDir.toFile(), destinationDir, -1);
}
IOUtils.recursiveDeleteDir(tempDestinationDir.toFile());
}
@@ -117,4 +122,18 @@ public class FileDirectory {
throw new IllegalArgumentException(e);
}
}
+
+ private void logfileInfo(File file ) throws IOException {
+ BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+ log.log(LogLevel.DEBUG, "Adding file " + file.getAbsolutePath() + " (created " + basicFileAttributes.creationTime() +
+ ", modified " + basicFileAttributes.lastModifiedTime() +
+ ", size " + basicFileAttributes.size() + ")");
+ }
+
+ private static void copyFile(File source, File dest) throws IOException {
+ try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
+ FileChannel destChannel = new FileOutputStream(dest).getChannel()) {
+ destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
+ }
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java
index 59c3a54897d..38fa3087f88 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java
@@ -12,8 +12,7 @@ import java.util.concurrent.locks.Lock;
/**
* Provides file distribution registry and invoker.
*
- * @author lulf
- * @since 5.1.14
+ * @author Ulf Lilleengen
*/
public class FileDistributionProvider {
@@ -37,18 +36,19 @@ public class FileDistributionProvider {
}
public FileDistributionProvider(File applicationDir, String zooKeepersSpec,
- String applicationId, Lock fileDistributionLock)
- {
+ String applicationId, Lock fileDistributionLock,
+ boolean disableFileDistributor) {
ensureDirExists(FileDistribution.getDefaultFileDBPath());
final FileDistributionManager manager = new FileDistributionManager(
FileDistribution.getDefaultFileDBPath(), applicationDir,
zooKeepersSpec, applicationId, fileDistributionLock);
- this.fileDistribution = new CombinedLegacyDistribution(new FileDBHandler(manager));
+ this.fileDistribution = new CombinedLegacyDistribution(new FileDBHandler(manager), disableFileDistributor);
this.fileRegistry = new CombinedLegacyRegistry(new FileDBRegistry(new ManagerWrapper(manager)),
new FileDBRegistry(new ApplicationFileManager(applicationDir, new FileDirectory())));
}
- public FileDistributionProvider(FileRegistry fileRegistry, FileDistribution fileDistribution) {
+ // For testing only
+ FileDistributionProvider(FileRegistry fileRegistry, FileDistribution fileDistribution) {
this.fileRegistry = fileRegistry;
this.fileDistribution = fileDistribution;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java
index 9dc94c9fe93..a9c56cf99d0 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java
@@ -2,24 +2,55 @@
package com.yahoo.vespa.config.server.filedistribution;
import com.google.inject.Inject;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.FileReference;
import com.yahoo.config.model.api.FileDistribution;
import com.yahoo.config.subscription.ConfigSourceSet;
-import com.yahoo.io.IOUtils;
+import com.yahoo.jrt.Int32Value;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Transport;
+import com.yahoo.log.LogLevel;
+import com.yahoo.net.HostName;
+import com.yahoo.vespa.config.Connection;
+import com.yahoo.vespa.config.ConnectionPool;
import com.yahoo.vespa.config.JRTConnectionPool;
+import com.yahoo.vespa.config.server.ConfigServerSpec;
+import com.yahoo.vespa.filedistribution.CompressedFileReference;
import com.yahoo.vespa.filedistribution.FileDownloader;
+import com.yahoo.vespa.filedistribution.FileReferenceData;
+import com.yahoo.vespa.filedistribution.FileReferenceDataBlob;
+import com.yahoo.vespa.filedistribution.LazyFileReferenceData;
import java.io.File;
import java.io.IOException;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
public class FileServer {
private static final Logger log = Logger.getLogger(FileServer.class.getName());
+
private final FileDirectory root;
- private final ExecutorService executor;
- private final FileDownloader downloader = new FileDownloader(new JRTConnectionPool(ConfigSourceSet.createDefault()));
+ private final ExecutorService pushExecutor;
+ private final ExecutorService pullExecutor;
+ private final FileDownloader downloader;
+
+ private enum FileApiErrorCodes {
+ OK(0, "OK"),
+ NOT_FOUND(1, "Filereference not found");
+ private final int code;
+ private final String description;
+ FileApiErrorCodes(int code, String description) {
+ this.code = code;
+ this.description = description;
+ }
+ int getCode() { return code; }
+ String getDescription() { return description; }
+ }
public static class ReplayStatus {
private final int code;
@@ -34,22 +65,26 @@ public class FileServer {
}
public interface Receiver {
- void receive(FileReference reference, String filename, byte [] content, ReplayStatus status);
+ void receive(FileReferenceData fileData, ReplayStatus status);
}
@Inject
- public FileServer() {
- this(FileDistribution.getDefaultFileDBPath());
+ public FileServer(ConfigserverConfig configserverConfig) {
+ this(createConnectionPool(configserverConfig), FileDistribution.getDefaultFileDBPath());
}
+ // For testing only
public FileServer(File rootDir) {
- this(rootDir, Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()));
+ this(new EmptyConnectionPool(), rootDir);
}
- public FileServer(File rootDir, ExecutorService executor) {
+ private FileServer(ConnectionPool connectionPool, File rootDir) {
+ this.downloader = new FileDownloader(connectionPool);
this.root = new FileDirectory(rootDir);
- this.executor = executor;
+ this.pushExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+ this.pullExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
+
public boolean hasFile(String fileName) {
return hasFile(new FileReference(fileName));
}
@@ -66,32 +101,102 @@ public class FileServer {
File file = root.getFile(reference);
if (file.exists()) {
- executor.execute(() -> serveFile(reference, target));
+ pushExecutor.execute(() -> serveFile(reference, target));
}
return false;
}
private void serveFile(FileReference reference, Receiver target) {
File file = root.getFile(reference);
- // TODO remove once verified in system tests.
- log.info("Start serving reference '" + reference.value() + "' with file '" + file.getAbsolutePath() + "'");
- byte [] blob = new byte [0];
+ log.log(LogLevel.DEBUG, "Start serving reference '" + reference.value() + "' with file '" + file.getAbsolutePath() + "'");
boolean success = false;
String errorDescription = "OK";
+ FileReferenceData fileData = FileReferenceDataBlob.empty(reference, file.getName());
try {
- blob = IOUtils.readFileBytes(file);
+ fileData = readFileReferenceData(reference);
success = true;
} catch (IOException e) {
- errorDescription = "For file reference '" + reference.value() + "' I failed reading file '" + file.getAbsolutePath() + "'";
- log.warning(errorDescription + "for sending to '" + target.toString() + "'. " + e.toString());
+ errorDescription = "For file reference '" + reference.value() + "': failed reading file '" + file.getAbsolutePath() + "'";
+ log.warning(errorDescription + " for sending to '" + target.toString() + "'. " + e.toString());
}
- target.receive(reference, file.getName(), blob,
- new ReplayStatus(success ? 0 : 1, success ? "OK" : errorDescription));
- // TODO remove once verified in system tests.
- log.info("Done serving reference '" + reference.toString() + "' with file '" + file.getAbsolutePath() + "'");
+
+ target.receive(fileData, new ReplayStatus(success ? 0 : 1, success ? "OK" : errorDescription));
+ log.log(LogLevel.DEBUG, "Done serving reference '" + reference.toString() + "' with file '" + file.getAbsolutePath() + "'");
+ }
+
+ private FileReferenceData readFileReferenceData(FileReference reference) throws IOException {
+ File file = root.getFile(reference);
+
+ if (file.isDirectory()) {
+ //TODO Here we should compress to file, but then we have to clean up too. Pending.
+ byte [] blob = CompressedFileReference.compress(file.getParentFile());
+ return new FileReferenceDataBlob(reference, file.getName(), FileReferenceData.Type.compressed, blob);
+ } else {
+ return new LazyFileReferenceData(reference, file.getName(), FileReferenceData.Type.file, file);
+ }
+ }
+ public void serveFile(Request request, Receiver receiver) {
+ pullExecutor.execute(() -> serveFile(request.parameters().get(0).asString(), request, receiver));
+ }
+ private void serveFile(String fileReference, Request request, Receiver receiver) {
+ FileApiErrorCodes result;
+ try {
+ log.log(LogLevel.DEBUG, "Received request for reference '" + fileReference + "'");
+ result = hasFile(fileReference)
+ ? FileApiErrorCodes.OK
+ : FileApiErrorCodes.NOT_FOUND;
+ if (result == FileApiErrorCodes.OK) {
+ startFileServing(fileReference, receiver);
+ } else {
+ download(new FileReference(fileReference));
+ }
+ } catch (IllegalArgumentException e) {
+ result = FileApiErrorCodes.NOT_FOUND;
+ log.warning("Failed serving file reference '" + fileReference + "' with error " + e.toString());
+ }
+ request.returnValues()
+ .add(new Int32Value(result.getCode()))
+ .add(new StringValue(result.getDescription()));
+ request.returnRequest();
}
public void download(FileReference fileReference) {
downloader.getFile(fileReference);
}
+
+ public FileDownloader downloader() {
+ return downloader;
+ }
+
+ // Connection pool with all config servers except this one (might be an empty pool if there is only one config server)
+ private static ConnectionPool createConnectionPool(ConfigserverConfig configserverConfig) {
+ List<String> configServers = ConfigServerSpec.fromConfig(configserverConfig)
+ .stream()
+ .filter(spec -> !spec.getHostName().equals(HostName.getLocalhost()))
+ .map(spec -> "tcp/" + spec.getHostName() + ":" + spec.getConfigServerPort())
+ .collect(Collectors.toList());
+
+ return configServers.size() > 0 ? new JRTConnectionPool(new ConfigSourceSet(configServers)) : new EmptyConnectionPool();
+ }
+
+ private static class EmptyConnectionPool implements ConnectionPool {
+
+ @Override
+ public void close() {}
+
+ @Override
+ public void setError(Connection connection, int i) {}
+
+ @Override
+ public Connection getCurrent() { return null; }
+
+ @Override
+ public Connection setNewCurrentConnection() { return null; }
+
+ @Override
+ public int getSize() { return 0; }
+
+ @Override
+ public Supervisor getSupervisor() { return new Supervisor(new Transport()); }
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java
index eb23d38e23e..caf64cca4d0 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java
@@ -8,8 +8,7 @@ import java.util.Collection;
import java.util.Set;
/**
- * @author lulf
- * @since 5.1
+ * @author Ulf Lilleengen
*/
public class MockFileDBHandler implements FileDistribution {
public int sendDeployedFilesCalled = 0;
@@ -22,6 +21,9 @@ public class MockFileDBHandler implements FileDistribution {
}
@Override
+ public void startDownload(String hostName, Set<FileReference> fileReferences) { /* not implemented */ }
+
+ @Override
public void reloadDeployFileDistributor() {
reloadDeployFileDistributorCalled++;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
index 48732814919..ea8405f6b65 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java
@@ -185,7 +185,8 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> {
HostName.from(configserverConfig.loadBalancerAddress()),
configserverConfig.hostedVespa(),
zone,
- rotations);
+ rotations,
+ configserverConfig.disableFiledistributor());
}
/**
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
index 78d660b347e..b3ade24603c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java
@@ -88,7 +88,8 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P
log.log(LogLevel.DEBUG, "Building model " + modelVersion + " for " + applicationId);
FileDistributionProvider fileDistributionProvider = fileDistributionFactory.createProvider(
context.getServerDBSessionDir(),
- applicationId);
+ applicationId,
+ properties.disableFileDistributor());
// Use empty on non-hosted systems, use already allocated hosts if available, create connection to a host provisioner otherwise
Optional<HostProvisioner> hostProvisioner = createHostProvisioner(allocatedHosts);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
index d17cdf722ea..72576292471 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
@@ -41,8 +41,8 @@ import com.yahoo.vespa.config.server.monitoring.MetricUpdaterFactory;
import com.yahoo.vespa.config.server.tenant.TenantHandlerProvider;
import com.yahoo.vespa.config.server.tenant.TenantListener;
import com.yahoo.vespa.config.server.tenant.Tenants;
-import net.jpountz.xxhash.XXHash64;
-import net.jpountz.xxhash.XXHashFactory;
+import com.yahoo.vespa.filedistribution.FileReceiver;
+import com.yahoo.vespa.filedistribution.FileReferenceData;
import java.nio.ByteBuffer;
import java.util.Collection;
@@ -76,20 +76,9 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
static final int TRACELEVEL_DEBUG = 9;
private static final String THREADPOOL_NAME = "rpcserver worker pool";
private static final long SHUTDOWN_TIMEOUT = 60;
- private enum FileApiErrorCodes {
- OK(0, "OK"),
- NOT_FOUND(1, "Filereference not found");
- private final int code;
- private final String description;
- FileApiErrorCodes(int code, String description) {
- this.code = code;
- this.description = description;
- }
- int getCode() { return code; }
- String getDescription() { return description; }
- }
+
private final Supervisor supervisor = new Supervisor(new Transport());
- private Spec spec = null;
+ private Spec spec;
private final boolean useRequestVersion;
private final boolean hostedVespa;
@@ -106,6 +95,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
private final FileServer fileServer;
private final ThreadPoolExecutor executorService;
+ private final boolean useChunkedFileTransfer;
private volatile boolean allTenantsLoaded = false;
/**
@@ -131,6 +121,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
this.useRequestVersion = config.useVespaVersionInRequest();
this.hostedVespa = config.hostedVespa();
this.fileServer = fileServer;
+ this.useChunkedFileTransfer = config.usechunkedtransfer();
setUpHandlers();
}
@@ -426,9 +417,9 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
return useRequestVersion;
}
- class FileReceiver implements FileServer.Receiver {
+ class WholeFileReceiver implements FileServer.Receiver {
Target target;
- FileReceiver(Target target) {
+ WholeFileReceiver(Target target) {
this.target = target;
}
@@ -438,44 +429,111 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
}
@Override
- public void receive(FileReference reference, String filename, byte [] content, FileServer.ReplayStatus status) {
- XXHash64 hasher = XXHashFactory.fastestInstance().hash64();
- Request fileBlob = new Request("filedistribution.receiveFile");
- fileBlob.parameters().add(new StringValue(reference.value()));
- fileBlob.parameters().add(new StringValue(filename));
- fileBlob.parameters().add(new DataValue(content));
- fileBlob.parameters().add(new Int64Value(hasher.hash(ByteBuffer.wrap(content), 0)));
+ public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) {
+ Request fileBlob = new Request(FileReceiver.RECEIVE_METHOD);
+ fileBlob.parameters().add(new StringValue(fileData.fileReference().value()));
+ fileBlob.parameters().add(new StringValue(fileData.filename()));
+ fileBlob.parameters().add(new StringValue(fileData.type().name()));
+ fileBlob.parameters().add(new DataValue(fileData.content().array()));
+ fileBlob.parameters().add(new Int64Value(fileData.xxhash()));
fileBlob.parameters().add(new Int32Value(status.getCode()));
fileBlob.parameters().add(new StringValue(status.getDescription()));
target.invokeSync(fileBlob, 600);
if (fileBlob.isError()) {
- log.warning("Failed delivering reference '" + reference.value() + "' with file '" + filename + "' to " +
- target.toString() + " with error : '" + fileBlob.errorMessage() + "'.");
+ log.warning("Failed delivering reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " +
+ target.toString() + " with error: '" + fileBlob.errorMessage() + "'.");
}
}
}
- @SuppressWarnings("UnusedDeclaration")
- public final void serveFile(Request request) {
- String fileReference = request.parameters().get(0).asString();
- FileApiErrorCodes result;
- try {
- // TODO remove once verified in system tests.
- log.info("Received request for reference '" + fileReference + "'");
- result = fileServer.hasFile(fileReference)
- ? FileApiErrorCodes.OK
- : FileApiErrorCodes.NOT_FOUND;
- if (result == FileApiErrorCodes.OK) {
- fileServer.startFileServing(fileReference, new FileReceiver(request.target()));
- } else {
- fileServer.download(new FileReference(fileReference));
+ class ChunkedFileReceiver implements FileServer.Receiver {
+ Target target;
+ ChunkedFileReceiver(Target target) {
+ this.target = target;
+ }
+
+ @Override
+ public String toString() {
+ return target.toString();
+ }
+
+ @Override
+ public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) {
+ int session = sendMeta(fileData);
+ sendParts(session, fileData);
+ sendEof(session, fileData, status);
+ }
+ private void sendParts(int session, FileReferenceData fileData) {
+ ByteBuffer bb = ByteBuffer.allocate(0x100000);
+ for (int partId = 0, read = fileData.nextContent(bb); read >= 0; partId++, read = fileData.nextContent(bb)) {
+ byte [] buf = bb.array();
+ if (buf.length != bb.position()) {
+ buf = new byte [bb.position()];
+ bb.flip();
+ bb.get(buf);
+ }
+ sendPart(session, fileData.fileReference(), partId, buf);
+ bb.clear();
+ }
+ }
+ private int sendMeta(FileReferenceData fileData) {
+ Request request = new Request(FileReceiver.RECEIVE_META_METHOD);
+ request.parameters().add(new StringValue(fileData.fileReference().value()));
+ request.parameters().add(new StringValue(fileData.filename()));
+ request.parameters().add(new StringValue(fileData.type().name()));
+ request.parameters().add(new Int64Value(fileData.size()));
+ target.invokeSync(request, 600);
+ if (request.isError()) {
+ log.warning("Failed delivering meta for reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " +
+ target.toString() + " with error: '" + request.errorMessage() + "'.");
+ }
+ int retCode = request.returnValues().get(0).asInt32();
+ if (retCode != 0) {
+ throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName());
+ }
+ return request.returnValues().get(1).asInt32();
+ }
+ private void sendPart(int session, FileReference ref, int partId, byte [] buf) {
+ Request request = new Request(FileReceiver.RECEIVE_PART_METHOD);
+ request.parameters().add(new StringValue(ref.value()));
+ request.parameters().add(new Int32Value(session));
+ request.parameters().add(new Int32Value(partId));
+ request.parameters().add(new DataValue(buf));
+ target.invokeSync(request, 600);
+ if (request.isError()) {
+ log.warning("Failed delivering reference '" + ref.value() + "' to " +
+ target.toString() + " with error: '" + request.errorMessage() + "'.");
+ }
+ int retCode = request.returnValues().get(0).asInt32();
+ if (retCode != 0) {
+ throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName());
+ }
+ }
+ private void sendEof(int session, FileReferenceData fileData, FileServer.ReplayStatus status) {
+ Request request = new Request(FileReceiver.RECEIVE_EOF_METHOD);
+ request.parameters().add(new StringValue(fileData.fileReference().value()));
+ request.parameters().add(new Int32Value(session));
+ request.parameters().add(new Int64Value(fileData.xxhash()));
+ request.parameters().add(new Int32Value(status.getCode()));
+ request.parameters().add(new StringValue(status.getDescription()));
+ target.invokeSync(request, 600);
+ if (request.isError()) {
+ log.warning("Failed delivering reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " +
+ target.toString() + " with error: '" + request.errorMessage() + "'.");
+ }
+ int retCode = request.returnValues().get(0).asInt32();
+ if (retCode != 0) {
+ throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName());
}
- } catch (IllegalArgumentException e) {
- result = FileApiErrorCodes.NOT_FOUND;
- log.warning("Failed serving file reference '" + fileReference + "' with error " + e.toString());
}
- request.returnValues()
- .add(new Int32Value(result.getCode()))
- .add(new StringValue(result.getDescription()));
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public final void serveFile(Request request) {
+ request.detach();
+ FileServer.Receiver receiver = useChunkedFileTransfer
+ ? new ChunkedFileReceiver(request.target())
+ : new WholeFileReceiver(request.target());
+ fileServer.serveFile(request, receiver);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
index 003f931a218..99a34a45a2f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
@@ -13,8 +13,7 @@ import java.util.concurrent.locks.Lock;
/**
* Factory for creating providers that are used to interact with file distribution.
*
- * @author lulf
- * @since 5.1
+ * @author Ulf Lilleengen
*/
@SuppressWarnings("WeakerAccess")
public class FileDistributionFactory {
@@ -33,8 +32,8 @@ public class FileDistributionFactory {
this.zkSpec = zkSpec;
}
- public FileDistributionProvider createProvider(File applicationPackage, ApplicationId applicationId) {
- return new FileDistributionProvider(applicationPackage, zkSpec, applicationId.serializedForm(), lock);
+ public FileDistributionProvider createProvider(File applicationPackage, ApplicationId applicationId, boolean disableFileDistributor) {
+ return new FileDistributionProvider(applicationPackage, zkSpec, applicationId.serializedForm(), lock, disableFileDistributor);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java
index 2269a7ed997..41c4ddd676e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java
@@ -28,12 +28,11 @@ import org.apache.curator.framework.recipes.cache.*;
/**
* Will watch/prepare sessions (applications) based on watched nodes in ZooKeeper, set for example
* by the prepare HTTP handler on another configserver. The zookeeper state watched in this class is shared
- * between all configservers, so it should not modify any global state, because the operation will be performed
+ * between all config servers, so it should not modify any global state, because the operation will be performed
* on all servers. The repo can be regarded as read only from the POV of the configserver.
*
- * @author vegardh
- * @author lulf
- * @since 5.1
+ * @author Vegard Havdal
+ * @author Ulf Lilleengen
*/
public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements NodeCacheListener, PathChildrenCacheListener {
@@ -74,6 +73,17 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements Nod
sessionsChanged();
}
+ // For testing only
+ public RemoteSessionRepo(TenantName tenantName) {
+ this.curator = null;
+ this.remoteSessionFactory = null;
+ this.reloadHandler = null;
+ this.sessionsPath = Tenants.getSessionsPath(tenantName);
+ this.metrics = null;
+ this.directoryCache = null;
+ this.applicationRepo = null;
+ }
+
//---------- START overrides to keep sessions changed in sync
@Override
@@ -122,17 +132,6 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements Nod
}
}
- // For testing only
- public RemoteSessionRepo() {
- this.curator = null;
- this.remoteSessionFactory = null;
- this.reloadHandler = null;
- this.sessionsPath = Path.createRoot();
- this.metrics = null;
- this.directoryCache = null;
- this.applicationRepo = null;
- }
-
private List<Long> getSessionList(List<ChildData> children) {
List<Long> sessions = new ArrayList<>();
for (ChildData data : children) {
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 531085883c4..24666f42dc9 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
@@ -155,7 +155,8 @@ public class SessionPreparer {
HostName.from(configserverConfig.loadBalancerAddress()),
configserverConfig.hostedVespa(),
zone,
- rotationsSet);
+ rotationsSet,
+ configserverConfig.disableFiledistributor());
this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry,
permanentApplicationPackage,
configDefinitionRepo,
@@ -177,7 +178,7 @@ public class SessionPreparer {
void preprocess() {
try {
- this.applicationPackage = context.getApplicationPackage().preprocess(properties.zone(), null, logger);
+ this.applicationPackage = context.getApplicationPackage().preprocess(properties.zone().id(), null, logger);
} catch (IOException | TransformerException | ParserConfigurationException | SAXException e) {
throw new RuntimeException("Error deploying application package", e);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java
index d2cf17a38d4..5fb62a68e3e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java
@@ -96,18 +96,17 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen
}
/**
- * New instance containing the given tenants. This will not create Zookeeper watches. For testing only
+ * New instance containing the given tenants. Creates no system tenants and noZookeeper watches. For testing only.
* @param globalComponentRegistry a {@link com.yahoo.vespa.config.server.GlobalComponentRegistry} instance
* @param tenants a collection of {@link Tenant}s
*/
+ // TODO: Get rid of the second argument and let callers use addTenant() instead
public Tenants(GlobalComponentRegistry globalComponentRegistry, Collection<Tenant> tenants) {
this.globalComponentRegistry = globalComponentRegistry;
this.curator = globalComponentRegistry.getCurator();
metricUpdater = globalComponentRegistry.getMetrics().getOrCreateMetricUpdater(Collections.emptyMap());
this.tenantListeners.add(globalComponentRegistry.getTenantListener());
curator.create(tenantsPath);
- createSystemTenants(globalComponentRegistry.getConfigserverConfig());
- createTenants();
this.directoryCache = curator.createDirectoryCache(tenantsPath.getAbsolute(), false, false, pathChildrenExecutor);
this.tenants.putAll(addTenants(tenants));
}
@@ -120,13 +119,13 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen
// Pre-condition: tenants path needs to exist in zk
private LinkedHashMap<TenantName, Tenant> addTenants(Collection<Tenant> newTenants) {
- LinkedHashMap<TenantName, Tenant> sessionTenants = new LinkedHashMap<>();
+ LinkedHashMap<TenantName, Tenant> tenants = new LinkedHashMap<>();
for (Tenant t : newTenants) {
- sessionTenants.put(t.getName(), t);
+ tenants.put(t.getName(), t);
}
- log.log(LogLevel.DEBUG, "Tenants at startup: " + sessionTenants);
- metricUpdater.setTenants(tenants.size());
- return sessionTenants;
+ log.log(LogLevel.DEBUG, "Tenants at startup: " + tenants);
+ metricUpdater.setTenants(this.tenants.size());
+ return tenants;
}
public synchronized void addTenant(TenantName tenantName) throws Exception {
@@ -191,7 +190,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen
try {
Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName).build();
notifyNewTenant(tenant);
- tenants.put(tenantName, tenant);
+ tenants.putIfAbsent(tenantName, tenant);
} catch (Exception e) {
log.log(LogLevel.WARNING, "Error loading tenant '" + tenantName + "', skipping.", e);
}
diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml
index 635ce07e727..fd77fedd789 100644
--- a/configserver/src/main/resources/configserver-app/services.xml
+++ b/configserver/src/main/resources/configserver-app/services.xml
@@ -6,6 +6,10 @@
<maxthreads>100</maxthreads> <!-- Reduced thread count to minimize memory consumption -->
</config>
+ <config name="container.jdisc.config.health-monitor">
+ <initialStatus>initializing</initialStatus>
+ </config>
+
<accesslog type="vespa" fileNamePattern="logs/vespa/configserver/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" />
<preprocess:include file='access-logging.xml' required='false' />
<component id="com.yahoo.vespa.config.server.ConfigServerBootstrap" bundle="configserver" />
@@ -34,6 +38,7 @@
<component id="com.yahoo.config.provision.Zone" bundle="config-provisioning" />
<component id="com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.application.HttpProxy" bundle="configserver" />
+ <component id="com.yahoo.vespa.config.server.filedistribution.FileServer" bundle="configserver" />
<component id="com.yahoo.vespa.serviceview.ConfigServerLocation" bundle="configserver" />
@@ -72,64 +77,96 @@
<handler id='com.yahoo.vespa.config.server.http.HttpGetConfigHandler' bundle='configserver'>
<binding>http://*/config/v1/*/*</binding>
+ <binding>https://*/config/v1/*/*</binding>
<binding>http://*/config/v1/*</binding>
+ <binding>https://*/config/v1/*</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.HttpListConfigsHandler' bundle='configserver'>
<binding>http://*/config/v1/</binding>
+ <binding>https://*/config/v1/</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.HttpListNamedConfigsHandler' bundle='configserver'>
<binding>http://*/config/v1/*/</binding>
+ <binding>https://*/config/v1/*/</binding>
<binding>http://*/config/v1/*/*/</binding>
+ <binding>https://*/config/v1/*/*/</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.TenantHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/</binding>
+ <binding>https://*/application/v2/tenant/</binding>
<binding>http://*/application/v2/tenant/*</binding>
+ <binding>https://*/application/v2/tenant/*</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.SessionCreateHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/*/session</binding>
+ <binding>https://*/application/v2/tenant/*/session</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.SessionPrepareHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/*/session/*/prepared</binding>
+ <binding>https://*/application/v2/tenant/*/session/*/prepared</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.SessionActiveHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/*/session/*/active</binding>
+ <binding>https://*/application/v2/tenant/*/session/*/active</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.SessionContentHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/*/session/*/content/*</binding>
+ <binding>https://*/application/v2/tenant/*/session/*/content/*</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.ListApplicationsHandler' bundle='configserver'>
<binding>http://*/application/v2/tenant/*/application/</binding>
+ <binding>https://*/application/v2/tenant/*/application/</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.ApplicationHandler' bundle='configserver'>
<!-- WARNING: THIS LIST *MUST* MATCH THE ONE IN ApplicationHandler::getBindingMatch -->
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/content/*</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/content/*</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/restart</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/restart</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/log</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/log</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/converge</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/converge</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*</binding>
<binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding>
+ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding>
<binding>http://*/application/v2/tenant/*/application/*</binding>
+ <binding>https://*/application/v2/tenant/*/application/*</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.HttpGetConfigHandler' bundle='configserver'>
<binding>http://*/config/v2/tenant/*/application/*/*</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/*</binding>
<binding>http://*/config/v2/tenant/*/application/*/*/*</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/*/*</binding>
<binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*</binding>
<binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.HttpListConfigsHandler' bundle='configserver'>
<binding>http://*/config/v2/tenant/*/application/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/</binding>
<binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.HttpListNamedConfigsHandler' bundle='configserver'>
<binding>http://*/config/v2/tenant/*/application/*/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/*/</binding>
<binding>http://*/config/v2/tenant/*/application/*/*/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/*/*/</binding>
<binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/</binding>
<binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*/</binding>
+ <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*/</binding>
</handler>
<handler id='com.yahoo.vespa.config.server.http.v2.HostHandler' bundle='configserver'>
- <binding>http://*/application/v2/host/*</binding>
+ <binding>http://*/application/v2/host/*</binding>
+ <binding>https://*/application/v2/host/*</binding>
</handler>
<http>
diff --git a/configserver/src/main/sh/start-filedistribution b/configserver/src/main/sh/start-filedistribution
index bb8599f2bc9..1995e59de4e 100755
--- a/configserver/src/main/sh/start-filedistribution
+++ b/configserver/src/main/sh/start-filedistribution
@@ -63,7 +63,7 @@ ROOT=${VESPA_HOME%/}
VESPA_CONFIG_ID="dir:${ROOT}/conf/filedistributor"
export VESPA_CONFIG_ID
-if [ "$multitenant" = "true" ]; then
+if [ "$multitenant" = "true" ] && [ "$disable_filedistributor" = "" ] || [ "$disable_filedistributor" = "false" ]; then
foo=`${ROOT}/libexec/vespa/vespa-config.pl -mkfiledistributorconfig`
PIDFILE_FILEDISTRIBUTOR=var/run/filedistributor.pid
LOGFILE="${ROOT}/logs/vespa/vespa.log"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
index e0d65055f21..5d573323bb6 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
@@ -6,7 +6,10 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.config.HealthMonitorConfig;
+import com.yahoo.container.jdisc.state.StateMonitor;
import com.yahoo.io.IOUtils;
+import com.yahoo.jdisc.core.SystemTimer;
import com.yahoo.vespa.config.server.deploy.MockDeployer;
import com.yahoo.vespa.config.server.host.HostRegistries;
import com.yahoo.vespa.config.server.http.SessionHandlerTest;
@@ -26,7 +29,6 @@ import java.io.File;
import java.io.FileReader;
import java.time.Clock;
import java.util.ArrayList;
-import java.util.Optional;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
@@ -67,7 +69,9 @@ public class ConfigServerBootstrapTest extends TestWithTenant {
assertFalse(myServer.stopped);
VersionState versionState = new VersionState(versionFile);
assertTrue(versionState.isUpgraded());
- ConfigServerBootstrap bootstrap = new ConfigServerBootstrap(applicationRepository, rpc, (application, timeout) -> Optional.empty(), versionState);
+ ConfigServerBootstrap bootstrap =
+ new ConfigServerBootstrap(applicationRepository, rpc, new MockDeployer(), versionState,
+ new StateMonitor(new HealthMonitorConfig(new HealthMonitorConfig.Builder()), new SystemTimer()));
waitUntilStarted(rpc, 60000);
assertFalse(versionState.isUpgraded());
assertThat(versionState.currentVersion(), is(versionState.storedVersion()));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
index aed0a6a9750..e4c336a55f1 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
@@ -48,7 +48,8 @@ public class ModelContextImplTest {
null,
false,
Zone.defaultZone(),
- rotations),
+ rotations,
+ false),
Optional.empty(),
new Version(6),
new Version(6));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
index d9a0db7e811..63f0f5f26b7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
@@ -23,6 +23,7 @@ import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ProvisionLogger;
import com.yahoo.config.provision.Provisioner;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Version;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.config.server.ApplicationRepository;
@@ -57,6 +58,8 @@ import java.util.Optional;
*/
public class DeployTester {
+ private static final TenantName tenantName = TenantName.from("deploytester");
+
private final Clock clock;
private final Tenants tenants;
private final File testApp;
@@ -95,6 +98,7 @@ public class DeployTester {
try {
this.testApp = new File(appPath);
this.tenants = new Tenants(componentRegistry, Collections.emptySet());
+ tenants.addTenant(tenantName);
}
catch (Exception e) {
throw new IllegalArgumentException(e);
@@ -103,7 +107,7 @@ public class DeployTester {
}
public Tenant tenant() {
- return tenants.defaultTenant();
+ return tenants.getTenant(tenantName);
}
/** Create a model factory for the version of this source*/
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java
index 4fc54943a79..051e7c9a8f9 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java
@@ -15,6 +15,11 @@ public class MockDeployer implements com.yahoo.config.provision.Deployer {
public ApplicationId lastDeployed;
@Override
+ public Optional<Deployment> deployFromLocalActive(ApplicationId application) {
+ return deployFromLocalActive(application, Duration.ofSeconds(60));
+ }
+
+ @Override
public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout) {
lastDeployed = application;
return Optional.empty();
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
index 76734aadae7..50e8c711330 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
@@ -7,7 +7,6 @@ import com.yahoo.config.model.api.ModelFactory;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
-import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Version;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.config.server.session.LocalSession;
@@ -60,7 +59,7 @@ public class RedeployTest {
modelFactories.add(DeployTester.createModelFactory(Clock.systemUTC()));
modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0)));
DeployTester tester = new DeployTester("ignored/app/path", modelFactories);
- ApplicationId id = ApplicationId.from(TenantName.from("default"),
+ ApplicationId id = ApplicationId.from(tester.tenant().getName(),
ApplicationName.from("default"),
InstanceName.from("default"));
assertFalse(tester.redeployFromLocalActive(id).isPresent());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java
new file mode 100644
index 00000000000..ad807f9527f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java
@@ -0,0 +1,69 @@
+// 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.FileReference;
+import com.yahoo.io.IOUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertTrue;
+
+public class FileDirectoryTest {
+
+ private FileDirectory fileDirectory;
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setup() {
+ fileDirectory = new FileDirectory(temporaryFolder.getRoot());
+ }
+
+ @Test
+ public void requireThatFileReferenceWithFilesWorks() throws IOException {
+ FileReference foo = createFile("foo");
+ FileReference bar = createFile("bar");
+
+ assertTrue(fileDirectory.getFile(foo).exists());
+ assertTrue(fileDirectory.getFile(bar).exists());
+ }
+
+
+ @Test
+ public void requireThatFileReferenceWithSubDirectoriesWorks() throws IOException {
+ FileDirectory fileDirectory = new FileDirectory(temporaryFolder.getRoot());
+
+ FileReference foo = createFileInSubDir("subdir", "foo");
+ FileReference bar = createFileInSubDir("subdir", "bar");
+
+ assertTrue(fileDirectory.getFile(foo).exists());
+ assertTrue(fileDirectory.getFile(bar).exists());
+ }
+
+ // Content in created file is equal to the filename string
+ private FileReference createFile(String filename) throws IOException {
+ File file = temporaryFolder.newFile(filename);
+ IOUtils.writeFile(file, filename, false);
+ return fileDirectory.addFile(file);
+ }
+
+ private FileReference createFileInSubDir(String subdirName, String filename) throws IOException {
+ File subDirectory = new File(temporaryFolder.getRoot(), subdirName);
+ if (!subDirectory.exists())
+ subDirectory.mkdirs();
+ File file = new File(subDirectory, filename);
+ IOUtils.writeFile(file, filename, false);
+ return fileDirectory.addFile(file);
+ }
+
+
+}
+
+
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java
index 4913798e5ad..b0dce359d58 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.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.config.server.filedistribution;
-import com.yahoo.config.FileReference;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.io.IOUtils;
+import com.yahoo.net.HostName;
+import com.yahoo.vespa.filedistribution.FileReferenceData;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -32,7 +35,7 @@ public class FileServerTest {
}
@Test
- public void requireThatExistingFileCanbeFound() throws IOException {
+ public void requireThatExistingFileCanBeFound() throws IOException {
createCleanDir("123");
IOUtils.writeFile("123/f1", "test", true);
assertTrue(fs.hasFile("123"));
@@ -47,16 +50,15 @@ public class FileServerTest {
cleanup();
}
- private static class FileReceiver implements FileServer.Receiver {
- CompletableFuture<byte []> content;
- FileReceiver(CompletableFuture<byte []> content) {
- this.content = content;
- }
- @Override
- public void receive(FileReference reference, String filename, byte[] content, FileServer.ReplayStatus status) {
- this.content.complete(content);
- }
+ @Test
+ public void requireThatFileReferenceWithDirectoryCanBeFound() throws IOException {
+ createCleanDir("124/subdir");
+ IOUtils.writeFile("124/subdir/f1", "test", false);
+ IOUtils.writeFile("124/subdir/f2", "test", false);
+ assertTrue(fs.hasFile("124/subdir"));
+ cleanup();
}
+
@Test
public void requireThatWeCanReplayFile() throws IOException, InterruptedException, ExecutionException {
createCleanDir("12y");
@@ -67,6 +69,44 @@ public class FileServerTest {
cleanup();
}
+ @Test
+ public void requireThatDifferentNumberOfConfigServersWork() throws IOException {
+ // Empty connection pool in tests etc.
+ ConfigserverConfig.Builder builder = new ConfigserverConfig.Builder();
+ FileServer fileServer = new FileServer(new ConfigserverConfig(builder));
+ assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize());
+
+ // Empty connection pool when only one server, no use in downloading from yourself
+ List<ConfigserverConfig.Zookeeperserver.Builder> servers = new ArrayList<>();
+ ConfigserverConfig.Zookeeperserver.Builder serverBuilder = new ConfigserverConfig.Zookeeperserver.Builder();
+ serverBuilder.hostname(HostName.getLocalhost());
+ serverBuilder.port(123456);
+ servers.add(serverBuilder);
+ builder.zookeeperserver(servers);
+ fileServer = new FileServer(new ConfigserverConfig(builder));
+ assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize());
+
+ // connection pool of size 1 when 2 servers
+ ConfigserverConfig.Zookeeperserver.Builder serverBuilder2 = new ConfigserverConfig.Zookeeperserver.Builder();
+ serverBuilder2.hostname("bar");
+ serverBuilder2.port(123456);
+ servers.add(serverBuilder2);
+ builder.zookeeperserver(servers);
+ fileServer = new FileServer(new ConfigserverConfig(builder));
+ assertEquals(1, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize());
+ }
+
+ private static class FileReceiver implements FileServer.Receiver {
+ CompletableFuture<byte []> content;
+ FileReceiver(CompletableFuture<byte []> content) {
+ this.content = content;
+ }
+ @Override
+ public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) {
+ this.content.complete(fileData.content().array());
+ }
+ }
+
private void cleanup() {
created.forEach((file) -> IOUtils.recursiveDeleteDir(file));
created.clear();
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
index 9d04b7e982d..6542c865d56 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
@@ -84,16 +84,24 @@ public class SessionActiveHandlerTest extends SessionHandlerTest {
private LocalSessionRepo localRepo;
private TenantApplications applicationRepo;
private MockProvisioner hostProvisioner;
+ private VespaModelFactory modelFactory;
+ private TestComponentRegistry componentRegistry;
@Before
public void setup() throws Exception {
- remoteSessionRepo = new RemoteSessionRepo();
+ remoteSessionRepo = new RemoteSessionRepo(tenant);
applicationRepo = new MemoryTenantApplications();
curator = new MockCurator();
configCurator = ConfigCurator.create(curator);
localRepo = new LocalSessionRepo(Clock.systemUTC());
pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
hostProvisioner = new MockProvisioner();
+ modelFactory = new VespaModelFactory(new NullConfigModelRegistry());
+ componentRegistry = new TestComponentRegistry.Builder()
+ .curator(curator)
+ .configCurator(configCurator)
+ .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory)))
+ .build();
}
@Test
@@ -211,14 +219,8 @@ public class SessionActiveHandlerTest extends SessionHandlerTest {
private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient, Clock clock) throws IOException {
zkClient.writeStatus(status);
ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId)));
- VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry());
zkC.write(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry()));
zkC.write(AllocatedHosts.withHosts(Collections.emptySet()));
- TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder()
- .curator(curator)
- .configCurator(configCurator)
- .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory)))
- .build();
RemoteSession session = new RemoteSession(TenantName.from("default"), sessionId, componentRegistry, zkClient, clock);
remoteSessionRepo.addSession(session);
return session;
@@ -246,7 +248,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest {
return activateRequest;
}
- private ActivateRequest activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock,
+ private void activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock,
int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception {
ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, "", clock);
activateRequest.invoke();
@@ -256,7 +258,6 @@ public class SessionActiveHandlerTest extends SessionHandlerTest {
String message = getRenderedString(actResponse);
assertThat(message, Is.is("{\"error-code\":\"" + errorCode.name() + "\",\"message\":\"" + expectedError + "\"}"));
assertThat(session.getStatus(), Is.is(Session.Status.PREPARE));
- return activateRequest;
}
private void testUnsupportedMethod(com.yahoo.container.jdisc.HttpRequest request) throws Exception {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
index 310342e81f1..65b12490b17 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
@@ -45,7 +45,6 @@ import static org.junit.Assert.fail;
/**
* @author hmusum
- * @since 5.1
*/
public class SessionCreateHandlerTest extends SessionHandlerTest {
@@ -106,7 +105,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
assertThat(factory.applicationName, is("ulfio"));
}
- protected void assertFromParameter(String expected, String from) throws IOException {
+ private void assertFromParameter(String expected, String from) throws IOException {
HttpRequest request = post(Collections.singletonMap("from", from));
MockSessionFactory factory = new MockSessionFactory();
factory.applicationPackage = testApp;
@@ -120,7 +119,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
expected + "/content/\",\"message\":\"Session " + expected + createdMessage + "}"));
}
- protected void assertIllegalFromParameter(String fromValue) throws IOException {
+ private void assertIllegalFromParameter(String fromValue) throws IOException {
File outFile = CompressedApplicationInputStreamTest.createTarFile();
HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue));
HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'");
@@ -218,7 +217,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz/instance");
}
- public SessionCreateHandler createHandler() {
+ private SessionCreateHandler createHandler() {
try {
return createHandler(new MockSessionFactory());
} catch (Exception e) {
@@ -228,7 +227,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
return null;
}
- public SessionCreateHandler createHandler(SessionFactory sessionFactory) {
+ private SessionCreateHandler createHandler(SessionFactory sessionFactory) {
try {
TestTenantBuilder testBuilder = new TestTenantBuilder();
testBuilder.createTenant(tenant).withSessionFactory(sessionFactory)
@@ -250,15 +249,15 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
Clock.systemUTC()));
}
- public HttpRequest post() throws FileNotFoundException {
+ private HttpRequest post() throws FileNotFoundException {
return post(null, postHeaders, new HashMap<>());
}
- public HttpRequest post(File file) throws FileNotFoundException {
+ private HttpRequest post(File file) throws FileNotFoundException {
return post(file, postHeaders, new HashMap<>());
}
- public HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException {
+ private HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException {
HttpRequest request = HttpRequest.createTestRequest("http://" + hostname + ":" + port + "/application/v2/tenant/" + tenant + "/session",
POST,
file == null ? null : new FileInputStream(file),
@@ -269,7 +268,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
return request;
}
- public HttpRequest post(Map<String, String> parameters) throws FileNotFoundException {
+ private HttpRequest post(Map<String, String> parameters) throws FileNotFoundException {
return post(null, new HashMap<>(), parameters);
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
index 7900a67bddd..74a2dcf8054 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
@@ -32,7 +32,6 @@ import com.yahoo.vespa.config.server.http.*;
import com.yahoo.vespa.config.server.session.*;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.mock.MockCurator;
-import org.apache.commons.lang.NullArgumentException;
import org.junit.Before;
import org.junit.Test;
@@ -148,7 +147,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
* A mock remote session repo based on contents of local repo
*/
private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo, Clock clock) {
- RemoteSessionRepo remoteRepo = new RemoteSessionRepo();
+ RemoteSessionRepo remoteRepo = new RemoteSessionRepo(tenant);
for (LocalSession ls : localRepo.listSessions()) {
zooKeeperClient = new MockSessionZKClient(curator, tenant, ls.getSessionId());
@@ -239,7 +238,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
// Need different repos for 'default' tenant as opposed to the 'test' tenant
LocalSessionRepo localRepoDefault = new LocalSessionRepo(Clock.systemUTC());
final TenantName defaultTenant = TenantName.defaultName();
- addTenant(defaultTenant, localRepoDefault, new RemoteSessionRepo(), new MockSessionFactory());
+ addTenant(defaultTenant, localRepoDefault, new RemoteSessionRepo(tenant), new MockSessionFactory());
addTestTenant();
final SessionHandler handler = createHandler(builder);
@@ -379,7 +378,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
}
private TestTenantBuilder addTestTenant() {
- return addTenant(tenant, localRepo, new RemoteSessionRepo(), new MockSessionFactory());
+ return addTenant(tenant, localRepo, new RemoteSessionRepo(tenant), new MockSessionFactory());
}
private SessionHandler createHandler(TestTenantBuilder builder) {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java
index 16ce605d4d1..32f29858b74 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java
@@ -19,7 +19,6 @@ import java.util.*;
* Test utility for creating tenants used for testing and setup wiring of tenant stuff.
*
* @author Ulf Lilleengen
- * @since 5.1
*/
public class TestTenantBuilder {
@@ -35,7 +34,7 @@ public class TestTenantBuilder {
TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName)
.withSessionFactory(new SessionCreateHandlerTest.MockSessionFactory())
.withLocalSessionRepo(new LocalSessionRepo(componentRegistry.getClock()))
- .withRemoteSessionRepo(new RemoteSessionRepo())
+ .withRemoteSessionRepo(new RemoteSessionRepo(tenantName))
.withApplicationRepo(applicationRepo);
tenantMap.put(tenantName, builder);
return builder;
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java
index ce9de5e066f..d32b3a9e1a6 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java
@@ -10,8 +10,7 @@ import com.yahoo.vespa.curator.mock.MockCurator;
import java.io.File;
/**
-* @author lulf
-* @since 5.1
+* @author Ulf Lilleengen
*/
public class MockFileDistributionFactory extends FileDistributionFactory {
@@ -27,7 +26,7 @@ public class MockFileDistributionFactory extends FileDistributionFactory {
}
@Override
- public FileDistributionProvider createProvider(File applicationFile, ApplicationId applicationId) {
+ public FileDistributionProvider createProvider(File applicationFile, ApplicationId applicationId, boolean disableFileDistributor) {
return mockFileDistributionProvider;
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java
index e650997b7e0..617ad8733f9 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java
@@ -125,13 +125,10 @@ public class TenantsTestCase extends TestWithCurator {
public void testTenantsChanged() throws Exception {
tenants.close(); // close the Tenants instance created in setupSession, we do not want to use one with a PatchChildrenCache listener
tenants = new Tenants(globalComponentRegistry, new ArrayList<>());
- TenantName defaultTenant = TenantName.defaultName();
tenants.addTenant(tenant2);
tenants.createTenants();
Set<TenantName> allTenants = tenants.getAllTenantNames();
assertTrue(allTenants.contains(tenant2));
- assertEquals("default", defaultTenant.value());
- assertTrue(allTenants.contains(defaultTenant));
tenants.deleteTenant(tenant1);
tenants.deleteTenant(tenant2);
tenants.createTenants();
diff --git a/container-accesslogging/pom.xml b/container-accesslogging/pom.xml
index 45d7b4a2ed2..b4c7094a50a 100644
--- a/container-accesslogging/pom.xml
+++ b/container-accesslogging/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-accesslogging</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
index 9120c747293..24078151d64 100644
--- a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
+++ b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
@@ -4,6 +4,7 @@ package com.yahoo.container.logging;
import com.yahoo.collections.ListMap;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import javax.security.auth.x500.X500Principal;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
@@ -94,6 +95,7 @@ public class AccessLogEntry {
private String scheme;
private int localPort;
private Principal principal;
+ private X500Principal sslPrincipal;
private ListMap<String,String> keyValues=null;
@@ -724,6 +726,19 @@ public class AccessLogEntry {
}
}
+ public Principal getSslPrincipal() {
+ synchronized (monitor) {
+ return sslPrincipal;
+ }
+ }
+
+ public void setSslPrincipal(X500Principal sslPrincipal) {
+ synchronized (monitor) {
+ requireNull(this.sslPrincipal);
+ this.sslPrincipal = sslPrincipal;
+ }
+ }
+
@Override
public String toString() {
synchronized (monitor) {
diff --git a/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java b/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java
index cca8da2e936..85026296363 100644
--- a/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java
+++ b/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java
@@ -65,10 +65,12 @@ public class JSONFormatter {
Principal principal = accessLogEntry.getUserPrincipal();
if (principal != null) {
- generator.writeObjectFieldStart("user-principal");
- generator.writeStringField("name", principal.getName());
- generator.writeStringField("type", principal.getClass().getName());
- generator.writeEndObject();
+ generator.writeStringField("user-principal", principal.getName());
+ }
+
+ Principal sslPrincipal = accessLogEntry.getSslPrincipal();
+ if (sslPrincipal != null) {
+ generator.writeStringField("ssl-principal", sslPrincipal.getName());
}
// Only add remote address/port fields if relevant
diff --git a/container-core/pom.xml b/container-core/pom.xml
index c608f35f26c..1be10215419 100644
--- a/container-core/pom.xml
+++ b/container-core/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-core</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
index ce9779b83d9..23c247fa438 100644
--- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
+++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
@@ -190,7 +190,7 @@ public class StateHandler extends AbstractRequestHandler {
private JSONObjectWithLegibleException buildJsonForConsumer(String consumer) throws JSONException {
JSONObjectWithLegibleException ret = new JSONObjectWithLegibleException();
ret.put("time", timer.currentTimeMillis());
- ret.put("status", new JSONObjectWithLegibleException().put("code", "up"));
+ ret.put("status", new JSONObjectWithLegibleException().put("code", getStatus().name()));
ret.put(METRICS_PATH, buildJsonForSnapshot(consumer, getSnapshot()));
return ret;
}
@@ -203,6 +203,10 @@ public class StateHandler extends AbstractRequestHandler {
}
}
+ private StateMonitor.Status getStatus() {
+ return monitor.status();
+ }
+
private JSONObjectWithLegibleException buildJsonForSnapshot(String consumer, MetricSnapshot metricSnapshot) throws JSONException {
if (metricSnapshot == null) {
return new JSONObjectWithLegibleException();
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java
index 140257cbcef..6234a96d7a0 100644
--- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java
+++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java
@@ -6,6 +6,7 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.config.HealthMonitorConfig;
import com.yahoo.jdisc.Timer;
import com.yahoo.jdisc.application.MetricConsumer;
+import com.yahoo.log.LogLevel;
import java.util.Map;
import java.util.TreeSet;
@@ -14,7 +15,7 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
- * A statemonitor keeps track of the current metrics state of a container.
+ * A state monitor keeps track of the current health and metrics state of a container.
* It is used by jDisc to hand out metric update API endpoints to workers through {@link #newMetricConsumer},
* and to inspect the current accumulated state of metrics through {@link #snapshot}.
*
@@ -23,12 +24,16 @@ import java.util.logging.Logger;
public class StateMonitor extends AbstractComponent {
private final static Logger log = Logger.getLogger(StateMonitor.class.getName());
+
+ public enum Status {up, down, initializing};
+
private final CopyOnWriteArrayList<StateMetricConsumer> consumers = new CopyOnWriteArrayList<>();
private final Thread thread;
private final Timer timer;
private final long snapshotIntervalMs;
private long lastSnapshotTimeMs;
private volatile MetricSnapshot snapshot;
+ private volatile Status status;
private final TreeSet<String> valueNames = new TreeSet<>();
@Inject
@@ -36,13 +41,8 @@ public class StateMonitor extends AbstractComponent {
this.timer = timer;
this.snapshotIntervalMs = (long)(config.snapshot_interval() * TimeUnit.SECONDS.toMillis(1));
this.lastSnapshotTimeMs = timer.currentTimeMillis();
- thread = new Thread(new Runnable() {
-
- @Override
- public void run() {
- StateMonitor.this.run();
- }
- }, "StateMonitor");
+ this.status = Status.valueOf(config.initialStatus());
+ thread = new Thread(StateMonitor.this::run, "StateMonitor");
thread.setDaemon(true);
thread.start();
}
@@ -54,6 +54,13 @@ public class StateMonitor extends AbstractComponent {
return consumer;
}
+ public void status(Status status) {
+ log.log(LogLevel.INFO, "Changing health status code from '" + this.status + "' to '" + status.name() + "'");
+ this.status = status;
+ }
+
+ public Status status() { return status; }
+
/** Returns the last snapshot taken of the metrics in this system */
public MetricSnapshot snapshot() {
return snapshot;
diff --git a/container-core/src/main/resources/configdefinitions/health-monitor.def b/container-core/src/main/resources/configdefinitions/health-monitor.def
index dc5cdbc6ca4..5e70c72ae3f 100644
--- a/container-core/src/main/resources/configdefinitions/health-monitor.def
+++ b/container-core/src/main/resources/configdefinitions/health-monitor.def
@@ -4,3 +4,6 @@ namespace=container.jdisc.config
# How far between snapshots. 5 minutes by default
snapshot_interval double default=300
+
+# Initial status used in /state/v1/health API (value for 'code' in 'status'). See StateMonitor for valid values
+initialStatus string default="up"
diff --git a/container-dependencies-enforcer/pom.xml b/container-dependencies-enforcer/pom.xml
index 1b0a7f9e958..3384da7c1eb 100644
--- a/container-dependencies-enforcer/pom.xml
+++ b/container-dependencies-enforcer/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-dependencies-enforcer</artifactId>
@@ -62,8 +63,9 @@
<rules>
<bannedDependencies>
<excludes>
- <!-- Only allow explicitly listed deps in provided scope -->
+ <!-- Only allow explicitly listed deps in provided and compile scope -->
<exclude>*:*:*:jar:provided:*</exclude>
+ <exclude>*:*:*:jar:compile:*</exclude>
</excludes>
<includes>
<include>com.yahoo.vespa</include>
@@ -80,36 +82,36 @@
<include>com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:[2.5.4, ${jackson2.version}]:jar:provided</include>
<include>com.fasterxml.jackson.module:jackson-module-jaxb-annotations:[2.5.4, ${jackson2.version}]:jar:provided</include>
- <include>com.google.code.findbugs:annotations:[1.3.9]:jar:provided</include>
- <include>com.google.code.findbugs:jsr305:[1.3.9]:jar:provided</include>
- <include>com.google.guava:guava:[18.0]:jar:provided</include>
- <include>com.google.inject.extensions:guice-assistedinject:[3.0]:jar:provided</include>
- <include>com.google.inject.extensions:guice-multibindings:[3.0]:jar:provided</include>
- <include>com.google.inject:guice:[3.0]:jar:provided:no_aop</include>
+ <include>com.google.code.findbugs:annotations:[${findbugs.version}]:jar:provided</include>
+ <include>com.google.code.findbugs:jsr305:[${findbugs.version}]:jar:provided</include>
+ <include>com.google.guava:guava:[${guava.version}]:jar:provided</include>
+ <include>com.google.inject.extensions:guice-assistedinject:[${guice.version}]:jar:provided</include>
+ <include>com.google.inject.extensions:guice-multibindings:[${guice.version}]:jar:provided</include>
+ <include>com.google.inject:guice:[${guice.version}]:jar:provided:no_aop</include>
<include>commons-codec:commons-codec:[1.4]:jar:provided</include>
<include>commons-daemon:commons-daemon:[1.0.3]:jar:provided</include>
<include>commons-logging:commons-logging:[1.1.1]:jar:provided</include>
- <include>javax.annotation:javax.annotation-api:[1.2]:jar:provided</include>
+ <include>javax.annotation:javax.annotation-api:[${javax.annotation-api.version}]:jar:provided</include>
<include>javax.inject:javax.inject:[1]:jar:provided</include>
<include>javax.servlet:javax.servlet-api:[3.1.0]:jar:provided</include>
- <include>javax.validation:validation-api:[1.1.0.Final]:jar:provided</include>
+ <include>javax.validation:validation-api:[${javax.validation-api.version}]:jar:provided</include>
<include>javax.ws.rs:javax.ws.rs-api:[${javax.ws.rs-api.version}]:jar:provided</include>
<include>net.jcip:jcip-annotations:[1.0]:jar:provided</include>
<include>net.jpountz.lz4:lz4:[1.3.0]:jar:provided</include>
- <include>org.apache.felix:org.apache.felix.framework:[4.2.1]:jar:provided</include>
+ <include>org.apache.felix:org.apache.felix.framework:[${felix.version}]:jar:provided</include>
<include>org.apache.felix:org.apache.felix.log:[1.0.1]:jar:provided</include>
- <include>org.apache.felix:org.apache.felix.main:[4.2.1]:jar:provided</include>
+ <include>org.apache.felix:org.apache.felix.main:[${felix.version}]:jar:provided</include>
<include>org.apache.httpcomponents:httpclient:[4.3.6]:jar:provided</include>
<include>org.apache.httpcomponents:httpcore:[4.3.3]:jar:provided</include>
<include>org.eclipse.jetty:jetty-http:[${jetty.version}]:jar:provided</include>
<include>org.eclipse.jetty:jetty-io:[${jetty.version}]:jar:provided</include>
<include>org.eclipse.jetty:jetty-util:[${jetty.version}]:jar:provided</include>
- <include>org.glassfish.hk2.external:aopalliance-repackaged:[2.5.0-b05]:jar:provided</include>
- <include>org.glassfish.hk2.external:javax.inject:[2.5.0-b05]:jar:provided</include>
- <include>org.glassfish.hk2:hk2-api:[2.5.0-b05]:jar:provided</include>
- <include>org.glassfish.hk2:hk2-locator:[2.5.0-b05]:jar:provided</include>
- <include>org.glassfish.hk2:hk2-utils:[2.5.0-b05]:jar:provided</include>
- <include>org.glassfish.hk2:osgi-resource-locator:[1.0.1]:jar:provided</include>
+ <include>org.glassfish.hk2.external:aopalliance-repackaged:[${hk2.version}]:jar:provided</include>
+ <include>org.glassfish.hk2.external:javax.inject:[${hk2.version}]:jar:provided</include>
+ <include>org.glassfish.hk2:hk2-api:[${hk2.version}]:jar:provided</include>
+ <include>org.glassfish.hk2:hk2-locator:[${hk2.version}]:jar:provided</include>
+ <include>org.glassfish.hk2:hk2-utils:[${hk2.version}]:jar:provided</include>
+ <include>org.glassfish.hk2:osgi-resource-locator:[${hk2.osgi-resource-locator.version}]:jar:provided</include>
<include>org.glassfish.jersey.bundles.repackaged:jersey-guava:[${jersey2.version}]:jar:provided</include>
<include>org.glassfish.jersey.containers:jersey-container-servlet-core:[${jersey2.version}]:jar:provided</include>
<include>org.glassfish.jersey.containers:jersey-container-servlet:[${jersey2.version}]:jar:provided</include>
@@ -121,13 +123,13 @@
<include>org.glassfish.jersey.media:jersey-media-jaxb:[${jersey2.version}]:jar:provided</include>
<include>org.glassfish.jersey.media:jersey-media-json-jackson:[${jersey2.version}]:jar:provided</include>
<include>org.glassfish.jersey.media:jersey-media-multipart:[${jersey2.version}]:jar:provided</include>
- <include>org.javassist:javassist:[3.20.0-GA]:jar:provided</include>
+ <include>org.javassist:javassist:[${javassist.version}]:jar:provided</include>
<include>org.json:json:[20090211]:jar:provided</include>
- <include>org.jvnet.mimepull:mimepull:[1.9.6]:jar:provided</include>
- <include>org.slf4j:jcl-over-slf4j:[1.7.5]:jar:provided</include>
- <include>org.slf4j:log4j-over-slf4j:[1.7.5]:jar:provided</include>
- <include>org.slf4j:slf4j-api:[1.7.5]:jar:provided</include>
- <include>org.slf4j:slf4j-jdk14:[1.7.5]:jar:provided</include>
+ <include>org.jvnet.mimepull:mimepull:[${mimepull.version}]:jar:provided</include>
+ <include>org.slf4j:jcl-over-slf4j:[${slf4j.version}]:jar:provided</include>
+ <include>org.slf4j:log4j-over-slf4j:[${slf4j.version}]:jar:provided</include>
+ <include>org.slf4j:slf4j-api:[${slf4j.version}]:jar:provided</include>
+ <include>org.slf4j:slf4j-jdk14:[${slf4j.version}]:jar:provided</include>
<include>xml-apis:xml-apis:[1.4.01]:jar:provided</include>
</includes>
</bannedDependencies>
diff --git a/container-dependency-versions/pom.xml b/container-dependency-versions/pom.xml
new file mode 100644
index 00000000000..4d639ceedab
--- /dev/null
+++ b/container-dependency-versions/pom.xml
@@ -0,0 +1,441 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>container-dependency-versions</artifactId>
+ <packaging>pom</packaging>
+ <version>6-SNAPSHOT</version>
+ <name>container-dependency-versions</name>
+ <description>Versions for all 3rd party dependencies provided from the Vespa container.</description>
+ <url>https://github.com/vespa-engine</url>
+
+ <licenses>
+ <license>
+ <name>The Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ </license>
+ </licenses>
+ <developers>
+ <developer>
+ <name>Vespa</name>
+ <url>https://github.com/vespa-engine</url>
+ </developer>
+ </developers>
+ <distributionManagement>
+ <repository>
+ <id>bintray-vespa-repo</id>
+ <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url>
+ </repository>
+ </distributionManagement>
+ <scm>
+ <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection>
+ <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection>
+ <url>git@github.com:vespa-engine/vespa.git</url>
+ </scm>
+
+
+ <!-- TODO: add pluginManagement for bundle-plugin and/or compiler-plugin?
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.6.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <optimize>true</optimize>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <configGenVersion>${project.version}</configGenVersion>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ -->
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>aopalliance</groupId>
+ <artifactId>aopalliance</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jdk8</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-base</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${findbugs.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>${findbugs.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${guava.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-assistedinject</artifactId>
+ <version>${guice.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-multibindings</artifactId>
+ <version>${guice.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <version>${guice.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <version>${guice.version}</version>
+ <classifier>no_aop</classifier>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-daemon</groupId>
+ <artifactId>commons-daemon</artifactId>
+ <version>1.0.3</version>
+ </dependency>
+ <dependency>
+ <!-- This version is exported by jdisc via jcl-over-slf4j. -->
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>${javax.annotation-api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>${javax.validation-api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <version>${javax.ws.rs-api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.jcip</groupId>
+ <artifactId>jcip-annotations</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>net.jpountz.lz4</groupId>
+ <artifactId>lz4</artifactId>
+ <version>1.3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>${felix.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.log</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.main</artifactId>
+ <version>${felix.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.3.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <version>4.3.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>aopalliance-repackaged</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-api</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-locator</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-utils</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>osgi-resource-locator</artifactId>
+ <version>${hk2.osgi-resource-locator.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.bundles.repackaged</groupId>
+ <artifactId>jersey-guava</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-entity-filtering</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-proxy-client</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-jaxb</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-multipart</artifactId>
+ <version>${jersey2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <version>${javassist.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20090211</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jvnet.mimepull</groupId>
+ <artifactId>mimepull</artifactId>
+ <version>${mimepull.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>log4j-over-slf4j</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ <version>1.4.01</version>
+ </dependency>
+
+ <!-- NOTE: The dependencies below are not provided from the jdisc container runtime, but had to be moved
+ here from 'parent' because factorylib reads the text in parent/pom.xml and this pom file to
+ build a pom model used to bootstrap the maven cache on factory. Hence all deps using properties
+ declared in this pom also have to reside in this pom.
+ See factorylib:com.yahoo.vespa.dependencies.pom.list.Main.-->
+ <!-- TODO: move these back to parent/pom.xml when the above does not hold anymore. -->
+
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-xml-provider</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ <version>${jackson2.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-continuation</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlets</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <!-- NOT provided from jdisc runtime -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-jmx</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+
+ <!-- Please don't add deps here, but instead above the NOTE. -->
+
+ </dependencies>
+ </dependencyManagement>
+
+ <properties>
+ <felix.version>4.2.1</felix.version>
+ <findbugs.version>1.3.9</findbugs.version>
+ <guava.version>18.0</guava.version>
+ <guice.version>3.0</guice.version>
+ <jetty.version>9.4.8.v20171121</jetty.version>
+ <slf4j.version>1.7.5</slf4j.version>
+
+ <!-- These must be kept in sync with version used by current jersey2.version. -->
+ <!-- MUST be updated each time jersey2 is upgraded! -->
+ <!-- Check versions by doing: ' ls -l vespa/vespa_jersey2/target/dependency' -->
+ <hk2.version>2.5.0-b05</hk2.version>
+ <hk2.osgi-resource-locator.version>1.0.1</hk2.osgi-resource-locator.version>
+ <jackson2.version>2.8.3</jackson2.version>
+ <javassist.version>3.20.0-GA</javassist.version>
+ <javax.annotation-api.version>1.2</javax.annotation-api.version>
+ <javax.validation-api.version>1.1.0.Final</javax.validation-api.version>
+ <javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version>
+ <jersey2.version>2.23.2</jersey2.version>
+ <mimepull.version>1.9.6</mimepull.version>
+ </properties>
+
+</project>
diff --git a/container-dev-builder/OWNERS b/container-dev-builder/OWNERS
deleted file mode 100644
index 3b2ba1ede81..00000000000
--- a/container-dev-builder/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-gjoranv
diff --git a/container-dev-builder/README.md b/container-dev-builder/README.md
deleted file mode 100644
index 3218833930a..00000000000
--- a/container-dev-builder/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
-# container-dev-builder
-
-TODO
diff --git a/container-dev-builder/dependency_blacklist b/container-dev-builder/dependency_blacklist
deleted file mode 100644
index 7b0d647464d..00000000000
--- a/container-dev-builder/dependency_blacklist
+++ /dev/null
@@ -1,2 +0,0 @@
-antlr:antlr
-junit:junit
diff --git a/container-dev-builder/make.sh b/container-dev-builder/make.sh
deleted file mode 100755
index c434b13ee7b..00000000000
--- a/container-dev-builder/make.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/bin/bash
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-export VERSION=$1
-if [ -z "$VERSION" ]
-then
- echo "usage: $(basename $0) <version> [<builddir>]"
- exit 1
-fi
-echo VERSION=$VERSION
-
-export BASEDIR=$2
-if [ "$BASEDIR" ]
-then
- export POM_FORMAT="$BASEDIR/%s-HEAD/%s/pom.xml"
-else
- export POM_FORMAT="$(pwd)/../%s/pom.xml"
-fi
-echo POM_FORMAT=$POM_FORMAT
-echo
-
-echo "-------------------------------------------------------------------------------"
-echo "Compiling toolkit."
-echo "-------------------------------------------------------------------------------"
-cd tools
-mvn -q clean install -DskipTests
-JAVA_TOOLS="java -cp $(pwd)/target/tools-jar-with-dependencies.jar"
-echo
-cd ..
-
-DEPS_NEXT="com.yahoo.vespa:container-disc:jar:$VERSION:compile"
-DEPS_PREV=""
-BLACKLIST=$(cat dependency_blacklist)
-
-PASS=0
-rm -rf target
-mkdir target
-cd target
-while [ "$DEPS_NEXT" ]
-do
- PASS=$((PASS+1))
- mkdir -p "pass$PASS"
- cd "pass$PASS"
-
- DEPS_PREV=$DEPS_NEXT
- DEPS_NEXT=""
-
- echo "-------------------------------------------------------------------------------"
- echo "Deriving dependencies, pass $PASS."
- echo "-------------------------------------------------------------------------------"
- echo DEPENDENCIES=$DEPS_PREV
- echo "Building dependency tree.."
- $JAVA_TOOLS com.yahoo.container.dev.builder.PomFileGenerator $VERSION $DEPS_PREV > pom.xml
- mvn -q dependency:tree -DoutputFile=dependencies
- DEPS_NEXT="$($JAVA_TOOLS com.yahoo.container.dev.builder.DependencyResolver . $BLACKLIST)"
-
- echo "Resolving X-JDisc-Preinstall-Bundle instructions.."
- mvn -q dependency:unpack-dependencies \
- -DexcludeTransitive=true \
- -Dmdep.unpack.includes="META-INF/MANIFEST.MF" \
- -Dmdep.useSubDirectoryPerArtifact=true \
- -DoutputDirectory=.
- DEPS_NEXT="$DEPS_NEXT $($JAVA_TOOLS com.yahoo.container.dev.builder.PreinstalledBundleResolver . "$POM_FORMAT")"
-
- DEPS_NEXT=$(echo $DEPS_NEXT | sort | uniq)
- [ "$DEPS_NEXT" == "$DEPS_PREV" ] && DEPS_NEXT=""
- echo
- cd ..
-done
-
-echo "-------------------------------------------------------------------------------"
-echo "Testing final pom.xml"
-echo "-------------------------------------------------------------------------------"
-cp pass$PASS/pom.xml .
-mvn clean install -DskipTests || exit 1
-cd ..
diff --git a/container-dev-builder/tools/pom.xml b/container-dev-builder/tools/pom.xml
deleted file mode 100644
index 3c1132e439d..00000000000
--- a/container-dev-builder/tools/pom.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0"?>
-<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
- http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>tools</groupId>
- <artifactId>tools</artifactId>
- <version>1.0</version>
- <dependencies>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-core</artifactId>
- <version>3.1.1</version>
- </dependency>
- </dependencies>
- <build>
- <finalName>${project.artifactId}</finalName>
- <plugins>
- <plugin>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.6.1</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-assembly-plugin</artifactId>
- <version>2.4</version>
- <configuration>
- <descriptorRefs>
- <descriptorRef>jar-with-dependencies</descriptorRef>
- </descriptorRefs>
- </configuration>
- <executions>
- <execution>
- <id>make-assembly</id>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
-</project>
diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java
deleted file mode 100644
index 5ebf5488b02..00000000000
--- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java
+++ /dev/null
@@ -1,58 +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.dev.builder;
-
-import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
- */
-public class DependencyResolver {
-
- private static final Path DEPENDENCIES = Paths.get("dependencies");
-
- public static void main(String[] args) throws IOException, XmlPullParserException {
- final Set<String> blacklist = new HashSet<>(Arrays.asList(args).subList(1, args.length));
- final Set<String> dependencies = new TreeSet<>();
- Files.walkFileTree(Paths.get(args[0]), new SimpleFileVisitor<Path>() {
-
- @Override
- public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
- if (!attrs.isRegularFile()) {
- return FileVisitResult.CONTINUE;
- }
- if (!file.getFileName().equals(DEPENDENCIES)) {
- return FileVisitResult.CONTINUE;
- }
- for (final String line : Files.readAllLines(file, StandardCharsets.UTF_8)) {
- for (final String dependency : line.split(" ")) {
- if (dependency == null || dependency.isEmpty()) {
- continue;
- }
- final String[] arr = dependency.split(":");
- if (arr.length != 5 || blacklist.contains(arr[0] + ":" + arr[1])) {
- continue;
- }
- dependencies.add(dependency);
- }
- }
- return FileVisitResult.CONTINUE;
- }
- });
- for (final String dependency : dependencies) {
- System.out.println(dependency);
- }
- }
-}
diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java
deleted file mode 100644
index 7eb884034d6..00000000000
--- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java
+++ /dev/null
@@ -1,50 +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.dev.builder;
-
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.TreeSet;
-
-/**
- * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
- */
-public class PomFileGenerator {
-
- public static void main(String[] args) throws IOException {
- Model model = new Model();
- model.setModelVersion("4.0.0");
- model.setGroupId("com.yahoo.vespa");
- model.setArtifactId("container-dev");
- model.setVersion(args[0]);
- model.getProperties().setProperty("project.build.sourceEncoding", StandardCharsets.UTF_8.name());
- for (String str : new TreeSet<>(Arrays.asList(args).subList(1, args.length))) {
- Dependency dependency = newDependency(str);
- if (dependency == null) {
- continue;
- }
- if (dependency.getGroupId().equals(model.getGroupId()) &&
- dependency.getArtifactId().equals(model.getArtifactId())) {
- continue;
- }
- model.addDependency(dependency);
- }
- new MavenXpp3Writer().write(System.out, model);
- }
-
- private static Dependency newDependency(String str) {
- String[] arr = str.split(":");
- if (arr.length != 5) {
- return null;
- }
- Dependency out = new Dependency();
- out.setGroupId(arr[0]);
- out.setArtifactId(arr[1]);
- out.setVersion(arr[3]);
- return out;
- }
-}
diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java
deleted file mode 100644
index b9187f5a026..00000000000
--- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java
+++ /dev/null
@@ -1,79 +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.dev.builder;
-
-import org.apache.maven.model.Model;
-import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
-import org.apache.maven.project.MavenProject;
-import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.jar.Manifest;
-
-public class PreinstalledBundleResolver {
-
- private static final Path MANIFEST_MF = Paths.get("MANIFEST.MF");
- private static final String X_JDISC_PREINSTALL_BUNDLE = "X-JDisc-Preinstall-Bundle";
- private static final String REMOVABLE_SUFFIX = ".jar";
- private static final String REMOVABLE_ASSEMBLY_ID = "-jar-with-dependencies";
-
- public static void main(final String[] args) throws Throwable {
- Files.walkFileTree(Paths.get(args[0]), new SimpleFileVisitor<Path>() {
-
- @Override
- public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
- if (!attrs.isRegularFile()) {
- return FileVisitResult.CONTINUE;
- }
- if (!file.getFileName().equals(MANIFEST_MF)) {
- return FileVisitResult.CONTINUE;
- }
- final String preinstall = new Manifest(Files.newInputStream(file))
- .getMainAttributes()
- .getValue(X_JDISC_PREINSTALL_BUNDLE);
- if (preinstall == null) {
- return FileVisitResult.CONTINUE;
- }
- for (String bundle : preinstall.split(",")) {
- printDependency(args[1], bundle);
- }
- return super.visitFile(file, attrs);
- }
- });
- }
-
- private static void printDependency(String pomFormat, String bundle) throws IOException {
- bundle = bundle.trim();
- if (bundle.isEmpty()) {
- return;
- }
- if (bundle.endsWith(REMOVABLE_SUFFIX)) {
- bundle = bundle.substring(0, bundle.length() - REMOVABLE_SUFFIX.length());
- }
- if (bundle.endsWith(REMOVABLE_ASSEMBLY_ID)) {
- bundle = bundle.substring(0, bundle.length() - REMOVABLE_ASSEMBLY_ID.length());
- }
- Path pom = Paths.get(String.format(pomFormat, bundle));
- if (!Files.exists(pom)) {
- return;
- }
- Model model;
- try {
- model = new MavenXpp3Reader().read(Files.newBufferedReader(pom, StandardCharsets.UTF_8));
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- return;
- }
- model.setPomFile(pom.toFile());
- final MavenProject project = new MavenProject(model);
- System.out.println(project.getGroupId() + ":" +
- project.getArtifactId() + ":jar:" +
- project.getVersion() + ":compile");
- }
-}
diff --git a/container-dev/pom.xml b/container-dev/pom.xml
index d02ec233d96..f62bbd22690 100644
--- a/container-dev/pom.xml
+++ b/container-dev/pom.xml
@@ -10,6 +10,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-dev</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-di/pom.xml b/container-di/pom.xml
index cf6a9e54daa..abc652d6e6e 100644
--- a/container-di/pom.xml
+++ b/container-di/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-di</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-disc/pom.xml b/container-disc/pom.xml
index ad86b3ffcff..38b4bfc2ff5 100644
--- a/container-disc/pom.xml
+++ b/container-disc/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-disc</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml
index 8f579e49876..26dfa762032 100644
--- a/container-jersey2/pom.xml
+++ b/container-jersey2/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-jersey2</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-messagebus/pom.xml b/container-messagebus/pom.xml
index 6058063daf3..1daf1df6fcb 100644
--- a/container-messagebus/pom.xml
+++ b/container-messagebus/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-messagebus</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-search-and-docproc/pom.xml b/container-search-and-docproc/pom.xml
index fb2794670a2..9b4419ae2b6 100644
--- a/container-search-and-docproc/pom.xml
+++ b/container-search-and-docproc/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-search-and-docproc</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container-search/pom.xml b/container-search/pom.xml
index f622567acde..0df502cf21a 100644
--- a/container-search/pom.xml
+++ b/container-search/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-search</artifactId>
<packaging>jar</packaging>
diff --git a/container-test-jars/pom.xml b/container-test-jars/pom.xml
index dcde18fc8cf..804ca5eb879 100644
--- a/container-test-jars/pom.xml
+++ b/container-test-jars/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa.container-test-jars</groupId>
<artifactId>container-test-jars</artifactId>
diff --git a/container-test/pom.xml b/container-test/pom.xml
index 7aaffed652e..26f02767062 100644
--- a/container-test/pom.xml
+++ b/container-test/pom.xml
@@ -11,6 +11,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container-test</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/container/pom.xml b/container/pom.xml
index 4726071db55..3793a3508a4 100644
--- a/container/pom.xml
+++ b/container/pom.xml
@@ -11,6 +11,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>container</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/controller-api/pom.xml b/controller-api/pom.xml
index 51666da0c03..5ef130a22ba 100644
--- a/controller-api/pom.xml
+++ b/controller-api/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>controller-api</artifactId>
<packaging>container-plugin</packaging>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java
index 80fe98a4489..f1c584c7a3c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java
@@ -1,57 +1,54 @@
// 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.identifiers;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
+
+import java.util.Objects;
/**
- * Application + zone.
+ * ApplicationId x ZoneId.
*
* @author smorgrav
* @author bratseth
*/
public class DeploymentId {
- private final com.yahoo.config.provision.ApplicationId application;
- private final Zone zone;
+ private final com.yahoo.config.provision.ApplicationId applicationId;
+ private final ZoneId zoneId;
- public DeploymentId(com.yahoo.config.provision.ApplicationId application, Zone zone) {
- this.application = application;
- this.zone = zone;
+ public DeploymentId(com.yahoo.config.provision.ApplicationId applicationId, ZoneId zoneId) {
+ this.applicationId = applicationId;
+ this.zoneId = zoneId;
}
public com.yahoo.config.provision.ApplicationId applicationId() {
- return application;
+ return applicationId;
}
- public Zone zone() { return zone; }
+ public ZoneId zoneId() {
+ return zoneId;
+ }
public String dottedString() {
return unCapitalize(applicationId().tenant().value()) + "."
- + unCapitalize(applicationId().application().value()) + "."
- + unCapitalize(zone.environment().value()) + "."
- + unCapitalize(zone.region().value()) + "."
- + unCapitalize(application.instance().value());
+ + unCapitalize(applicationId().application().value()) + "."
+ + unCapitalize(zoneId.environment().value()) + "."
+ + unCapitalize(zoneId.region().value()) + "."
+ + unCapitalize(applicationId.instance().value());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- DeploymentId other = (DeploymentId) o;
- if ( ! this.application.equals(other.application)) return false;
- // TODO: Simplify when Zone implements equals
- if ( ! this.zone.environment().equals(other.zone.environment())) return false;
- if ( ! this.zone.region().equals(other.zone.region())) return false;
- return true;
+ if ( ! (o instanceof DeploymentId)) return false;
+ DeploymentId id = (DeploymentId) o;
+ return Objects.equals(applicationId, id.applicationId) &&
+ Objects.equals(zoneId, id.zoneId);
}
@Override
public int hashCode() {
- // TODO: Simplify when Zone implements hashCode
- return application.hashCode() +
- 7 * zone.environment().hashCode() +
- 31 * zone.region().hashCode();
+ return Objects.hash(applicationId, zoneId);
}
@Override
@@ -60,10 +57,11 @@ public class DeploymentId {
}
public String toUserFriendlyString() {
- return application + " in " + zone;
+ return applicationId + " in " + zoneId;
}
private static String unCapitalize(String str) {
return str.toLowerCase().substring(0,1) + str.substring(1);
}
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java
index aab18595d20..2eeb0f60748 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.api.identifiers;
/**
* @author smorgrav
*/
+// TODO: Used in serialization (ConfigServerClient). Remove when no longer used by ControllerDb and ConfigServerClient
public class RotationId extends Identifier {
public RotationId(String id) {
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java
deleted file mode 100644
index 79210143d19..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java
+++ /dev/null
@@ -1,13 +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.hosted.controller.api.identifiers;
-
-/**
- * @author smorgrav
- */
-public class ZoneId extends Identifier {
-
- public ZoneId(EnvironmentId envId, RegionId regionId) {
- super(envId.id() + ":" + regionId.id());
- }
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java
index 1ceb064ad44..d3e1b881bbd 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import java.util.Map;
@@ -15,9 +15,9 @@ public interface MetricsService {
ApplicationMetrics getApplicationMetrics(ApplicationId application);
- DeploymentMetrics getDeploymentMetrics(ApplicationId application, Zone zone);
+ DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone);
- Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, Zone zone);
+ Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, ZoneId zone);
class DeploymentMetrics {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java
index 8614414dc95..3323cda89b3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
/**
* @author bjorncs
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java
index b6a21f94f74..a2a16d10cdb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
/**
* @author bjorncs
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java
new file mode 100644
index 00000000000..ef63ef2581f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java
@@ -0,0 +1,16 @@
+// 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.athenz;
+
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
+
+/**
+ * @author bjorncs
+ */
+public interface AthenzIdentity {
+ AthenzDomain getDomain();
+ String getName();
+ default String getFullName() {
+ return getDomain().id() + "." + getName();
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java
new file mode 100644
index 00000000000..d53817c09e4
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java
@@ -0,0 +1,27 @@
+// 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.athenz;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzIdentityCertificate {
+
+ private final X509Certificate certificate;
+ private final PrivateKey privateKey;
+
+ public AthenzIdentityCertificate(X509Certificate certificate, PrivateKey privateKey) {
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ }
+
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java
new file mode 100644
index 00000000000..8279edcd8e6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java
@@ -0,0 +1,59 @@
+// 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.athenz;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
+
+import java.security.Principal;
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzPrincipal implements Principal {
+
+ private final AthenzIdentity athenzIdentity;
+ private final NToken nToken;
+
+ public AthenzPrincipal(AthenzIdentity athenzIdentity,
+ NToken nToken) {
+ this.athenzIdentity = athenzIdentity;
+ this.nToken = nToken;
+ }
+
+ public AthenzIdentity getIdentity() {
+ return athenzIdentity;
+ }
+
+ @Override
+ public String getName() {
+ return athenzIdentity.getFullName();
+ }
+
+ public AthenzDomain getDomain() {
+ return athenzIdentity.getDomain();
+ }
+
+ public NToken getNToken() {
+ return nToken;
+ }
+
+ @Override
+ public String toString() {
+ return "AthenzPrincipal{" +
+ "athenzIdentity=" + athenzIdentity +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AthenzPrincipal principal = (AthenzPrincipal) o;
+ return Objects.equals(athenzIdentity, principal.athenzIdentity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(athenzIdentity);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPublicKey.java
index 01596ead0f4..c7f370dd4e3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPublicKey.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import java.security.PublicKey;
import java.util.Objects;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java
new file mode 100644
index 00000000000..80548cccd89
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java
@@ -0,0 +1,27 @@
+// 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.athenz;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzRoleCertificate {
+
+ private final X509Certificate certificate;
+ private final PrivateKey privateKey;
+
+ public AthenzRoleCertificate(X509Certificate certificate, PrivateKey privateKey) {
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ }
+
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzService.java
index 37c6459b687..24cd7671d96 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzService.java
@@ -1,14 +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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import java.util.Objects;
/**
* @author bjorncs
*/
-public class AthenzService {
+public class AthenzService implements AthenzIdentity {
private final AthenzDomain domain;
private final String serviceName;
@@ -22,15 +23,17 @@ public class AthenzService {
this(new AthenzDomain(domain), serviceName);
}
- public String toFullServiceName() {
- return domain.id() + "." + serviceName;
+ public static AthenzService fromScrewdriverId(ScrewdriverId screwdriverId) {
+ return new AthenzService(AthenzUtils.SCREWDRIVER_DOMAIN, "sd" + screwdriverId.id());
}
+ @Override
public AthenzDomain getDomain() {
return domain;
}
- public String getServiceName() {
+ @Override
+ public String getName() {
return serviceName;
}
@@ -50,6 +53,6 @@ public class AthenzService {
@Override
public String toString() {
- return String.format("AthenzService(%s)", toFullServiceName());
+ return String.format("AthenzService(%s)", getFullName());
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java
new file mode 100644
index 00000000000..480105a2d86
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java
@@ -0,0 +1,14 @@
+// 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.athenz;
+
+import com.google.inject.Provider;
+
+import javax.net.ssl.SSLContext;
+
+/**
+ * Provides a {@link SSLContext} for use in controller clients communicating with Athenz TLS secured services.
+ * It is configured with a keystore containing the Athenz service certificate and a trust store with the Athenz CA certificates.
+ *
+ * @author bjorncs
+ */
+public interface AthenzSslContextProvider extends Provider<SSLContext> {}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUser.java
index 1e4952a39c5..782876f21f1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUser.java
@@ -1,35 +1,28 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import java.security.Principal;
import java.util.Objects;
/**
* @author bjorncs
*/
-public class AthenzPrincipal implements Principal {
-
- private final AthenzDomain domain;
+public class AthenzUser implements AthenzIdentity {
private final UserId userId;
- public AthenzPrincipal(AthenzDomain domain, UserId userId) {
- this.domain = domain;
+ public AthenzUser(UserId userId) {
this.userId = userId;
}
- public UserId getUserId() {
- return userId;
+ public static AthenzUser fromUserId(UserId userId) {
+ return new AthenzUser(userId);
}
+ @Override
public AthenzDomain getDomain() {
- return domain;
- }
-
- public String toYRN() {
- return domain.id() + "." + userId.id();
+ return AthenzUtils.USER_PRINCIPAL_DOMAIN;
}
@Override
@@ -37,11 +30,14 @@ public class AthenzPrincipal implements Principal {
return userId.id();
}
+ public UserId getUserId() {
+ return userId;
+ }
+
@Override
public String toString() {
- return "AthenzPrincipal{" +
- "domain=" + domain +
- ", userId=" + userId +
+ return "AthenzUser{" +
+ "userId=" + userId +
'}';
}
@@ -49,14 +45,12 @@ public class AthenzPrincipal implements Principal {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- AthenzPrincipal that = (AthenzPrincipal) o;
- return Objects.equals(domain, that.domain) &&
- Objects.equals(userId, that.userId);
+ AthenzUser that = (AthenzUser) o;
+ return Objects.equals(userId, that.userId);
}
@Override
public int hashCode() {
- return Objects.hash(domain, userId);
+ return Objects.hash(userId);
}
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUtils.java
index 18bd626369d..0ed5d86dd7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUtils.java
@@ -1,8 +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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
/**
@@ -16,13 +15,12 @@ public class AthenzUtils {
public static final AthenzDomain SCREWDRIVER_DOMAIN = new AthenzDomain("cd.screwdriver.project");
public static final AthenzService ZMS_ATHENZ_SERVICE = new AthenzService("sys.auth", "zms");
- public static AthenzPrincipal createPrincipal(UserId userId) {
- return new AthenzPrincipal(USER_PRINCIPAL_DOMAIN, userId);
+ public static AthenzIdentity createAthenzIdentity(AthenzDomain domain, String identityName) {
+ if (domain.equals(USER_PRINCIPAL_DOMAIN)) {
+ return AthenzUser.fromUserId(new UserId(identityName));
+ } else {
+ return new AthenzService(domain, identityName);
+ }
}
- public static AthenzPrincipal createPrincipal(ScrewdriverId screwdriverId) {
- return new AthenzPrincipal(SCREWDRIVER_DOMAIN, new UserId("sd" + screwdriverId.id()));
- }
-
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/InvalidTokenException.java
index e41bd8d4283..1df1746b02e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/InvalidTokenException.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
/**
* @author bjorncs
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java
new file mode 100644
index 00000000000..c2796befdc8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java
@@ -0,0 +1,36 @@
+// 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.athenz;
+
+import java.util.Objects;
+
+/**
+ * Represents an Athenz NToken (principal token)
+ *
+ * @author bjorncs
+ */
+public class NToken {
+
+ private final String rawToken;
+
+ public NToken(String rawToken) {
+ this.rawToken = rawToken;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NToken nToken = (NToken) o;
+ return Objects.equals(rawToken, nToken.rawToken);
+ }
+
+ public String getRawToken() {
+ return rawToken;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rawToken);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java
new file mode 100644
index 00000000000..cfa63b04197
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java
@@ -0,0 +1,36 @@
+// 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.athenz;
+
+import java.util.Objects;
+
+/**
+ * Represents an Athenz ZToken (role token)
+ *
+ * @author bjorncs
+ */
+public class ZToken {
+
+ private final String rawToken;
+
+ public ZToken(String rawToken) {
+ this.rawToken = rawToken;
+ }
+
+ public String getRawToken() {
+ return rawToken;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ZToken zToken = (ZToken) o;
+ return Objects.equals(rawToken, zToken.rawToken);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rawToken);
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java
index 407bce05c6e..d72b8960427 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
@@ -19,12 +19,12 @@ public interface ZmsClient {
void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName);
- boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName);
+ boolean hasApplicationAccess(AthenzIdentity athenzIdentity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName);
- boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain);
+ boolean hasTenantAdminAccess(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain);
// Used before vespa tenancy is established for the domain.
- boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain);
+ boolean isDomainAdmin(AthenzIdentity athenzIdentity, AthenzDomain domain);
List<AthenzDomain> getDomainList(String prefix);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java
new file mode 100644
index 00000000000..31e9e549c08
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java
@@ -0,0 +1,24 @@
+// 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.athenz;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsException extends RuntimeException {
+
+ private final int code;
+
+ public ZmsException(int code, Throwable cause) {
+ super(cause.getMessage(), cause);
+ this.code = code;
+ }
+
+ public ZmsException(int code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsKeystore.java
index 93fed95c768..e2cb38a8466 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsKeystore.java
@@ -1,5 +1,5 @@
// 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.athenz;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
import java.security.PublicKey;
import java.util.Optional;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java
new file mode 100644
index 00000000000..f37c1679d1e
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java
@@ -0,0 +1,19 @@
+// 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.athenz;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+public interface ZtsClient {
+
+ List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity principal);
+
+ AthenzIdentityCertificate getIdentityCertificate();
+
+ AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName);
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsException.java
index cb0b21ba459..2be998e1544 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsException.java
@@ -1,7 +1,5 @@
// 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.athenz;
-
-import com.yahoo.athenz.zts.ZTSClientException;
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
/**
* @author bjorncs
@@ -10,12 +8,11 @@ public class ZtsException extends RuntimeException {
private final int code;
- public ZtsException(ZTSClientException e) {
- super(e.getMessage(), e);
- this.code = e.getCode();
+ public ZtsException(int code, Throwable cause) {
+ super(cause.getMessage(), cause);
+ this.code = code;
}
-
public int getCode() {
return code;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java
new file mode 100644
index 00000000000..d66525275bc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
index 10f9e18fa41..ec9cf0b3436 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
@@ -2,9 +2,9 @@
package com.yahoo.vespa.hosted.controller.api.integration.dns;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@@ -15,22 +15,41 @@ import java.util.UUID;
*/
public class MemoryNameService implements NameService {
- private final List<Record> records = new ArrayList<>();
+ private final Map<RecordId, Record> records = new HashMap<>();
- public List<Record> records() {
- return Collections.unmodifiableList(records);
+ public Map<RecordId, Record> records() {
+ return Collections.unmodifiableMap(records);
}
@Override
- public RecordId createCname(String alias, String canonicalName) {
- records.add(new Record(Record.Type.CNAME.name(), alias, canonicalName));
- return new RecordId(UUID.randomUUID().toString());
+ public RecordId createCname(RecordName alias, RecordData canonicalName) {
+ RecordId id = new RecordId(UUID.randomUUID().toString());
+ records.put(id, new Record(id, Record.Type.CNAME, alias, canonicalName));
+ return id;
}
@Override
- public Optional<Record> findRecord(Record.Type type, String name) {
- return records.stream()
+ public Optional<Record> findRecord(Record.Type type, RecordName name) {
+ return records.values().stream()
.filter(record -> record.type() == type && record.name().equals(name))
.findFirst();
}
+
+ @Override
+ public Optional<Record> findRecord(Record.Type type, RecordData data) {
+ return records.values()
+ .stream()
+ .filter(record -> record.type() == type && record.data().equals(data))
+ .findFirst();
+ }
+
+ @Override
+ public void updateRecord(RecordId id, RecordData newData) {
+ records.computeIfPresent(id, (k, record) -> new Record(id, record.type(), record.name(), newData));
+ }
+
+ @Override
+ public void removeRecord(RecordId id) {
+ records.remove(id);
+ }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
index 2ccce23b60c..078a7e7cefb 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
@@ -14,11 +14,20 @@ public interface NameService {
* Create a new CNAME record
*
* @param alias The alias to create
- * @param canonicalName The canonical name which the alias should point to. This must be a domain.
+ * @param canonicalName The canonical name which the alias should point to. This must be a FQDN.
*/
- RecordId createCname(String alias, String canonicalName);
+ RecordId createCname(RecordName alias, RecordData canonicalName);
/** Find record by type and name */
- Optional<Record> findRecord(Record.Type type, String name);
+ Optional<Record> findRecord(Record.Type type, RecordName name);
+
+ /** Find record by type and data */
+ Optional<Record> findRecord(Record.Type type, RecordData data);
+
+ /** Update existing record */
+ void updateRecord(RecordId id, RecordData newData);
+
+ /** Remove record by ID */
+ void removeRecord(RecordId id);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
index 0782a82da79..b51202e8261 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
@@ -4,35 +4,41 @@ package com.yahoo.vespa.hosted.controller.api.integration.dns;
import java.util.Objects;
/**
- * A basic representation of a DNS resource record, containing only the record type, name and value.
+ * A basic representation of a DNS resource record, containing the record id, type, name and value.
*
* @author mpolden
*/
public class Record {
+ private final RecordId id;
private final Type type;
- private final String name;
- private final String value;
+ private final RecordName name;
+ private final RecordData data;
- public Record(Type type, String name, String value) {
- this.type = type;
- this.name = name;
- this.value = value;
+ public Record(RecordId id, Type type, RecordName name, RecordData data) {
+ this.id = Objects.requireNonNull(id, "id cannot be null");
+ this.type = Objects.requireNonNull(type, "type cannot be null");
+ this.name = Objects.requireNonNull(name, "name cannot be null");
+ this.data = Objects.requireNonNull(data, "data cannot be null");
}
- public Record(String type, String name, String value) {
- this(Type.valueOf(type), name, value);
+ /** Unique identifier for this */
+ public RecordId id() {
+ return id;
}
+ /** DNS type of this */
public Type type() {
return type;
}
- public String value() {
- return value;
+ /** Data in this, e.g. IP address for "A" record */
+ public RecordData data() {
+ return data;
}
- public String name() {
+ /** Name of this, e.g. a FQDN for "A" record */
+ public RecordName name() {
return name;
}
@@ -51,24 +57,26 @@ public class Record {
@Override
public String toString() {
return "Record{" +
- "type=" + type +
- ", name='" + name + '\'' +
- ", value='" + value + '\'' +
- '}';
+ "id=" + id +
+ ", type=" + type +
+ ", name='" + name + '\'' +
+ ", data='" + data + '\'' +
+ '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof Record)) return false;
+ if (o == null || getClass() != o.getClass()) return false;
Record record = (Record) o;
- return type == record.type &&
- Objects.equals(name, record.name);
+ return Objects.equals(id, record.id) &&
+ type == record.type &&
+ Objects.equals(name, record.name) &&
+ Objects.equals(data, record.data);
}
@Override
public int hashCode() {
- return Objects.hash(type, name);
+ return Objects.hash(id, type, name, data);
}
-
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java
new file mode 100644
index 00000000000..e0d19e0fff9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java
@@ -0,0 +1,55 @@
+// 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.dns;
+
+import java.util.Objects;
+
+/**
+ * Represents the data field of a DNS record (RDATA).
+ *
+ * E.g. this may be an IP address for A records, or a FQDN for CNAME records.
+ *
+ * @author mpolden
+ */
+public class RecordData {
+
+ private final String data;
+
+ private RecordData(String data) {
+ this.data = Objects.requireNonNull(data, "data cannot be null");
+ }
+
+ public String asString() {
+ return data;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RecordData that = (RecordData) o;
+ return Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data);
+ }
+
+ @Override
+ public String toString() {
+ return "RecordValue{" +
+ "value='" + data + '\'' +
+ '}';
+ }
+
+ /** Create a new record containing the given data */
+ public static RecordData from(String data) {
+ return new RecordData(data);
+ }
+
+ /** Create a new record and append a trailing dot to given data, if missing */
+ public static RecordData fqdn(String data) {
+ return from(data.endsWith(".") ? data : data + ".");
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java
index 9c47be12855..da42c38252a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java
@@ -14,7 +14,7 @@ public class RecordId {
this.id = id;
}
- public String id() {
+ public String asString() {
return id;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java
new file mode 100644
index 00000000000..aa239ece588
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java
@@ -0,0 +1,47 @@
+// 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.dns;
+
+import java.util.Objects;
+
+/**
+ * Represents the name field of a DNS record (NAME). This is typically a FQDN.
+ *
+ * @author mpolden
+ */
+public class RecordName {
+
+ private final String name;
+
+ private RecordName(String name) {
+ this.name = Objects.requireNonNull(name, "name cannot be null");
+ }
+
+ public String asString() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RecordName that = (RecordName) o;
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public String toString() {
+ return "RecordName{" +
+ "name='" + name + '\'' +
+ '}';
+ }
+
+ public static RecordName from(String name) {
+ return new RecordName(name);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
index df182b56fd8..37356f2c2a6 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
@@ -19,8 +19,9 @@ public class Issue {
private final String label;
private final User assignee;
private final PropertyId propertyId;
+ private final Type type;
- private Issue(String summary, String description, String label, User assignee, PropertyId propertyId) {
+ private Issue(String summary, String description, String label, User assignee, PropertyId propertyId, Type type) {
if (summary.isEmpty()) throw new IllegalArgumentException("Issue summary can not be empty!");
if (description.isEmpty()) throw new IllegalArgumentException("Issue description can not be empty!");
Objects.requireNonNull(propertyId, "An issue must belong to a property!");
@@ -30,26 +31,31 @@ public class Issue {
this.label = label;
this.assignee = assignee;
this.propertyId = propertyId;
+ this.type = type;
}
public Issue(String summary, String description, PropertyId propertyId) {
- this(summary, description, null, null, propertyId);
+ this(summary, description, null, null, propertyId, Type.defect);
}
public Issue append(String appendage) {
- return new Issue(summary, description + appendage, label, assignee, propertyId);
+ return new Issue(summary, description + appendage, label, assignee, propertyId, type);
}
- public Issue withLabel(String label) {
- return new Issue(summary, description, label, assignee, propertyId);
+ public Issue with(String label) {
+ return new Issue(summary, description, label, assignee, propertyId, type);
}
- public Issue withAssignee(User assignee) {
- return new Issue(summary, description, label, assignee, propertyId);
+ public Issue with(User assignee) {
+ return new Issue(summary, description, label, assignee, propertyId, type);
}
- public Issue withPropertyId(PropertyId propertyId) {
- return new Issue(summary, description, label, assignee, propertyId);
+ public Issue with(PropertyId propertyId) {
+ return new Issue(summary, description, label, assignee, propertyId, type);
+ }
+
+ public Issue with(Type type) {
+ return new Issue(summary, description, label, assignee, propertyId, type);
}
public String summary() {
@@ -72,4 +78,16 @@ public class Issue {
return propertyId;
}
+ public Type type() {
+ return type;
+ }
+
+
+ public enum Type {
+
+ defect, // A defect which needs fixing.
+ task // A task the humans must perform.
+
+ }
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
index df6f023f6af..af7c464b8d7 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
@@ -5,7 +5,8 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import java.net.URI;
import java.time.Duration;
@@ -19,14 +20,38 @@ import java.util.Optional;
*/
public interface ZoneRegistry {
- SystemName system();
- List<Zone> zones();
- Optional<Zone> getZone(Environment environment, RegionName region);
- List<URI> getConfigServerUris(Environment environment, RegionName region);
- Optional<URI> getLogServerUri(Environment environment, RegionName region);
- Optional<Duration> getDeploymentTimeToLive(Environment environment, RegionName region);
+ /** Returns whether the system of this registry contains the given zone. */
+ boolean hasZone(ZoneId zoneId);
+
+ /** Returns a list containing the id of all zones in this registry. */
+ List<ZoneId> zones();
+
+ /** Returns the default region for the given environment, if one is configured. */
Optional<RegionName> getDefaultRegion(Environment environment);
- URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application);
+
+ /** Returns a list with all known config servers in the given zone.
+ *
+ * @deprecated Use {@link #getConfigServerSecureUris(ZoneId)} instead (requires that client trusts Athenz CA)
+ */
+ @Deprecated
+ List<URI> getConfigServerUris(ZoneId zoneId);
+
+ /** Returns a list with all known config servers in the given zone, with a secure connection URL. */
+ List<URI> getConfigServerSecureUris(ZoneId zoneId);
+
+ /** Returns a URL with the logs for the given deployment, if loggin is configured for its zone. */
+ Optional<URI> getLogServerUri(DeploymentId deploymentId);
+
+ /** Returns the time to live for deployments in the given zone, or empty if this is infinite. */
+ Optional<Duration> getDeploymentTimeToLive(ZoneId zoneId);
+
+ /** Returns a URL pointing at monitoring resources for the given deployment. */
+ URI getMonitoringSystemUri(DeploymentId deploymentId);
+
+ /** Returns the URL of the dashboard for the system of this registry. */
URI getDashboardUri();
+ /** Returns the system of this registry. */
+ SystemName system();
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java
index ed3e69bcac7..a1f78302e4b 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java
@@ -10,6 +10,8 @@ import java.util.Objects;
*
* @author Oyvind Gronnesby
*/
+// TODO: Used in serialization (ConfigServerClient). This should be removed and config server client should use a
+// Set<String> instead, like it does for CNAMEs.
public class Rotation {
/** The ID of the allocated rotation. This value is generated by global routing system. */
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java
index 97d99e262b5..1f1350ca001 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java
@@ -8,7 +8,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
-import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -18,7 +17,6 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -44,32 +42,28 @@ public interface ZoneApiV2 {
Response proxyGet(
@PathParam("environment") String env,
@PathParam("region") String region,
- @PathParam("proxy_request") String proxyRequest,
- @Context HttpServletRequest request);
+ @PathParam("proxy_request") String proxyRequest);
@POST
@Path("/{environment}/{region}/{proxy_request: .+}")
Response proxyPost(
@PathParam("environment") String env,
@PathParam("region") String region,
- @PathParam("proxy_request") String proxyRequest,
- @Context HttpServletRequest request);
+ @PathParam("proxy_request") String proxyRequest);
@PUT
@Path("/{environment}/{region}/{proxy_request: .+}")
Response proxyPut(
@PathParam("environment") String env,
@PathParam("region") String region,
- @PathParam("proxy_request") String proxyRequest,
- @Context HttpServletRequest request);
+ @PathParam("proxy_request") String proxyRequest);
@DELETE
@Path("/{environment}/{region}/{proxy_request: .+}")
Response proxyDelete(
@PathParam("environment") String env,
@PathParam("region") String region,
- @PathParam("proxy_request") String proxyRequest,
- @Context HttpServletRequest request);
+ @PathParam("proxy_request") String proxyRequest);
// Explicit mappings of some proxy requests (to enable creation of proxy clients with javax.ws.rs)
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
index 7e4d543fd18..aa3d1be879e 100644
--- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.identifiers;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -147,7 +147,7 @@ public class IdentifierTest {
@Test
public void application_instance_id_dotted_string_is_subindentifers_concatinated_with_dots() {
DeploymentId id = new DeploymentId(com.yahoo.config.provision.ApplicationId.from("tenant", "application", "instance"),
- new Zone(Environment.prod, RegionName.from("region")));
+ ZoneId.from("prod", "region"));
assertEquals("tenant.application.prod.region.instance", id.dottedString());
}
}
diff --git a/controller-server/pom.xml b/controller-server/pom.xml
index 0cfcbc40601..b033286b82a 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>controller-server</artifactId>
<packaging>container-plugin</packaging>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index e3400d76bce..ae2de96f511 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -7,14 +7,16 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.application.ApplicationRotation;
import com.yahoo.vespa.hosted.controller.application.ApplicationRevision;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Change.VersionChange;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import java.time.Instant;
import java.util.Collections;
@@ -38,38 +40,44 @@ public class Application {
private final ApplicationId id;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
- private final Map<Zone, Deployment> deployments;
+ private final Map<ZoneId, Deployment> deployments;
private final DeploymentJobs deploymentJobs;
private final Optional<Change> deploying;
private final boolean outstandingChange;
private final Optional<IssueId> ownershipIssueId;
private final ApplicationMetrics metrics;
+ private final Optional<RotationId> rotation;
/** Creates an empty application */
public Application(ApplicationId id) {
- this(id, DeploymentSpec.empty, ValidationOverrides.empty, ImmutableMap.of(),
+ this(id, DeploymentSpec.empty, ValidationOverrides.empty, Collections.emptyMap(),
new DeploymentJobs(Optional.empty(), Collections.emptyList(), Optional.empty()),
- Optional.empty(), false, Optional.empty(), new ApplicationMetrics(0, 0));
+ Optional.empty(), false, Optional.empty(), new ApplicationMetrics(0, 0),
+ Optional.empty());
}
/** Used from persistence layer: Do not use */
public Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
List<Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
- boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics) {
+ boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics,
+ Optional<RotationId> rotation) {
this(id, deploymentSpec, validationOverrides,
deployments.stream().collect(Collectors.toMap(Deployment::zone, d -> d)),
- deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics);
+ deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics, rotation);
}
Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- Map<Zone, Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
- boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics) {
+ Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
+ boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics,
+ Optional<RotationId> rotation) {
Objects.requireNonNull(id, "id cannot be null");
Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
Objects.requireNonNull(validationOverrides, "validationOverrides cannot be null");
Objects.requireNonNull(deployments, "deployments cannot be null");
Objects.requireNonNull(deploymentJobs, "deploymentJobs cannot be null");
Objects.requireNonNull(deploying, "deploying cannot be null");
+ Objects.requireNonNull(metrics, "metrics cannot be null");
+ Objects.requireNonNull(rotation, "rotation cannot be null");
this.id = id;
this.deploymentSpec = deploymentSpec;
this.validationOverrides = validationOverrides;
@@ -79,6 +87,7 @@ public class Application {
this.outstandingChange = outstandingChange;
this.ownershipIssueId = ownershipIssueId;
this.metrics = metrics;
+ this.rotation = rotation;
}
public ApplicationId id() { return id; }
@@ -97,13 +106,13 @@ public class Application {
public ValidationOverrides validationOverrides() { return validationOverrides; }
/** Returns an immutable map of the current deployments of this */
- public Map<Zone, Deployment> deployments() { return deployments; }
+ public Map<ZoneId, Deployment> deployments() { return deployments; }
/**
* Returns an immutable map of the current *production* deployments of this
* (deployments also includes manually deployed environments)
*/
- public Map<Zone, Deployment> productionDeployments() {
+ public Map<ZoneId, Deployment> productionDeployments() {
return ImmutableMap.copyOf(deployments.values().stream()
.filter(deployment -> deployment.zone().environment() == Environment.prod)
.collect(Collectors.toMap(Deployment::zone, Function.identity())));
@@ -142,7 +151,7 @@ public class Application {
}
/** Returns the version a new deployment to this zone should use for this application */
- public Version deployVersionIn(Zone zone, Controller controller) {
+ public Version deployVersionIn(ZoneId zone, Controller controller) {
if (deploying().isPresent() && deploying().get() instanceof VersionChange)
return ((Change.VersionChange) deploying().get()).version();
@@ -150,13 +159,13 @@ public class Application {
}
/** Returns the current version this application has, or if none; should use, in the given zone */
- public Version versionIn(Zone zone, Controller controller) {
+ public Version versionIn(ZoneId zone, Controller controller) {
return Optional.ofNullable(deployments().get(zone)).map(Deployment::version) // Already deployed in this zone: Use that version
.orElse(oldestDeployedVersion().orElse(controller.systemVersion()));
}
/** Returns the revision a new deployment to this zone should use for this application, or empty if we don't know */
- public Optional<ApplicationRevision> deployRevisionIn(Zone zone) {
+ public Optional<ApplicationRevision> deployRevisionIn(ZoneId zone) {
if (deploying().isPresent() && deploying().get() instanceof Change.ApplicationChange)
return ((Change.ApplicationChange) deploying().get()).revision();
@@ -164,10 +173,15 @@ public class Application {
}
/** Returns the revision this application is or should be deployed with in the given zone, or empty if unknown. */
- public Optional<ApplicationRevision> revisionIn(Zone zone) {
+ public Optional<ApplicationRevision> revisionIn(ZoneId zone) {
return Optional.ofNullable(deployments().get(zone)).map(Deployment::revision);
}
+ /** Returns the global rotation of this, if present */
+ public Optional<ApplicationRotation> rotation() {
+ return rotation.map(rotation -> new ApplicationRotation(id, rotation));
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 841b9b4dd9f..ec1051a3674 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
@@ -1,17 +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;
-import com.google.common.collect.ImmutableSet;
import com.yahoo.component.Version;
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.Environment;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
-import com.yahoo.vespa.hosted.controller.api.ApplicationAlias;
import com.yahoo.vespa.hosted.controller.api.InstanceEndpoints;
import com.yahoo.vespa.hosted.controller.api.Tenant;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
@@ -23,33 +21,39 @@ 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.RevisionId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NoInstanceException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.PrepareResponse;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
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.RecordId;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
-import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.ApplicationRevision;
+import com.yahoo.vespa.hosted.controller.application.ApplicationRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.maintenance.DeploymentExpirer;
import com.yahoo.vespa.hosted.controller.persistence.ControllerDb;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
-import com.yahoo.vespa.hosted.rotation.RotationRepository;
+import com.yahoo.vespa.hosted.controller.rotation.Rotation;
+import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.rotation.RotationRepository;
+import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
+import com.yahoo.yolean.Exceptions;
import java.io.IOException;
import java.net.URI;
@@ -82,6 +86,7 @@ public class ApplicationController {
/** For permanent storage */
private final ControllerDb db;
+
/** For working memory storage and sharing between controllers */
private final CuratorDb curator;
@@ -93,26 +98,26 @@ public class ApplicationController {
private final Clock clock;
private final DeploymentTrigger deploymentTrigger;
-
+
ApplicationController(Controller controller, ControllerDb db, CuratorDb curator,
- RotationRepository rotationRepository,
- AthenzClientFactory zmsClientFactory,
+ AthenzClientFactory zmsClientFactory, RotationsConfig rotationsConfig,
NameService nameService, ConfigServerClient configserverClient,
RoutingGenerator routingGenerator, Clock clock) {
this.controller = controller;
this.db = db;
this.curator = curator;
- this.rotationRepository = rotationRepository;
this.zmsClientFactory = zmsClientFactory;
this.nameService = nameService;
this.configserverClient = configserverClient;
this.routingGenerator = routingGenerator;
this.clock = clock;
+ this.rotationRepository = new RotationRepository(rotationsConfig, this, curator);
this.deploymentTrigger = new DeploymentTrigger(controller, curator, clock);
- for (Application application : db.listApplications())
- lockedIfPresent(application.id(), this::store);
+ for (Application application : db.listApplications()) {
+ lockIfPresent(application.id(), this::store);
+ }
}
/** Returns the application with the given id, or null if it is not present */
@@ -231,10 +236,6 @@ public class ApplicationController {
if ( ! (id.instance().value().equals("default") || id.instance().value().startsWith("default-pr"))) // TODO: Support instances properly
throw new UnsupportedOperationException("Only the instance names 'default' and names starting with 'default-pr' are supported at the moment");
try (Lock lock = lock(id)) {
- // TODO: Throwing is duplicated below.
- if (get(id).isPresent())
- throw new IllegalArgumentException("An application with id '" + id + "' already exists");
-
com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId.validate(id.application().value());
Optional<Tenant> tenant = controller.tenants().tenant(new TenantId(id.tenant().value()));
@@ -266,13 +267,14 @@ public class ApplicationController {
/** Deploys an application. If the application does not exist it is created. */
// TODO: Get rid of the options arg
- public ActivateResult deployApplication(ApplicationId applicationId, Zone zone,
+ public ActivateResult deployApplication(ApplicationId applicationId, ZoneId zone,
ApplicationPackage applicationPackage, DeployOptions options) {
try (Lock lock = lock(applicationId)) {
- // TODO: Shouldn't this go through the above method? Seems you can cheat the checks here ... ?
- LockedApplication application = get(applicationId).map(application1 -> new LockedApplication(application1, lock)).orElse(new LockedApplication(
- new Application(applicationId), lock)
- );
+ // Not ideal, but since we create on missing and return a result computed inside the lock,
+ // the lock-with-action methods cannot be used
+ LockedApplication application = get(applicationId)
+ .map(app -> new LockedApplication(app, lock))
+ .orElseGet(() -> new LockedApplication(new Application(applicationId), lock));
// Determine what we are doing
Version version;
@@ -328,15 +330,35 @@ public class ApplicationController {
throw new IllegalArgumentException("Rejecting deployment of " + application + " to " + zone +
" as the requested version " + version + " is older than" +
" the current version " + existingDeployment.version());
- }
+ }
+
+ Optional<Rotation> rotation;
+ try (RotationLock rotationLock = rotationRepository.lock()) {
+ rotation = getRotation(application, zone, rotationLock);
+ if (rotation.isPresent()) {
+ application = application.with(rotation.get().id());
+ store(application); // store assigned rotation even if deployment fails
+ registerRotationInDns(rotation.get(), application.rotation().get().dnsName());
+ }
+ }
+
+ // TODO: Improve config server client interface and simplify
+ Set<String> cnames = application.rotation()
+ .map(ApplicationRotation::dnsName)
+ .map(Collections::singleton)
+ .orElseGet(Collections::emptySet);
+
+ Set<com.yahoo.vespa.hosted.controller.api.rotation.Rotation> rotations = rotation
+ .map(r -> new com.yahoo.vespa.hosted.controller.api.rotation.Rotation(
+ new com.yahoo.vespa.hosted.controller.api.identifiers.RotationId(
+ r.id().asString()), r.name()))
+ .map(Collections::singleton)
+ .orElseGet(Collections::emptySet);
// Carry out deployment
- DeploymentId deploymentId = new DeploymentId(applicationId, zone);
- ApplicationRotation rotationInDns = registerRotationInDns(deploymentId, getOrAssignRotation(deploymentId,
- applicationPackage));
- options = withVersion(version, options);
+ options = withVersion(version, options);
ConfigServerClient.PreparedApplication preparedApplication =
- configserverClient.prepare(deploymentId, options, rotationInDns.cnames(), rotationInDns.rotations(),
+ configserverClient.prepare(new DeploymentId(applicationId, zone), options, cnames, rotations,
applicationPackage.zippedContent());
preparedApplication.activate();
application = application.withNewDeployment(zone, revision, version, clock.instant());
@@ -347,7 +369,7 @@ public class ApplicationController {
}
}
- private ActivateResult unexpectedDeployment(ApplicationId applicationId, Zone zone, ApplicationPackage applicationPackage) {
+ private ActivateResult unexpectedDeployment(ApplicationId applicationId, ZoneId zone, ApplicationPackage applicationPackage) {
Log logEntry = new Log();
logEntry.level = "WARNING";
logEntry.time = clock.instant().toEpochMilli();
@@ -384,9 +406,9 @@ public class ApplicationController {
private LockedApplication deleteUnreferencedDeploymentJobs(LockedApplication application) {
for (DeploymentJobs.JobType job : application.deploymentJobs().jobStatus().keySet()) {
- Optional<Zone> zone = job.zone(controller.system());
+ Optional<ZoneId> zone = job.zone(controller.system());
- if ( ! job.isProduction() || (zone.isPresent() && application.deploymentSpec().includes(zone.get().environment(), zone.map(Zone::region))))
+ if ( ! job.isProduction() || (zone.isPresent() && application.deploymentSpec().includes(zone.get().environment(), zone.map(ZoneId::region))))
continue;
application = application.withoutDeploymentJob(job);
}
@@ -430,35 +452,35 @@ public class ApplicationController {
gitRevision.commit.id()));
}
- private ApplicationRotation registerRotationInDns(DeploymentId deploymentId, ApplicationRotation applicationRotation) {
- ApplicationAlias alias = new ApplicationAlias(deploymentId.applicationId());
- if (applicationRotation.rotations().isEmpty()) return applicationRotation;
-
- Rotation rotation = applicationRotation.rotations().iterator().next(); // at this time there should be only one rotation assigned
- String endpointName = alias.toString();
+ /** Register a DNS name for rotation */
+ private void registerRotationInDns(Rotation rotation, String dnsName) {
try {
- Optional<Record> record = nameService.findRecord(Record.Type.CNAME, endpointName);
- if (!record.isPresent()) {
- RecordId recordId = nameService.createCname(endpointName, rotation.rotationName);
- log.info("Registered mapping with record ID " + recordId.id() + ": " +
- endpointName + " -> " + rotation.rotationName);
+ Optional<Record> record = nameService.findRecord(Record.Type.CNAME, RecordName.from(dnsName));
+ RecordData rotationName = RecordData.fqdn(rotation.name());
+ if (record.isPresent()) {
+ // Ensure that the existing record points to the correct rotation
+ if (!record.get().data().equals(rotationName)) {
+ nameService.updateRecord(record.get().id(), rotationName);
+ log.info("Updated mapping for record ID " + record.get().id().asString() + ": '" + dnsName
+ + "' -> '" + rotation.name() + "'");
+ }
+ } else {
+ RecordId id = nameService.createCname(RecordName.from(dnsName), rotationName);
+ log.info("Registered mapping with record ID " + id.asString() + ": '" + dnsName + "' -> '"
+ + rotation.name() + "'");
}
- }
- catch (RuntimeException e) {
+ } catch (RuntimeException e) {
log.log(Level.WARNING, "Failed to register CNAME", e);
}
- return new ApplicationRotation(Collections.singleton(endpointName), Collections.singleton(rotation));
}
- private ApplicationRotation getOrAssignRotation(DeploymentId deploymentId, ApplicationPackage applicationPackage) {
- if (deploymentId.zone().environment().equals(Environment.prod)) {
- return new ApplicationRotation(Collections.emptySet(),
- rotationRepository.getOrAssignRotation(deploymentId.applicationId(),
- applicationPackage.deploymentSpec()));
- } else {
- return new ApplicationRotation(Collections.emptySet(),
- Collections.emptySet());
+ /** Get an available rotation, if deploying to a production zone and a service ID is specified */
+ private Optional<Rotation> getRotation(Application application, ZoneId zone, RotationLock lock) {
+ if (zone.environment() != Environment.prod ||
+ !application.deploymentSpec().globalServiceId().isPresent()) {
+ return Optional.empty();
}
+ return Optional.of(rotationRepository.getRotation(application, lock));
}
/** Returns the endpoints of the deployment, or empty if obtaining them failed */
@@ -476,37 +498,50 @@ public class ApplicationController {
return Optional.of(new InstanceEndpoints(endPointUrls));
}
catch (RuntimeException e) {
- log.log(Level.FINE, "Failed to get endpoint information for " + deploymentId, e);
+ log.log(Level.WARNING, "Failed to get endpoint information for " + deploymentId + ": "
+ + Exceptions.toMessageString(e));
return Optional.empty();
}
}
/**
- * Deletes the application with this id
+ * Deletes the the given application. All known instances of the applications will be deleted,
+ * including PR instances.
*
* @throws IllegalArgumentException if the application has deployments or the caller is not authorized
- * @throws NotExistsException if the application does not exist
+ * @throws NotExistsException if no instances of the application exist
*/
- public void deleteApplication(ApplicationId id, Optional<NToken> token) {
- if ( ! controller.applications().get(id).isPresent())
- throw new NotExistsException("Could not delete application '" + id + "': Application not found");
+ public void deleteApplication(ApplicationId applicationId, Optional<NToken> token) {
+ // Find all instances of the application
+ List<ApplicationId> instances = controller.applications().asList(applicationId.tenant())
+ .stream()
+ .map(Application::id)
+ .filter(id -> id.application().equals(applicationId.application()) &&
+ id.tenant().equals(applicationId.tenant()))
+ .collect(Collectors.toList());
+ if (instances.isEmpty()) {
+ throw new NotExistsException("Could not delete application '" + applicationId + "': Application not found");
+ }
- lockedOrThrow(id, application -> {
+ // TODO: Make this one transaction when database is moved to ZooKeeper
+ instances.forEach(id -> lockOrThrow(id, application -> {
if ( ! application.deployments().isEmpty())
throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments");
-
+
Tenant tenant = controller.tenants().tenant(new TenantId(id.tenant().value())).get();
if (tenant.isAthensTenant() && ! token.isPresent())
throw new IllegalArgumentException("Could not delete '" + application + "': No NToken provided");
- // NB: Next 2 lines should have been one transaction
- if (tenant.isAthensTenant())
+ // Only delete in Athenz once
+ if (id.instance().isDefault() && tenant.isAthensTenant()) {
zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get())
- .deleteApplication(tenant.getAthensDomain().get(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()));
+ .deleteApplication(tenant.getAthensDomain().get(),
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()));
+ }
db.deleteApplication(id);
log.info("Deleted " + application);
- });
+ }));
}
/**
@@ -521,24 +556,25 @@ public class ApplicationController {
/**
* Acquire a locked application to modify and store, if there is an application with the given id.
*
- * @param applicationId Id of the application to lock and get.
- * @param actions Things to do with the locked application.
+ * @param applicationId ID of the application to lock and get.
+ * @param action Function which acts on the locked application.
*/
- public void lockedIfPresent(ApplicationId applicationId, Consumer<LockedApplication> actions) {
+ public void lockIfPresent(ApplicationId applicationId, Consumer<LockedApplication> action) {
try (Lock lock = lock(applicationId)) {
- get(applicationId).map(application -> new LockedApplication(application, lock)).ifPresent(actions);
+ get(applicationId).map(application -> new LockedApplication(application, lock)).ifPresent(action);
}
}
/**
* Acquire a locked application to modify and store, or throw an exception if no application has the given id.
*
- * @param applicationId Id of the application to lock and require.
- * @param actions Things to do with the locked application.
+ * @param applicationId ID of the application to lock and require.
+ * @param action Function which acts on the locked application.
+ * @throws IllegalArgumentException when application does not exist.
*/
- public void lockedOrThrow(ApplicationId applicationId, Consumer<LockedApplication> actions) {
+ public void lockOrThrow(ApplicationId applicationId, Consumer<LockedApplication> action) {
try (Lock lock = lock(applicationId)) {
- actions.accept(new LockedApplication(require(applicationId), lock));
+ action.accept(new LockedApplication(require(applicationId), lock));
}
}
@@ -551,18 +587,14 @@ public class ApplicationController {
deploymentTrigger.triggerFromCompletion(report);
}
- // TODO: Collapse this method and the next
- public void restart(DeploymentId deploymentId) {
- try {
- configserverClient.restart(deploymentId, Optional.empty());
- }
- catch (NoInstanceException e) {
- throw new IllegalArgumentException("Could not restart " + deploymentId + ": No such deployment");
- }
- }
- public void restartHost(DeploymentId deploymentId, Hostname hostname) {
+ /**
+ * 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
+ */
+ public void restart(DeploymentId deploymentId, Optional<Hostname> hostname) {
try {
- configserverClient.restart(deploymentId, Optional.of(hostname));
+ configserverClient.restart(deploymentId, hostname);
}
catch (NoInstanceException e) {
throw new IllegalArgumentException("Could not restart " + deploymentId + ": No such deployment");
@@ -570,7 +602,7 @@ public class ApplicationController {
}
/** Deactivate application in the given zone */
- public void deactivate(Application application, Zone zone) {
+ public void deactivate(Application application, ZoneId zone) {
deactivate(application, zone, Optional.empty(), false);
}
@@ -579,13 +611,13 @@ public class ApplicationController {
deactivate(application, deployment.zone(), Optional.of(deployment), requireThatDeploymentHasExpired);
}
- private void deactivate(Application application, Zone zone, Optional<Deployment> deployment,
+ private void deactivate(Application application, ZoneId zone, Optional<Deployment> deployment,
boolean requireThatDeploymentHasExpired) {
if (requireThatDeploymentHasExpired && deployment.isPresent()
&& ! DeploymentExpirer.hasExpired(controller.zoneRegistry(), deployment.get(), clock.instant()))
return;
- lockedOrThrow(application.id(), lockedApplication ->
+ lockOrThrow(application.id(), lockedApplication ->
store(deactivate(lockedApplication, zone)));
}
@@ -594,7 +626,7 @@ public class ApplicationController {
*
* @return the application with the deployment in the given zone removed
*/
- private LockedApplication deactivate(LockedApplication application, Zone zone) {
+ private LockedApplication deactivate(LockedApplication application, ZoneId zone) {
try {
configserverClient.deactivate(new DeploymentId(application.id(), zone));
}
@@ -624,7 +656,7 @@ public class ApplicationController {
}
/** Returns whether a direct deployment to given zone is allowed */
- private static boolean canDeployDirectlyTo(Zone zone, DeployOptions options) {
+ private static boolean canDeployDirectlyTo(ZoneId zone, DeployOptions options) {
return ! options.screwdriverBuildJob.isPresent() ||
options.screwdriverBuildJob.get().screwdriverId == null ||
zone.environment().isManuallyDeployed();
@@ -635,25 +667,13 @@ public class ApplicationController {
deploymentSpec.zones().stream()
.filter(zone -> zone.environment() == Environment.prod)
.forEach(zone -> {
- if ( ! controller.zoneRegistry().getZone(zone.environment(), zone.region().orElse(null)).isPresent())
+ if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(), zone.region().orElse(null))))
throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!");
});
}
-
- private static final class ApplicationRotation {
-
- private final ImmutableSet<String> cnames;
- private final ImmutableSet<Rotation> rotations;
-
- public ApplicationRotation(Set<String> cnames, Set<Rotation> rotations) {
- this.cnames = ImmutableSet.copyOf(cnames);
- this.rotations = ImmutableSet.copyOf(rotations);
- }
-
- public Set<String> cnames() { return cnames; }
- public Set<Rotation> rotations() { return rotations; }
-
+ public RotationRepository rotationRepository() {
+ return rotationRepository;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index b854ad3f771..f50958f0e66 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -6,10 +6,8 @@ import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
@@ -25,12 +23,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingSe
import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
import com.yahoo.vespa.hosted.controller.persistence.ControllerDb;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
-import com.yahoo.vespa.hosted.rotation.RotationRepository;
+import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import java.net.URI;
@@ -39,7 +37,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.logging.Logger;
/**
@@ -64,7 +61,6 @@ public class Controller extends AbstractComponent {
private final ApplicationController applicationController;
private final TenantController tenantController;
private final Clock clock;
- private final RotationRepository rotationRepository;
private final GitHub gitHub;
private final EntityService entityService;
private final GlobalRoutingService globalRoutingService;
@@ -82,19 +78,19 @@ public class Controller extends AbstractComponent {
* @param curator the curator instance storing working state shared between controller instances
*/
@Inject
- public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository,
+ public Controller(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig,
GitHub gitHub, EntityService entityService, Organization organization,
GlobalRoutingService globalRoutingService,
ZoneRegistry zoneRegistry, ConfigServerClient configServerClient,
MetricsService metricsService, NameService nameService,
RoutingGenerator routingGenerator, Chef chefClient, AthenzClientFactory athenzClientFactory) {
- this(db, curator, rotationRepository,
+ this(db, curator, rotationsConfig,
gitHub, entityService, organization, globalRoutingService, zoneRegistry,
configServerClient, metricsService, nameService, routingGenerator, chefClient,
Clock.systemUTC(), athenzClientFactory);
}
- public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository,
+ public Controller(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig,
GitHub gitHub, EntityService entityService, Organization organization,
GlobalRoutingService globalRoutingService,
ZoneRegistry zoneRegistry, ConfigServerClient configServerClient,
@@ -103,7 +99,7 @@ public class Controller extends AbstractComponent {
AthenzClientFactory athenzClientFactory) {
Objects.requireNonNull(db, "Controller db cannot be null");
Objects.requireNonNull(curator, "Curator cannot be null");
- Objects.requireNonNull(rotationRepository, "Rotation repository cannot be null");
+ Objects.requireNonNull(rotationsConfig, "RotationsConfig cannot be null");
Objects.requireNonNull(gitHub, "GitHubClient cannot be null");
Objects.requireNonNull(entityService, "EntityService cannot be null");
Objects.requireNonNull(organization, "Organization cannot be null");
@@ -117,7 +113,6 @@ public class Controller extends AbstractComponent {
Objects.requireNonNull(clock, "Clock cannot be null");
Objects.requireNonNull(athenzClientFactory, "Athens cannot be null");
- this.rotationRepository = rotationRepository;
this.curator = curator;
this.gitHub = gitHub;
this.entityService = entityService;
@@ -130,7 +125,8 @@ public class Controller extends AbstractComponent {
this.clock = clock;
this.athenzClientFactory = athenzClientFactory;
- applicationController = new ApplicationController(this, db, curator, rotationRepository, athenzClientFactory,
+ applicationController = new ApplicationController(this, db, curator, athenzClientFactory,
+ rotationsConfig,
nameService, configServerClient, routingGenerator, clock);
tenantController = new TenantController(this, db, curator, entityService, athenzClientFactory);
}
@@ -156,35 +152,16 @@ public class Controller extends AbstractComponent {
public Clock clock() { return clock; }
- public URI getElkUri(DeploymentId deploymentId) {
- return elkUrl(zoneRegistry.getLogServerUri(deploymentId.zone().environment(), deploymentId.zone().region()), deploymentId);
+ public Optional<URI> getLogServerUrl(DeploymentId deploymentId) {
+ return zoneRegistry.getLogServerUri(deploymentId);
}
- public List<URI> getConfigServerUris(Environment environment, RegionName region) {
- return zoneRegistry.getConfigServerUris(environment, region);
+ public List<URI> getConfigServerUris(ZoneId zoneId) {
+ return zoneRegistry.getConfigServerUris(zoneId);
}
public ZoneRegistry zoneRegistry() { return zoneRegistry; }
- private URI elkUrl(Optional<URI> kibanaHost, DeploymentId deploymentId) {
- String kibanaQuery = "/#/discover?_g=()&_a=(columns:!(_source)," +
- "index:'logstash-*',interval:auto," +
- "query:(query_string:(analyze_wildcard:!t,query:'" +
- "HV-tenant:%22" + deploymentId.applicationId().tenant().value() + "%22%20" +
- "AND%20HV-application:%22" + deploymentId.applicationId().application().value() + "%22%20" +
- "AND%20HV-region:%22" + deploymentId.zone().region().value() + "%22%20" +
- "AND%20HV-instance:%22" + deploymentId.applicationId().instance().value() + "%22%20" +
- "AND%20HV-environment:%22" + deploymentId.zone().environment().value() + "%22'))," +
- "sort:!('@timestamp',desc))";
-
- URI kibanaPath = URI.create(kibanaQuery);
- return kibanaHost.map(uri -> uri.resolve(kibanaPath)).orElse(null);
- }
-
- public Set<URI> getRotationUris(ApplicationId id) {
- return rotationRepository.getRotationUris(id);
- }
-
public Map<String, RotationStatus> getHealthStatus(String hostname) {
return globalRoutingService.getHealthStatus(hostname);
}
@@ -209,7 +186,9 @@ public class Controller extends AbstractComponent {
return configServerClient.grabLog(deploymentId);
}
- public GitHub gitHub() { return gitHub; }
+ public GitHub gitHub() {
+ return gitHub;
+ }
/** Replace the current version status by a new one */
public void updateVersionStatus(VersionStatus newStatus) {
@@ -232,7 +211,9 @@ public class Controller extends AbstractComponent {
.orElse(Vtag.currentVersion);
}
- public MetricsService metricsService() { return metricsService; }
+ public MetricsService metricsService() {
+ return metricsService;
+ }
public SystemName system() {
return zoneRegistry.system();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index e8c8f8a389c..72ed1a42435 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -6,10 +6,12 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.application.ApplicationRotation;
import com.yahoo.vespa.hosted.controller.application.ApplicationRevision;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
@@ -18,7 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
-
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import java.time.Instant;
import java.util.LinkedHashMap;
@@ -34,12 +36,6 @@ import java.util.Optional;
*/
public class LockedApplication extends Application {
- private LockedApplication(Builder builder) {
- super(builder.applicationId, builder.deploymentSpec, builder.validationOverrides,
- builder.deployments, builder.deploymentJobs, builder.deploying,
- builder.hasOutstandingChange, builder.ownershipIssueId, builder.metrics);
- }
-
/**
* Used to create a locked application
*
@@ -50,6 +46,12 @@ public class LockedApplication extends Application {
this(new Builder(application));
}
+ private LockedApplication(Builder builder) {
+ super(builder.applicationId, builder.deploymentSpec, builder.validationOverrides,
+ builder.deployments, builder.deploymentJobs, builder.deploying,
+ builder.hasOutstandingChange, builder.ownershipIssueId, builder.metrics, builder.rotation);
+ }
+
public LockedApplication withProjectId(long projectId) {
return new LockedApplication(new Builder(this).with(deploymentJobs().withProjectId(projectId)));
}
@@ -67,7 +69,7 @@ public class LockedApplication extends Application {
return new LockedApplication(new Builder(this).with(deploymentJobs().withTriggering(type, change, version, revision, reason, triggerTime)));
}
- public LockedApplication withNewDeployment(Zone zone, ApplicationRevision revision, Version version, Instant instant) {
+ public LockedApplication withNewDeployment(ZoneId zone, ApplicationRevision revision, Version version, Instant instant) {
// Use info from previous deployment if available, otherwise create a new one.
Deployment previousDeployment = deployments().getOrDefault(zone, new Deployment(zone, revision, version, instant));
Deployment newDeployment = new Deployment(zone, revision, version, instant,
@@ -77,27 +79,27 @@ public class LockedApplication extends Application {
return with(newDeployment);
}
- public LockedApplication withClusterUtilization(Zone zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
+ public LockedApplication withClusterUtilization(ZoneId zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
Deployment deployment = deployments().get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withClusterUtils(clusterUtilization));
}
- public LockedApplication withClusterInfo(Zone zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) {
+ public LockedApplication withClusterInfo(ZoneId zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) {
Deployment deployment = deployments().get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withClusterInfo(clusterInfo));
}
- public LockedApplication with(Zone zone, DeploymentMetrics deploymentMetrics) {
+ public LockedApplication with(ZoneId zone, DeploymentMetrics deploymentMetrics) {
Deployment deployment = deployments().get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withMetrics(deploymentMetrics));
}
- public LockedApplication withoutDeploymentIn(Zone zone) {
- Map<Zone, Deployment> deployments = new LinkedHashMap<>(deployments());
+ public LockedApplication withoutDeploymentIn(ZoneId zone) {
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments());
deployments.remove(zone);
return new LockedApplication(new Builder(this).with(deployments));
}
@@ -130,6 +132,10 @@ public class LockedApplication extends Application {
return new LockedApplication(new Builder(this).with(metrics));
}
+ public LockedApplication with(RotationId rotation) {
+ return new LockedApplication(new Builder(this).with(rotation));
+ }
+
public Version deployVersionFor(DeploymentJobs.JobType jobType, Controller controller) {
return jobType == JobType.component
? controller.systemVersion()
@@ -144,7 +150,7 @@ public class LockedApplication extends Application {
/** Don't expose non-leaf sub-objects. */
private LockedApplication with(Deployment deployment) {
- Map<Zone, Deployment> deployments = new LinkedHashMap<>(deployments());
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments());
deployments.put(deployment.zone(), deployment);
return new LockedApplication(new Builder(this).with(deployments));
}
@@ -155,12 +161,13 @@ public class LockedApplication extends Application {
private final ApplicationId applicationId;
private DeploymentSpec deploymentSpec;
private ValidationOverrides validationOverrides;
- private Map<Zone, Deployment> deployments;
+ private Map<ZoneId, Deployment> deployments;
private DeploymentJobs deploymentJobs;
private Optional<Change> deploying;
private boolean hasOutstandingChange;
private Optional<IssueId> ownershipIssueId;
private ApplicationMetrics metrics;
+ private Optional<RotationId> rotation;
private Builder(Application application) {
this.applicationId = application.id();
@@ -172,16 +179,53 @@ public class LockedApplication extends Application {
this.hasOutstandingChange = application.hasOutstandingChange();
this.ownershipIssueId = application.ownershipIssueId();
this.metrics = application.metrics();
+ this.rotation = application.rotation().map(ApplicationRotation::id);
+ }
+
+ private Builder with(DeploymentSpec deploymentSpec) {
+ this.deploymentSpec = deploymentSpec;
+ return this;
}
- private Builder with(DeploymentSpec deploymentSpec) { this.deploymentSpec = deploymentSpec; return this; }
- private Builder with(ValidationOverrides validationOverrides) { this.validationOverrides = validationOverrides; return this; }
- private Builder with(Map<Zone, Deployment> deployments) { this.deployments = deployments; return this; }
- private Builder with(DeploymentJobs deploymentJobs) { this.deploymentJobs = deploymentJobs; return this; }
- private Builder withDeploying(Optional<Change> deploying) { this.deploying = deploying; return this; }
- private Builder with(boolean hasOutstandingChange) { this.hasOutstandingChange = hasOutstandingChange; return this; }
- private Builder withOwnershipIssueId(Optional<IssueId> ownershipIssueId) { this.ownershipIssueId = ownershipIssueId; return this; }
- private Builder with(ApplicationMetrics metrics) { this.metrics = metrics; return this; }
+ private Builder with(ValidationOverrides validationOverrides) {
+ this.validationOverrides = validationOverrides;
+ return this;
+ }
+
+ private Builder with(Map<ZoneId, Deployment> deployments) {
+ this.deployments = deployments;
+ return this;
+ }
+
+ private Builder with(DeploymentJobs deploymentJobs) {
+ this.deploymentJobs = deploymentJobs;
+ return this;
+ }
+
+ private Builder withDeploying(Optional<Change> deploying) {
+ this.deploying = deploying;
+ return this;
+ }
+
+ private Builder with(boolean hasOutstandingChange) {
+ this.hasOutstandingChange = hasOutstandingChange;
+ return this;
+ }
+
+ private Builder withOwnershipIssueId(Optional<IssueId> ownershipIssueId) {
+ this.ownershipIssueId = ownershipIssueId;
+ return this;
+ }
+
+ private Builder with(ApplicationMetrics metrics) {
+ this.metrics = metrics;
+ return this;
+ }
+
+ private Builder with(RotationId rotation) {
+ this.rotation = Optional.of(rotation);
+ return this;
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index 9530e9a982c..a52098a4a0f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -12,11 +12,11 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.persistence.ControllerDb;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.PersistenceException;
@@ -67,7 +67,7 @@ public class TenantController {
public List<Tenant> asList(UserId user) {
Set<UserGroup> userGroups = entityService.getUserGroups(user);
Set<AthenzDomain> userDomains = new HashSet<>(athenzClientFactory.createZtsClientWithServicePrincipal()
- .getTenantDomainsForUser(AthenzUtils.createPrincipal(user)));
+ .getTenantDomainsForUser(AthenzUser.fromUserId(user)));
Predicate<Tenant> hasUsersGroup = (tenant) -> tenant.getUserGroup().isPresent() && userGroups.contains(tenant.getUserGroup().get());
Predicate<Tenant> hasUsersDomain = (tenant) -> tenant.getAthensDomain().isPresent() && userDomains.contains(tenant.getAthensDomain().get());
@@ -200,8 +200,7 @@ public class TenantController {
try (Lock lock = lock(tenantId)) {
Tenant existing = tenant(tenantId).orElseThrow(() -> new NotExistsException(tenantId));
if (existing.isAthensTenant()) return existing; // nothing to do
- log.info("Starting migration of " + existing + " to Athenz domain " + tenantDomain.id() +
- " using " + nToken.getPrincipal());
+ log.info("Starting migration of " + existing + " to Athenz domain " + tenantDomain.id());
if (tenantHaving(tenantDomain).isPresent())
throw new IllegalArgumentException("Could not migrate " + existing + " to " + tenantDomain + ": " +
"This domain is already used by " + tenantHaving(tenantDomain).get());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java
deleted file mode 100644
index a9e144a3227..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java
+++ /dev/null
@@ -1,57 +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.hosted.controller.api;
-
-import com.yahoo.config.provision.ApplicationId;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * A DNS alias for an application endpoint.
- *
- * @author smorgrav
- */
-public class ApplicationAlias {
-
- private static final String dnsSuffix = "global.vespa.yahooapis.com";
-
- private final ApplicationId applicationId;
-
- public ApplicationAlias(ApplicationId applicationId) {
- this.applicationId = applicationId;
- }
-
- @Override
- public String toString() {
- return String.format("%s.%s.%s",
- toDns(applicationId.application().value()),
- toDns(applicationId.tenant().value()),
- dnsSuffix);
- }
-
- private String toDns(String id) {
- return id.replace('_', '-');
- }
-
- public URI toHttpUri() {
- try {
- return new URI("http://" + this + ":4080/");
- } catch(URISyntaxException use) {
- throw new RuntimeException("Illegal URI syntax");
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- ApplicationAlias that = (ApplicationAlias) o;
-
- return applicationId.equals(that.applicationId);
- }
-
- @Override
- public int hashCode() { return applicationId.hashCode(); }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java
new file mode 100644
index 00000000000..e4aed04a01c
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java
@@ -0,0 +1,51 @@
+// 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.application;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+
+import java.net.URI;
+
+/**
+ * Represents an application's global rotation.
+ *
+ * @author mpolden
+ */
+public class ApplicationRotation {
+
+ public static final String DNS_SUFFIX = "global.vespa.yahooapis.com";
+ private static final int port = 4080;
+
+ private final URI url;
+ private final RotationId id;
+
+ public ApplicationRotation(ApplicationId application, RotationId id) {
+ this.url = URI.create(String.format("http://%s.%s.%s:%d/",
+ sanitize(application.application().value()),
+ sanitize(application.tenant().value()),
+ DNS_SUFFIX,
+ port));
+ this.id = id;
+ }
+
+ /** ID of the rotation */
+ public RotationId id() {
+ return id;
+ }
+
+ /** URL to this rotation */
+ public URI url() {
+ return url;
+ }
+
+ /** DNS name for this rotation */
+ public String dnsName() {
+ return url.getHost();
+ }
+
+ /** Sanitize by translating '_' to '-' as the former is not allowed in a DNS name */
+ private static String sanitize(String s) {
+ return s.replace('_', '-');
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
index 98ae5ed1762..b9d07249cb2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ClusterSpec.Id;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import java.time.Instant;
import java.util.HashMap;
@@ -18,7 +19,7 @@ import java.util.Objects;
*/
public class Deployment {
- private final Zone zone;
+ private final ZoneId zone;
private final ApplicationRevision revision;
private final Version version;
private final Instant deployTime;
@@ -26,11 +27,11 @@ public class Deployment {
private final Map<Id, ClusterInfo> clusterInfo;
private final DeploymentMetrics metrics;
- public Deployment(Zone zone, ApplicationRevision revision, Version version, Instant deployTime) {
+ public Deployment(ZoneId zone, ApplicationRevision revision, Version version, Instant deployTime) {
this(zone, revision, version, deployTime, new HashMap<>(), new HashMap<>(), new DeploymentMetrics());
}
- public Deployment(Zone zone, ApplicationRevision revision, Version version, Instant deployTime,
+ public Deployment(ZoneId zone, ApplicationRevision revision, Version version, Instant deployTime,
Map<Id, ClusterUtilization> clusterUtils, Map<Id, ClusterInfo> clusterInfo, DeploymentMetrics metrics) {
Objects.requireNonNull(zone, "zone cannot be null");
Objects.requireNonNull(revision, "revision cannot be null");
@@ -49,7 +50,7 @@ public class Deployment {
}
/** Returns the zone this was deployed to */
- public Zone zone() { return zone; }
+ public ZoneId zone() { return zone; }
/** Returns the revision of the application which was deployed */
public ApplicationRevision revision() { return revision; }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
index 98f8c2a3d99..ec8b2d6d019 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
@@ -7,19 +7,17 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import java.time.Instant;
import java.util.Collection;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -161,34 +159,36 @@ public class DeploymentJobs {
/** Job types that exist in the build system */
public enum JobType {
-
- component ("component" ),
- systemTest ("system-test" , zone("test" , "us-east-1" ), zone(SystemName.cd, "test" , "cd-us-central-1")),
- stagingTest ("staging-test" , zone("staging", "us-east-3" ), zone(SystemName.cd, "staging", "cd-us-central-1")),
- productionCorpUsEast1 ("production-corp-us-east-1" , zone("prod" , "corp-us-east-1")),
- productionUsEast3 ("production-us-east-3" , zone("prod" , "us-east-3" )),
- productionUsWest1 ("production-us-west-1" , zone("prod" , "us-west-1" )),
- productionUsCentral1 ("production-us-central-1" , zone("prod" , "us-central-1" )),
- productionApNortheast1 ("production-ap-northeast-1" , zone("prod" , "ap-northeast-1")),
- productionApNortheast2 ("production-ap-northeast-2" , zone("prod" , "ap-northeast-2")),
- productionApSoutheast1 ("production-ap-southeast-1" , zone("prod" , "ap-southeast-1")),
- productionEuWest1 ("production-eu-west-1" , zone("prod" , "eu-west-1" )),
- productionCdUsCentral1 ("production-cd-us-central-1", zone(SystemName.cd, "prod", "cd-us-central-1")),
- productionCdUsCentral2 ("production-cd-us-central-2", zone(SystemName.cd, "prod", "cd-us-central-2"));
+// | enum name ------------| job name ------------------| Zone in main system ---------------------------------------| Zone in CD system -------------------------------------------
+ component ("component" , null , null ),
+ systemTest ("system-test" , ZoneId.from("test" , "us-east-1") , ZoneId.from("test" , "cd-us-central-1")),
+ stagingTest ("staging-test" , ZoneId.from("staging", "us-east-3") , ZoneId.from("staging", "cd-us-central-1")),
+ productionCorpUsEast1 ("production-corp-us-east-1" , ZoneId.from("prod" , "corp-us-east-1") , null ),
+ productionUsEast3 ("production-us-east-3" , ZoneId.from("prod" , "us-east-3") , null ),
+ productionUsWest1 ("production-us-west-1" , ZoneId.from("prod" , "us-west-1") , null ),
+ productionUsCentral1 ("production-us-central-1" , ZoneId.from("prod" , "us-central-1") , null ),
+ productionApNortheast1 ("production-ap-northeast-1" , ZoneId.from("prod" , "ap-northeast-1") , null ),
+ productionApNortheast2 ("production-ap-northeast-2" , ZoneId.from("prod" , "ap-northeast-2") , null ),
+ productionApSoutheast1 ("production-ap-southeast-1" , ZoneId.from("prod" , "ap-southeast-1") , null ),
+ productionEuWest1 ("production-eu-west-1" , ZoneId.from("prod" , "eu-west-1") , null ),
+ productionCdUsCentral1 ("production-cd-us-central-1", null , ZoneId.from("prod" , "cd-us-central-1")),
+ productionCdUsCentral2 ("production-cd-us-central-2", null , ZoneId.from("prod" , "cd-us-central-2"));
private final String jobName;
- private final ImmutableMap<SystemName, Zone> zones;
+ private final ImmutableMap<SystemName, ZoneId> zones;
- JobType(String jobName, Zone... zones) {
+ JobType(String jobName, ZoneId mainZone, ZoneId cdZone) {
this.jobName = jobName;
- this.zones = ImmutableMap.copyOf(Stream.of(zones).collect(Collectors.toMap(zone -> zone.system(),
- zone -> zone)));
+ ImmutableMap.Builder<SystemName, ZoneId> builder = ImmutableMap.builder();
+ if (mainZone != null) builder.put(SystemName.main, mainZone);
+ if (cdZone != null) builder.put(SystemName.cd, cdZone);
+ this.zones = builder.build();
}
public String jobName() { return jobName; }
/** Returns the zone for this job in the given system, or empty if this job does not have a zone */
- public Optional<Zone> zone(SystemName system) {
+ public Optional<ZoneId> zone(SystemName system) {
return Optional.ofNullable(zones.get(system));
}
@@ -207,7 +207,7 @@ public class DeploymentJobs {
/** Returns the region of this job type, or null if it does not have a region */
public Optional<RegionName> region(SystemName system) {
- return zone(system).map(Zone::region);
+ return zone(system).map(ZoneId::region);
}
public static JobType fromJobName(String jobName) {
@@ -217,7 +217,7 @@ public class DeploymentJobs {
}
/** Returns the job type for the given zone */
- public static Optional<JobType> from(SystemName system, Zone zone) {
+ public static Optional<JobType> from(SystemName system, ZoneId zone) {
return Stream.of(values())
.filter(job -> job.zone(system).filter(zone::equals).isPresent())
.findAny();
@@ -229,16 +229,9 @@ public class DeploymentJobs {
case test: return Optional.of(systemTest);
case staging: return Optional.of(stagingTest);
}
- return from(system, new Zone(environment, region));
- }
-
- private static Zone zone(SystemName system, String environment, String region) {
- return new Zone(system, Environment.from(environment), RegionName.from(region));
+ return from(system, ZoneId.from(environment, region));
}
- private static Zone zone(String environment, String region) {
- return new Zone(Environment.from(environment), RegionName.from(region));
- }
}
/** A job report. This class is immutable. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java
deleted file mode 100644
index 7e3abeb77d9..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java
+++ /dev/null
@@ -1,148 +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.hosted.controller.athenz;
-
-import com.yahoo.athenz.auth.token.PrincipalToken;
-import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.OptionalLong;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Represents an Athenz NToken (principal token)
- *
- * @author bjorncs
- */
-// TODO Split out encoding/decoding of token into separate class. Move NToken to controller-api.
-public class NToken {
-
- // Max allowed skew in token timestamp (only for creation, not expiry timestamp)
- private static final int ALLOWED_TIMESTAMP_OFFSET = (int) TimeUnit.SECONDS.toSeconds(300);
-
- private final PrincipalToken token;
-
- // Note: PrincipalToken does not provide any way of constructing an instance from a unsigned token string
- public NToken(String signedToken) {
- try {
- this.token = new PrincipalToken(signedToken);
- if (this.token.getSignature() == null) {
- throw new IllegalArgumentException("Signature missing (unsigned token)");
- }
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Malformed NToken: " + e.getMessage());
- }
- }
-
- public AthenzPrincipal getPrincipal() {
- return new AthenzPrincipal(getDomain(), getUser());
- }
-
- public UserId getUser() {
- return new UserId(token.getName());
- }
-
- public AthenzDomain getDomain() {
- return new AthenzDomain(token.getDomain());
- }
-
- public String getToken() {
- return token.getSignedToken();
- }
-
- public String getKeyId() {
- return token.getKeyId();
- }
-
- public void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException {
- StringBuilder errorMessageBuilder = new StringBuilder();
- if (!token.validate(publicKey, ALLOWED_TIMESTAMP_OFFSET, true, errorMessageBuilder)) {
- throw new InvalidTokenException("NToken is expired or has invalid signature: " + errorMessageBuilder.toString());
- }
- }
-
- @Override
- public String toString() {
- return String.format("NToken(%s)", getToken());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- NToken nToken = (NToken) o;
- return Objects.equals(getToken(), nToken.getToken()); // PrincipalToken does not implement equals()
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getToken()); // PrincipalToken does not implement hashcode()
- }
-
- public static class Builder {
-
- private final String version;
- private final AthenzPrincipal principal;
- private final PrivateKey privateKey;
- private final String keyId;
- private Optional<String> salt = Optional.empty();
- private Optional<String> hostname = Optional.empty();
- private Optional<String> ip = Optional.empty();
- private OptionalLong issueTime = OptionalLong.empty();
- private OptionalLong expirationWindow = OptionalLong.empty();
-
- /**
- * NOTE: We must have some signature, else we might end up with problems later on as
- * {@link PrincipalToken#PrincipalToken(String)} only accepts signed token
- * (supplying an unsigned token to the constructor will result in inconsistent state)
- */
- public Builder(String version, AthenzPrincipal principal, PrivateKey privateKey, String keyId) {
- this.version = version;
- this.principal = principal;
- this.privateKey = privateKey;
- this.keyId = keyId;
- }
-
- public Builder salt(String salt) {
- this.salt = Optional.of(salt);
- return this;
- }
-
- public Builder hostname(String hostname) {
- this.hostname = Optional.of(hostname);
- return this;
- }
-
- public Builder ip(String ip) {
- this.ip = Optional.of(ip);
- return this;
- }
-
- public Builder issueTime(long issueTime) {
- this.issueTime = OptionalLong.of(issueTime);
- return this;
- }
-
- public Builder expirationWindow(long expirationWindow) {
- this.expirationWindow = OptionalLong.of(expirationWindow);
- return this;
- }
-
- public NToken build() {
- PrincipalToken token = new PrincipalToken.Builder(version, principal.getDomain().id(), principal.getName())
- .keyId(this.keyId)
- .salt(this.salt.orElse(null))
- .host(this.hostname.orElse(null))
- .ip(this.ip.orElse(null))
- .issueTime(this.issueTime.orElse(0))
- .expirationWindow(this.expirationWindow.orElse(0))
- .build();
- token.sign(this.privateKey);
- return new NToken(token.getSignedToken());
- }
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java
deleted file mode 100644
index 59548339d11..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java
+++ /dev/null
@@ -1,22 +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.hosted.controller.athenz;
-
-import com.yahoo.athenz.zms.ZMSClientException;
-
-/**
- * @author bjorncs
- */
-public class ZmsException extends RuntimeException {
-
- private final int code;
-
- public ZmsException(ZMSClientException e) {
- super(e.getMessage(), e);
- this.code = e.getCode();
- }
-
-
- public int getCode() {
- return code;
- }
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java
deleted file mode 100644
index f400ba2eb99..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.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.vespa.hosted.controller.athenz;
-
-import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-
-import java.util.List;
-
-/**
- * @author bjorncs
- */
-public interface ZtsClient {
-
- List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal);
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java
index 51865be04fa..328461355db 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java
@@ -6,10 +6,10 @@ import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import java.util.concurrent.Executor;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java
new file mode 100644
index 00000000000..939a5667a36
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java
@@ -0,0 +1,45 @@
+// 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.athenz.filter;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.SslTrustStoreContext;
+import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+/**
+ * Load trust store with Athenz CA certificates
+ *
+ * @author bjorncs
+ */
+public class AthenzTrustStoreConfigurator implements SslTrustStoreConfigurator {
+
+ private final KeyStore trustStore;
+
+ @Inject
+ public AthenzTrustStoreConfigurator(AthenzConfig config) {
+ this.trustStore = createTrustStore(new File(config.athenzCaTrustStore()));
+ }
+
+ private static KeyStore createTrustStore(File trustStoreFile) {
+ try (FileInputStream in = new FileInputStream(trustStoreFile)) {
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ trustStore.load(in, "changeit".toCharArray());
+ return trustStore;
+ } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void configure(SslTrustStoreContext context) {
+ context.updateTrustStore(trustStore);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java
index f43d2d8e80e..69f59ebabe2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java
@@ -1,17 +1,21 @@
// 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.athenz.filter;
+import com.yahoo.athenz.auth.token.PrincipalToken;
import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import java.security.PublicKey;
+import java.time.Duration;
import java.util.Optional;
import java.util.logging.Logger;
-import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE;
+import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE;
/**
* Validates the content of an NToken:
@@ -22,6 +26,9 @@ import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SE
*/
class NTokenValidator {
+ // Max allowed skew in token timestamp (only for creation, not expiry timestamp)
+ private static final long ALLOWED_TIMESTAMP_OFFSET = Duration.ofMinutes(5).getSeconds();
+
private static final Logger log = Logger.getLogger(NTokenValidator.class.getName());
private final ZmsKeystore keystore;
@@ -35,10 +42,15 @@ class NTokenValidator {
}
AthenzPrincipal validate(NToken token) throws InvalidTokenException {
- PublicKey zmsPublicKey = getPublicKey(token.getKeyId())
+ PrincipalToken principalToken = new PrincipalToken(token.getRawToken());
+ PublicKey zmsPublicKey = getPublicKey(principalToken.getKeyId())
.orElseThrow(() -> new InvalidTokenException("NToken has an unknown keyId"));
- validateSignatureAndExpiration(token, zmsPublicKey);
- return token.getPrincipal();
+ validateSignatureAndExpiration(principalToken, zmsPublicKey);
+ return new AthenzPrincipal(
+ AthenzUtils.createAthenzIdentity(
+ new AthenzDomain(principalToken.getDomain()),
+ principalToken.getName()),
+ token);
}
private Optional<PublicKey> getPublicKey(String keyId) throws InvalidTokenException {
@@ -50,13 +62,13 @@ class NTokenValidator {
}
}
- private static void validateSignatureAndExpiration(NToken token, PublicKey zmsPublicKey) throws InvalidTokenException {
- try {
- token.validateSignatureAndExpiration(zmsPublicKey);
- } catch (InvalidTokenException e) {
- // The underlying error message is not user friendly
- logDebug(e.getMessage());
- throw new InvalidTokenException("NToken is expired or has invalid signature");
+ private static void validateSignatureAndExpiration(PrincipalToken token,
+ PublicKey zmsPublicKey) throws InvalidTokenException {
+ StringBuilder errorMessageBuilder = new StringBuilder();
+ if (!token.validate(zmsPublicKey, (int) ALLOWED_TIMESTAMP_OFFSET, true, errorMessageBuilder)) {
+ String message = "NToken is expired or has invalid signature: " + errorMessageBuilder.toString();
+ logDebug(message);
+ throw new InvalidTokenException(message);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
index bfa543f160a..b4859220667 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
@@ -8,13 +8,15 @@ import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import com.yahoo.vespa.hosted.controller.restapi.application.Authorizer;
import java.security.Principal;
+import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.stream.Stream;
@@ -34,11 +36,13 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter {
private static final Logger log = Logger.getLogger(UserAuthWithAthenzPrincipalFilter.class.getName());
private final String userAuthenticationPassThruAttribute;
+ private final String principalHeaderName;
@Inject
public UserAuthWithAthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) {
super(zmsKeystore, executor, config);
this.userAuthenticationPassThruAttribute = config.userAuthenticationPassThruAttribute();
+ this.principalHeaderName = config.principalHeaderName();
}
@Override
@@ -81,13 +85,14 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter {
* NOTE: The Bouncer user roles ({@link DiscFilterRequest#roles} are still intact as they are required
* for {@link Authorizer#isMemberOfVespaBouncerGroup(HttpRequest)}.
*/
- private static void rewriteUserPrincipalToAthenz(DiscFilterRequest request) {
+ private void rewriteUserPrincipalToAthenz(DiscFilterRequest request) {
Principal userPrincipal = request.getUserPrincipal();
log.log(LogLevel.DEBUG, () -> "Original user principal: " + userPrincipal.toString());
UserId userId = new UserId(userPrincipal.getName());
- AthenzPrincipal athenzPrincipal = AthenzUtils.createPrincipal(userId);
- request.setUserPrincipal(athenzPrincipal);
- request.setRemoteUser(athenzPrincipal.toYRN());
+ AthenzUser athenzIdentity = AthenzUser.fromUserId(userId);
+ request.setRemoteUser(athenzIdentity.getFullName());
+ NToken nToken = Optional.ofNullable(request.getHeader(principalHeaderName)).map(NToken::new).orElse(null);
+ request.setUserPrincipal(new AthenzPrincipal(athenzIdentity, nToken));
}
private enum UserAuthenticationResult {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
index 1c32b35f599..a91604f937b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
@@ -10,17 +10,17 @@ import com.yahoo.athenz.auth.token.PrincipalToken;
import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.zms.ZMSClient;
import com.yahoo.athenz.zts.ZTSClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient;
import com.yahoo.vespa.hosted.controller.api.integration.security.KeyService;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZtsClient;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import java.security.PrivateKey;
-import java.util.concurrent.TimeUnit;
+import java.time.Duration;
-import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.USER_PRINCIPAL_DOMAIN;
+import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.USER_PRINCIPAL_DOMAIN;
/**
* @author bjorncs
@@ -51,7 +51,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory {
*/
@Override
public ZtsClient createZtsClientWithServicePrincipal() {
- return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), config);
+ return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), getServicePrivateKey(), config);
}
/**
@@ -59,7 +59,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory {
*/
@Override
public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) {
- PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getToken());
+ PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getRawToken());
AthenzConfig.Service service = config.service();
signedToken.signForAuthorizedService(
config.domain() + "." + service.name(), service.publicKeyId(), getServicePrivateKey());
@@ -75,8 +75,12 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory {
// TODO bjorncs: Cache principal token
SimpleServiceIdentityProvider identityProvider =
new SimpleServiceIdentityProvider(
- athenzPrincipalAuthority, config.domain(), service.name(),
- getServicePrivateKey(), service.publicKeyId(), /*tokenTimeout*/TimeUnit.HOURS.toSeconds(1));
+ athenzPrincipalAuthority,
+ config.domain(),
+ service.name(),
+ getServicePrivateKey(),
+ service.publicKeyId(),
+ Duration.ofMinutes(service.credentialsExpiryMinutes()).getSeconds());
return identityProvider.getIdentity(config.domain(), service.name());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java
new file mode 100644
index 00000000000..3a7a72ac8ae
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java
@@ -0,0 +1,87 @@
+// 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.athenz.impl;
+
+import com.google.inject.Inject;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzSslContextProvider;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient;
+import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzSslContextProviderImpl implements AthenzSslContextProvider {
+
+ private final AthenzClientFactory clientFactory;
+ private final AthenzConfig config;
+
+ @Inject
+ public AthenzSslContextProviderImpl(AthenzClientFactory clientFactory, AthenzConfig config) {
+ this.clientFactory = clientFactory;
+ this.config = config;
+ }
+
+ @Override
+ public SSLContext get() {
+ return createSslContext();
+ }
+
+ private SSLContext createSslContext() {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
+ sslContext.init(createKeyManagersWithServiceCertificate(clientFactory.createZtsClientWithServicePrincipal()),
+ createTrustManagersWithAthenzCa(config),
+ null);
+ return sslContext;
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static KeyManager[] createKeyManagersWithServiceCertificate(ZtsClient ztsClient) {
+ try {
+ AthenzIdentityCertificate identityCertificate = ztsClient.getIdentityCertificate();
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ keyStore.load(null);
+ keyStore.setKeyEntry("athenz-controller-key",
+ identityCertificate.getPrivateKey(),
+ new char[0],
+ new Certificate[]{identityCertificate.getCertificate()});
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keyStore, new char[0]);
+ return keyManagerFactory.getKeyManagers();
+ } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static TrustManager[] createTrustManagersWithAthenzCa(AthenzConfig config) {
+ try {
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream in = new FileInputStream(config.athenzCaTrustStore())) {
+ trustStore.load(in, "changeit".toCharArray());
+ }
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+ return trustManagerFactory.getTrustManagers();
+ } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java
index 110e06b767c..d3fac257583 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java
@@ -13,12 +13,12 @@ import com.yahoo.athenz.zms.ZMSClientException;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzService;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import java.util.Arrays;
@@ -49,16 +49,16 @@ public class ZmsClientImpl implements ZmsClient {
runOrThrow(() -> {
Tenancy tenancy = new Tenancy()
.setDomain(tenantDomain.id())
- .setService(service.toFullServiceName())
+ .setService(service.getFullName())
.setResourceGroups(Collections.emptyList());
- zmsClient.putTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null, tenancy);
+ zmsClient.putTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null, tenancy);
});
}
@Override
public void deleteTenant(AthenzDomain tenantDomain) {
log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service);
- runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null));
+ runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null));
}
@Override
@@ -66,16 +66,16 @@ public class ZmsClientImpl implements ZmsClient {
List<TenantRoleAction> tenantRoleActions = createTenantRoleActions();
log("putProviderResourceGroupRoles(" +
"tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)",
- tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName, tenantRoleActions);
+ tenantDomain, service.getDomain().id(), service.getName(), applicationName, tenantRoleActions);
runOrThrow(() -> {
ProviderResourceGroupRoles resourceGroupRoles = new ProviderResourceGroupRoles()
.setDomain(service.getDomain().id())
- .setService(service.getServiceName())
+ .setService(service.getName())
.setTenant(tenantDomain.id())
.setResourceGroup(applicationName.id())
.setRoles(tenantRoleActions);
zmsClient.putProviderResourceGroupRoles(
- tenantDomain.id(), service.getDomain().id(), service.getServiceName(),
+ tenantDomain.id(), service.getDomain().id(), service.getName(),
applicationName.id(), /*auditref*/null, resourceGroupRoles);
});
}
@@ -83,34 +83,34 @@ public class ZmsClientImpl implements ZmsClient {
@Override
public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName) {
log("deleteProviderResourceGroupRoles(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)",
- tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName);
+ tenantDomain, service.getDomain().id(), service.getName(), applicationName);
runOrThrow(() -> {
zmsClient.deleteProviderResourceGroupRoles(
- tenantDomain.id(), service.getDomain().id(), service.getServiceName(), applicationName.id(), /*auditref*/null);
+ tenantDomain.id(), service.getDomain().id(), service.getName(), applicationName.id(), /*auditref*/null);
});
}
@Override
public boolean hasApplicationAccess(
- AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
+ AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
return hasAccess(
- action.name(), applicationResourceString(tenantDomain, applicationName), principal);
+ action.name(), applicationResourceString(tenantDomain, applicationName), identity);
}
@Override
- public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) {
- return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), principal);
+ public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) {
+ return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), identity);
}
/**
* Used when creating tenancies. As there are no tenancy policies at this point,
- * we cannot use {@link #hasTenantAdminAccess(AthenzPrincipal, AthenzDomain)}
+ * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)}
*/
@Override
- public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) {
- log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", principal);
+ public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
+ log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity);
return getOrThrow(
- () -> zmsClient.getMembership(domain.id(), "admin", principal.toYRN()).getIsMember());
+ () -> zmsClient.getMembership(domain.id(), "admin", identity.getFullName()).getIsMember());
}
@Override
@@ -127,18 +127,18 @@ public class ZmsClientImpl implements ZmsClient {
@Override
public AthenzPublicKey getPublicKey(AthenzService service, String keyId) {
- log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getServiceName(), keyId);
+ log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getName(), keyId);
return getOrThrow(() -> {
- PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getServiceName(), keyId);
+ PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getName(), keyId);
return fromYbase64EncodedKey(entry.getKey(), keyId);
});
}
@Override
public List<AthenzPublicKey> getPublicKeys(AthenzService service) {
- log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getServiceName());
+ log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getName());
return getOrThrow(() -> {
- ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getServiceName());
+ ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getName());
return toAthenzPublicKeys(serviceIdentity.getPublicKeys());
});
}
@@ -163,10 +163,11 @@ public class ZmsClientImpl implements ZmsClient {
.collect(toList());
}
- private boolean hasAccess(String action, String resource, AthenzPrincipal principal) {
- log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, principal);
+ private boolean hasAccess(String action, String resource, AthenzIdentity identity) {
+ log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, identity);
return getOrThrow(
- () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, principal.toYRN()).getGranted());
+ () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, identity.getFullName())
+ .getGranted());
}
private static void log(String format, Object... args) {
@@ -178,7 +179,7 @@ public class ZmsClientImpl implements ZmsClient {
wrappedCode.run();
} catch (ZMSClientException e) {
logWarning(e);
- throw new ZmsException(e);
+ throw new ZmsException(e.getCode(), e);
}
}
@@ -187,7 +188,7 @@ public class ZmsClientImpl implements ZmsClient {
return wrappedCode.get();
} catch (ZMSClientException e) {
logWarning(e);
- throw new ZmsException(e);
+ throw new ZmsException(e.getCode(), e);
}
}
@@ -197,7 +198,7 @@ public class ZmsClientImpl implements ZmsClient {
private String resourceStringPrefix(AthenzDomain tenantDomain) {
return String.format("%s:service.%s.tenant.%s",
- service.getDomain().id(), service.getServiceName(), tenantDomain.id());
+ service.getDomain().id(), service.getName(), tenantDomain.id());
}
private String tenantResourceString(AthenzDomain tenantDomain) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java
index fd58a3daba7..513434f7273 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java
@@ -3,11 +3,11 @@ package com.yahoo.vespa.hosted.controller.athenz.impl;
import com.google.inject.Inject;
import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzService;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import java.security.PublicKey;
import java.util.List;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java
index 1111e56c742..a29f2e81fba 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java
@@ -1,18 +1,27 @@
// 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.athenz.impl;
+import com.yahoo.athenz.auth.util.Crypto;
+import com.yahoo.athenz.zts.InstanceRefreshRequest;
+import com.yahoo.athenz.zts.RoleCertificateRequest;
import com.yahoo.athenz.zts.TenantDomains;
import com.yahoo.athenz.zts.ZTSClient;
import com.yahoo.athenz.zts.ZTSClientException;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzService;
-import com.yahoo.vespa.hosted.controller.athenz.ZtsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZtsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsException;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.List;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import static java.util.stream.Collectors.toList;
@@ -26,25 +35,79 @@ public class ZtsClientImpl implements ZtsClient {
private final ZTSClient ztsClient;
private final AthenzService service;
+ private final PrivateKey privateKey;
+ private final String certificateDnsDomain;
+ private final Duration certExpiry;
- public ZtsClientImpl(ZTSClient ztsClient, AthenzConfig config) {
+ public ZtsClientImpl(ZTSClient ztsClient, PrivateKey privateKey, AthenzConfig config) {
this.ztsClient = ztsClient;
this.service = new AthenzService(config.domain(), config.service().name());
+ this.privateKey = privateKey;
+ this.certificateDnsDomain = config.certDnsDomain();
+ this.certExpiry = Duration.ofMinutes(config.service().credentialsExpiryMinutes());
}
@Override
- public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) {
- log.log(LogLevel.DEBUG, String.format(
- "getTenantDomains(domain=%s, username=%s, rolename=admin, service=%s)",
- service.getDomain().id(), principal, service.getServiceName()));
- try {
+ public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) {
+ return getOrThrow(() -> {
+ log.log(LogLevel.DEBUG, String.format(
+ "getTenantDomains(domain=%s, identity=%s, rolename=admin, service=%s)",
+ service.getDomain().id(), identity.getFullName(), service.getFullName()));
TenantDomains domains = ztsClient.getTenantDomains(
- service.getDomain().id(), principal.toYRN(), "admin", service.getServiceName());
+ service.getDomain().id(), identity.getFullName(), "admin", service.getName());
return domains.getTenantDomainNames().stream()
.map(AthenzDomain::new)
.collect(toList());
+ });
+ }
+
+ @Override
+ public AthenzIdentityCertificate getIdentityCertificate() {
+ return getOrThrow(() -> {
+ log.log(LogLevel.DEBUG,
+ String.format("postInstanceRefreshRequest(service=%s)", service.getFullName()));
+ InstanceRefreshRequest req =
+ ZTSClient.generateInstanceRefreshRequest(
+ service.getDomain().id(),
+ service.getName(),
+ privateKey,
+ certificateDnsDomain,
+ (int) certExpiry.getSeconds());
+ X509Certificate certificate = Crypto.loadX509Certificate(
+ ztsClient.postInstanceRefreshRequest(service.getDomain().id(), service.getName(), req)
+ .getCertificate());
+ return new AthenzIdentityCertificate(certificate, privateKey);
+ });
+ }
+
+ @Override
+ public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) {
+ return getOrThrow(() -> {
+ log.log(LogLevel.DEBUG,
+ String.format("postRoleCertificateRequest(service=%s, roleDomain=%s, roleName=%s)",
+ service.getFullName(), roleDomain.id(), roleName));
+ RoleCertificateRequest req =
+ ZTSClient.generateRoleCertificateRequest(
+ service.getDomain().id(),
+ service.getName(),
+ roleDomain.id(),
+ roleName,
+ privateKey,
+ certificateDnsDomain,
+ (int)certExpiry.getSeconds());
+ X509Certificate roleCertificate = Crypto.loadX509Certificate(
+ ztsClient.postRoleCertificateRequest(roleDomain.id(), roleName, req)
+ .getToken());
+ return new AthenzRoleCertificate(roleCertificate, privateKey);
+ });
+ }
+
+ private static <T> T getOrThrow(Supplier<T> wrappedCode) {
+ try {
+ return wrappedCode.get();
} catch (ZTSClientException e) {
- throw new ZtsException(e);
+ log.warning("Error from Athenz: " + e.getMessage());
+ throw new ZtsException(e.getCode(), e);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
index d4a2d77c115..52a1f2d477d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
@@ -3,10 +3,10 @@ package com.yahoo.vespa.hosted.controller.athenz.mock;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.ZtsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient;
import java.util.logging.Level;
import java.util.logging.Logger;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
index 017e8c7be44..c633d780e30 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
@@ -3,8 +3,8 @@ package com.yahoo.vespa.hosted.controller.athenz.mock;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
import java.util.HashMap;
import java.util.HashSet;
@@ -26,8 +26,8 @@ public class AthenzDbMock {
public static class Domain {
public final AthenzDomain name;
- public final Set<AthenzPrincipal> admins = new HashSet<>();
- public final Set<AthenzPrincipal> tenantAdmins = new HashSet<>();
+ public final Set<AthenzIdentity> admins = new HashSet<>();
+ public final Set<AthenzIdentity> tenantAdmins = new HashSet<>();
public final Map<ApplicationId, Application> applications = new HashMap<>();
public boolean isVespaTenant = false;
@@ -35,13 +35,13 @@ public class AthenzDbMock {
this.name = name;
}
- public Domain admin(AthenzPrincipal user) {
- admins.add(user);
+ public Domain admin(AthenzIdentity identity) {
+ admins.add(identity);
return this;
}
- public Domain tenantAdmin(AthenzPrincipal user) {
- tenantAdmins.add(user);
+ public Domain tenantAdmin(AthenzIdentity identity) {
+ tenantAdmins.add(identity);
return this;
}
@@ -56,7 +56,7 @@ public class AthenzDbMock {
public static class Application {
- public final Map<ApplicationAction, Set<AthenzPrincipal>> acl = new HashMap<>();
+ public final Map<ApplicationAction, Set<AthenzIdentity>> acl = new HashMap<>();
public Application() {
acl.put(ApplicationAction.deploy, new HashSet<>());
@@ -64,8 +64,8 @@ public class AthenzDbMock {
acl.put(ApplicationAction.write, new HashSet<>());
}
- public Application addRoleMember(ApplicationAction action, AthenzPrincipal user) {
- acl.get(action).add(user);
+ public Application addRoleMember(ApplicationAction action, AthenzIdentity identity) {
+ acl.get(action).add(identity);
return this;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
index b2e657eae09..4b50a34094a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
@@ -1,15 +1,14 @@
// 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.athenz.mock;
-import com.yahoo.athenz.zms.ZMSClientException;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzService;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import java.util.ArrayList;
import java.util.List;
@@ -61,28 +60,28 @@ public class ZmsClientMock implements ZmsClient {
}
@Override
- public boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
+ public boolean hasApplicationAccess(AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')",
- principal, action, tenantDomain, applicationName);
+ identity, action, tenantDomain, applicationName);
AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
AthenzDbMock.Application application = domain.applications.get(applicationName);
if (application == null) {
throw zmsException(400, "Application '%s' not found", applicationName);
}
- return domain.admins.contains(principal) || application.acl.get(action).contains(principal);
+ return domain.admins.contains(identity) || application.acl.get(action).contains(identity);
}
@Override
- public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) {
- log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", principal, tenantDomain);
- return isDomainAdmin(principal, tenantDomain) ||
- getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(principal);
+ public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) {
+ log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", identity, tenantDomain);
+ return isDomainAdmin(identity, tenantDomain) ||
+ getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(identity);
}
@Override
- public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) {
- log("isDomainAdmin(principal='%s', domain='%s')", principal, domain);
- return getDomainOrThrow(domain, false).admins.contains(principal);
+ public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
+ log("isDomainAdmin(principal='%s', domain='%s')", identity, domain);
+ return getDomainOrThrow(domain, false).admins.contains(identity);
}
@Override
@@ -111,7 +110,7 @@ public class ZmsClientMock implements ZmsClient {
}
private static ZmsException zmsException(int code, String message, Object... args) {
- return new ZmsException(new ZMSClientException(code, String.format(message, args)));
+ return new ZmsException(code, String.format(message, args));
}
private static void log(String format, Object... args) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java
index f21bc011273..d778fb550ed 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java
@@ -1,10 +1,21 @@
// 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.athenz.mock;
+import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.ZtsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -24,11 +35,51 @@ public class ZtsClientMock implements ZtsClient {
}
@Override
- public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) {
- log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", principal);
+ public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) {
+ log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", identity);
return athenz.domains.values().stream()
- .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal))
+ .filter(domain -> domain.tenantAdmins.contains(identity) || domain.admins.contains(identity))
.map(domain -> domain.name)
.collect(toList());
}
+
+ @Override
+ public AthenzIdentityCertificate getIdentityCertificate() {
+ log.log(Level.INFO, "getIdentityCertificate()");
+ try {
+ KeyPair keyPair = createKeyPair();
+ String subject = "CN=controller";
+ return new AthenzIdentityCertificate(createCertificate(keyPair, subject), keyPair.getPrivate());
+ } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) {
+ log.log(Level.INFO,
+ String.format("getRoleCertificate(roleDomain=%s, roleName=%s)", roleDomain.id(), roleDomain));
+ try {
+ KeyPair keyPair = createKeyPair();
+ String subject = String.format("CN=%s:role.%s", roleDomain.id(), roleName);
+ return new AthenzRoleCertificate(createCertificate(keyPair, subject), keyPair.getPrivate());
+ } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static X509Certificate createCertificate(KeyPair keyPair, String subject) throws
+ OperatorCreationException, IOException {
+ PKCS10CertificationRequest csr =
+ Crypto.getPKCS10CertRequest(
+ Crypto.generateX509CSR(keyPair.getPrivate(), subject, null));
+ return Crypto.generateX509Certificate(csr, keyPair.getPrivate(), new X500Name(subject), 3600, false);
+ }
+
+ private static KeyPair createKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(512);
+ return keyGen.genKeyPair();
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
index 7c06ef27ce9..2bf64571bdf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.LockedApplication;
@@ -46,7 +46,6 @@ public class DeploymentOrder {
/** Returns a list of jobs to trigger after the given job */
// TODO: This does too much - should just tell us the order, as advertised
- // TODO: You're next!
public List<JobType> nextAfter(JobType job, LockedApplication application) {
if ( ! application.deploying().isPresent()) { // Change was cancelled
return Collections.emptyList();
@@ -106,9 +105,9 @@ public class DeploymentOrder {
/** Returns deployments sorted according to declared zones */
public List<Deployment> sortBy(List<DeploymentSpec.DeclaredZone> zones, Collection<Deployment> deployments) {
- List<Zone> productionZones = zones.stream()
+ List<ZoneId> productionZones = zones.stream()
.filter(z -> z.region().isPresent())
- .map(z -> new Zone(z.environment(), z.region().get()))
+ .map(z -> ZoneId.from(z.environment(), z.region().get()))
.collect(toList());
return deployments.stream()
.sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone())))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index 1eee727214b..f0c950b024b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -4,8 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.vespa.curator.Lock;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -28,7 +27,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
import java.util.logging.Logger;
/**
@@ -79,7 +77,7 @@ public class DeploymentTrigger {
* @param report information about the job that just completed
*/
public void triggerFromCompletion(JobReport report) {
- applications().lockedOrThrow(report.applicationId(), application -> {
+ applications().lockOrThrow(report.applicationId(), application -> {
application = application.withJobCompletion(report, clock.instant(), controller);
// Handle successful starting and ending
@@ -132,7 +130,7 @@ public class DeploymentTrigger {
ApplicationList applications = ApplicationList.from(applications().asList());
applications = applications.notPullRequest();
for (Application application : applications.asList())
- applications().lockedIfPresent(application.id(), this::triggerReadyJobs);
+ applications().lockIfPresent(application.id(), this::triggerReadyJobs);
}
/** Find the next step to trigger if any, and triggers it */
@@ -219,7 +217,7 @@ public class DeploymentTrigger {
* @throws IllegalArgumentException if this application already have an ongoing change
*/
public void triggerChange(ApplicationId applicationId, Change change) {
- applications().lockedOrThrow(applicationId, application -> {
+ applications().lockOrThrow(applicationId, application -> {
if (application.deploying().isPresent() && ! application.deploymentJobs().hasFailures())
throw new IllegalArgumentException("Could not start " + change + " on " + application + ": " +
application.deploying().get() + " is already in progress");
@@ -238,7 +236,7 @@ public class DeploymentTrigger {
* @param applicationId the application to trigger
*/
public void cancelChange(ApplicationId applicationId) {
- applications().lockedOrThrow(applicationId, application -> {
+ applications().lockOrThrow(applicationId, application -> {
buildSystem.removeJobs(application.id());
applications().store(application.withDeploying(Optional.empty()));
});
@@ -360,7 +358,7 @@ public class DeploymentTrigger {
*/
private boolean isOnNewerVersionInProductionThan(Version version, Application application, JobType job) {
if ( ! job.isProduction()) return false;
- Optional<Zone> zone = job.zone(controller.system());
+ Optional<ZoneId> zone = job.zone(controller.system());
if ( ! zone.isPresent()) return false;
Deployment existingDeployment = application.deployments().get(zone.get());
if (existingDeployment == null) return false;
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 09f8df58205..2b7260d5ffa 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
@@ -1,7 +1,6 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.Tenant;
@@ -86,7 +85,7 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
}
protected void store(IssueId issueId, ApplicationId applicationId) {
- controller().applications().lockedIfPresent(applicationId, application ->
+ controller().applications().lockIfPresent(applicationId, application ->
controller().applications().store(application.withOwnershipIssueId(issueId)));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
index ae617f87be6..ad7fa90967b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
@@ -4,10 +4,9 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.Zone;
-import com.yahoo.vespa.curator.Lock;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.LockedApplication;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeList;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
@@ -46,7 +45,7 @@ public class ClusterInfoMaintainer extends Maintainer {
return node.membership.clusterId;
}
- private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes, Zone zone) {
+ private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes, ZoneId zone) {
Map<ClusterSpec.Id, ClusterInfo> infoMap = new HashMap<>();
// Group nodes by clusterid
@@ -65,7 +64,8 @@ public class ClusterInfoMaintainer extends Maintainer {
double cpu = 0;
double mem = 0;
double disk = 0;
- if (zone.nodeFlavors().isPresent()) {
+ // TODO: This code was never run. Reenable when flavours are available from a FlavorRegistry or something, or remove.
+ /*if (zone.nodeFlavors().isPresent()) {
Optional<Flavor> flavorOptional = zone.nodeFlavors().get().getFlavor(node.flavor);
if ((flavorOptional.isPresent())) {
Flavor flavor = flavorOptional.get();
@@ -73,7 +73,7 @@ public class ClusterInfoMaintainer extends Maintainer {
mem = flavor.getMinMainMemoryAvailableGb();
disk = flavor.getMinMainMemoryAvailableGb();
}
- }
+ }*/
// Add to map
List<String> hostnames = clusterNodes.stream().map(node1 -> node1.hostname).collect(Collectors.toList());
@@ -93,7 +93,7 @@ public class ClusterInfoMaintainer extends Maintainer {
try {
NodeList nodes = controller().applications().configserverClient().getNodeList(deploymentId);
Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes, deployment.zone());
- controller().applications().lockedIfPresent(application.id(), lockedApplication ->
+ controller().applications().lockIfPresent(application.id(), lockedApplication ->
controller.applications().store(lockedApplication.withClusterInfo(deployment.zone(), clusterInfo)));
}
catch (IOException | IllegalArgumentException e) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
index 3744be67135..58e32344372 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
@@ -3,8 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.vespa.curator.Lock;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
@@ -30,7 +29,7 @@ public class ClusterUtilizationMaintainer extends Maintainer {
this.controller = controller;
}
- private Map<ClusterSpec.Id, ClusterUtilization> getUpdatedClusterUtilizations(ApplicationId app, Zone zone) {
+ private Map<ClusterSpec.Id, ClusterUtilization> getUpdatedClusterUtilizations(ApplicationId app, ZoneId zone) {
Map<String, MetricsService.SystemMetrics> systemMetrics = controller.metricsService().getSystemMetrics(app, zone);
Map<ClusterSpec.Id, ClusterUtilization> utilizationMap = new HashMap<>();
@@ -50,7 +49,7 @@ public class ClusterUtilizationMaintainer extends Maintainer {
Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization = getUpdatedClusterUtilizations(application.id(), deployment.zone());
- controller().applications().lockedIfPresent(application.id(), lockedApplication ->
+ controller().applications().lockIfPresent(application.id(), lockedApplication ->
controller().applications().store(lockedApplication.withClusterUtilization(deployment.zone(), clusterUtilization)));
}
}
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 bc2112ac0ca..4e9dd94d8e5 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.AbstractComponent;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues;
import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
@@ -34,11 +35,13 @@ public class ControllerMaintenance extends AbstractComponent {
private final ClusterUtilizationMaintainer clusterUtilizationMaintainer;
private final DeploymentMetricsMaintainer deploymentMetricsMaintainer;
private final ApplicationOwnershipConfirmer applicationOwnershipConfirmer;
+ private final DnsMaintainer dnsMaintainer;
@SuppressWarnings("unused") // instantiated by Dependency Injection
public ControllerMaintenance(MaintainerConfig maintainerConfig, Controller controller, CuratorDb curator,
JobControl jobControl, Metric metric, Chef chefClient,
- DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues) {
+ DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues,
+ NameService nameService) {
Duration maintenanceInterval = Duration.ofMinutes(maintainerConfig.intervalMinutes());
this.jobControl = jobControl;
deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl);
@@ -52,6 +55,7 @@ public class ControllerMaintenance extends AbstractComponent {
clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl);
deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl);
applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(12), jobControl, ownershipIssues);
+ dnsMaintainer = new DnsMaintainer(controller, Duration.ofHours(1), jobControl, nameService);
}
public Upgrader upgrader() { return upgrader; }
@@ -72,6 +76,7 @@ public class ControllerMaintenance extends AbstractComponent {
clusterInfoMaintainer.deconstruct();
deploymentMetricsMaintainer.deconstruct();
applicationOwnershipConfirmer.deconstruct();
+ dnsMaintainer.deconstruct();
}
}
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 eb44229e790..5638ff28904 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
@@ -53,14 +53,9 @@ public class DeploymentExpirer extends Maintainer {
}
public static boolean hasExpired(ZoneRegistry zoneRegistry, Deployment deployment, Instant now) {
- return zoneRegistry.getDeploymentTimeToLive(deployment.zone().environment(), deployment.zone().region())
- .map(duration -> getExpiration(deployment, duration))
- .map(now::isAfter)
+ return zoneRegistry.getDeploymentTimeToLive(deployment.zone())
+ .map(timeToLive -> deployment.at().plus(timeToLive).isBefore(now))
.orElse(false);
}
- private static Instant getExpiration(Deployment instance, Duration ttl) {
- return instance.at().plus(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 ae6ba364d25..324868878af 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.config.provision.ApplicationId;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.Tenant;
@@ -131,7 +130,7 @@ public class DeploymentIssueReporter extends Maintainer {
}
private void store(ApplicationId id, IssueId issueId) {
- controller().applications().lockedIfPresent(id, application ->
+ controller().applications().lockIfPresent(id, application ->
controller().applications().store(application.withDeploymentIssueId(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 13eb5075f34..821efba013d 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 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;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
@@ -34,7 +33,7 @@ public class DeploymentMetricsMaintainer extends Maintainer {
boolean hasWarned = false;
for (Application application : ApplicationList.from(controller().applications().asList()).notPullRequest().asList()) {
try {
- controller().applications().lockedIfPresent(application.id(), lockedApplication ->
+ controller().applications().lockIfPresent(application.id(), lockedApplication ->
controller().applications().store(lockedApplication.with(controller().metricsService().getApplicationMetrics(application.id()))));
for (Deployment deployment : application.deployments().values()) {
@@ -46,7 +45,7 @@ public class DeploymentMetricsMaintainer extends Maintainer {
deploymentMetrics.queryLatencyMillis(),
deploymentMetrics.writeLatencyMillis());
- controller().applications().lockedIfPresent(application.id(), lockedApplication ->
+ controller().applications().lockIfPresent(application.id(), lockedApplication ->
controller().applications().store(lockedApplication.with(deployment.zone(), appMetrics)));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java
new file mode 100644
index 00000000000..89394bf4dd9
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java
@@ -0,0 +1,67 @@
+// 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.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
+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.application.ApplicationRotation;
+import com.yahoo.vespa.hosted.controller.rotation.Rotation;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.rotation.RotationRepository;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ * Performs DNS maintenance tasks such as removing DNS aliases for unassigned rotations.
+ *
+ * @author mpolden
+ */
+public class DnsMaintainer extends Maintainer {
+
+ private static final Logger log = Logger.getLogger(DnsMaintainer.class.getName());
+
+ private final NameService nameService;
+
+ public DnsMaintainer(Controller controller, Duration interval, JobControl jobControl,
+ NameService nameService) {
+ super(controller, interval, jobControl);
+ this.nameService = nameService;
+ }
+
+ private RotationRepository rotationRepository() {
+ return controller().applications().rotationRepository();
+ }
+
+ @Override
+ protected void maintain() {
+ try (RotationLock lock = rotationRepository().lock()) {
+ Map<RotationId, Rotation> unassignedRotations = rotationRepository().availableRotations(lock);
+ unassignedRotations.values().forEach(this::removeDnsAlias);
+ }
+ }
+
+ /** Remove DNS alias for unassigned rotation */
+ private void removeDnsAlias(Rotation rotation) {
+ // When looking up CNAME by data, the data must be a FQDN
+ Optional<Record> record = nameService.findRecord(Record.Type.CNAME, RecordData.fqdn(rotation.name()));
+ record.filter(this::canUpdate)
+ .ifPresent(r -> {
+ log.warning(String.format("Want to remove DNS record %s (%s) because it points to the unassigned " +
+ "rotation %s (%s)", record.get().id().asString(),
+ record.get().name().asString(), rotation.id().asString(), rotation.name()));
+ // TODO: Actually remove the record
+ //nameService.removeRecord(r.id());
+ });
+ }
+
+ /** Returns whether we can update the given record */
+ private boolean canUpdate(Record record) {
+ return record.name().asString().endsWith(ApplicationRotation.DNS_SUFFIX);
+ }
+
+}
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 01e53ce4f79..ab388ca9a9f 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
@@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNode;
import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
+import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
import java.time.Clock;
import java.time.Duration;
@@ -23,11 +24,14 @@ import java.util.Optional;
/**
* @author mortent
+ * @author mpolden
*/
public class MetricsReporter extends Maintainer {
public static final String convergeMetric = "seconds.since.last.chef.convergence";
public static final String deploymentFailMetric = "deployment.failurePercentage";
+ public static final String remainingRotations = "remaining_rotations";
+
private final Metric metric;
private final Chef chefClient;
private final Clock clock;
@@ -51,6 +55,14 @@ public class MetricsReporter extends Maintainer {
public void maintain() {
reportChefMetrics();
reportDeploymentMetrics();
+ reportRemainingRotations();
+ }
+
+ private void reportRemainingRotations() {
+ try (RotationLock lock = controller().applications().rotationRepository().lock()) {
+ int availableRotations = controller().applications().rotationRepository().availableRotations(lock).size();
+ metric.set(remainingRotations, availableRotations, metric.createContext(Collections.emptyMap()));
+ }
}
private void reportChefMetrics() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 23316a74aae..762f12c3e8a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -8,7 +8,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
@@ -27,6 +27,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import java.time.Instant;
import java.util.ArrayList;
@@ -55,6 +56,7 @@ public class ApplicationSerializer {
private final String ownershipIssueIdField = "ownershipIssueId";
private final String writeQualityField = "writeQuality";
private final String queryQualityField = "queryQuality";
+ private final String rotationField = "rotation";
// Deployment fields
private final String zoneField = "zone";
@@ -114,7 +116,7 @@ public class ApplicationSerializer {
private final String deploymentMetricsQueryLatencyField = "queryLatencyMillis";
private final String deploymentMetricsWriteLatencyField = "writeLatencyMillis";
-
+
// ------------------ Serialization
public Slime toSlime(Application application) {
@@ -130,6 +132,7 @@ public class ApplicationSerializer {
application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
root.setDouble(queryQualityField, application.metrics().queryServiceQuality());
root.setDouble(writeQualityField, application.metrics().writeServiceQuality());
+ application.rotation().ifPresent(rotation -> root.setString(rotationField, rotation.id().asString()));
return slime;
}
@@ -139,7 +142,7 @@ public class ApplicationSerializer {
}
private void deploymentToSlime(Deployment deployment, Cursor object) {
- zoneToSlime(deployment.zone(), object.setObject(zoneField));
+ zoneIdToSlime(deployment.zone(), object.setObject(zoneField));
object.setString(versionField, deployment.version().toString());
object.setLong(deployTimeField, deployment.at().toEpochMilli());
toSlime(deployment.revision(), object.setObject(applicationPackageRevisionField));
@@ -191,7 +194,7 @@ public class ApplicationSerializer {
object.setDouble(clusterUtilsDiskBusyField, utils.getDiskBusy());
}
- private void zoneToSlime(Zone zone, Cursor object) {
+ private void zoneIdToSlime(ZoneId zone, Cursor object) {
object.setString(environmentField, zone.environment().value());
object.setString(regionField, zone.region().value());
}
@@ -267,9 +270,10 @@ public class ApplicationSerializer {
Optional<IssueId> ownershipIssueId = optionalString(root.field(ownershipIssueIdField)).map(IssueId::from);
ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(),
root.field(writeQualityField).asDouble());
+ Optional<RotationId> rotation = rotationFromSlime(root.field(rotationField));
- return new Application(id, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics);
+ return new Application(id, deploymentSpec, validationOverrides, deployments, deploymentJobs, deploying,
+ outstandingChange, ownershipIssueId, metrics, rotation);
}
private List<Deployment> deploymentsFromSlime(Inspector array) {
@@ -279,13 +283,13 @@ public class ApplicationSerializer {
}
private Deployment deploymentFromSlime(Inspector deploymentObject) {
- return new Deployment(zoneFromSlime(deploymentObject.field(zoneField)),
+ return new Deployment(zoneIdFromSlime(deploymentObject.field(zoneField)),
applicationRevisionFromSlime(deploymentObject.field(applicationPackageRevisionField)).get(),
Version.fromString(deploymentObject.field(versionField).asString()),
Instant.ofEpochMilli(deploymentObject.field(deployTimeField).asLong()),
- clusterUtilsMapFromSlime(deploymentObject.field(clusterUtilsField)),
- clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)),
- deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField)));
+ clusterUtilsMapFromSlime(deploymentObject.field(clusterUtilsField)),
+ clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)),
+ deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField)));
}
private DeploymentMetrics deploymentMetricsFromSlime(Inspector object) {
@@ -334,9 +338,8 @@ public class ApplicationSerializer {
return new ClusterInfo(flavor, cost, flavorCpu, flavorMem, flavorDisk, ClusterSpec.Type.from(type), hostnames);
}
- private Zone zoneFromSlime(Inspector object) {
- return new Zone(Environment.from(object.field(environmentField).asString()),
- RegionName.from(object.field(regionField).asString()));
+ private ZoneId zoneIdFromSlime(Inspector object) {
+ return ZoneId.from(object.field(environmentField).asString(), object.field(regionField).asString());
}
private Optional<ApplicationRevision> applicationRevisionFromSlime(Inspector object) {
@@ -403,6 +406,10 @@ public class ApplicationSerializer {
Instant.ofEpochMilli(object.field(atField).asLong())));
}
+ private Optional<RotationId> rotationFromSlime(Inspector field) {
+ return field.valid() ? optionalString(field).map(RotationId::new) : Optional.empty();
+ }
+
private Optional<Long> optionalLong(Inspector field) {
return field.valid() ? Optional.of(field.asLong()) : Optional.empty();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java
index 3fbfdd31808..fb6608ea643 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java
@@ -6,12 +6,10 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.Tenant;
import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
/**
* Used to store the permanent data of the controller.
@@ -19,55 +17,46 @@ import java.util.Set;
* @author Stian Kristoffersen
* @author bratseth
*/
-public abstract class ControllerDb {
+public interface ControllerDb {
// --------- Tenants
- public abstract void createTenant(Tenant tenant);
+ void createTenant(Tenant tenant);
- public abstract void updateTenant(Tenant tenant) throws PersistenceException;
+ // TODO: Remove exception from all signatures
+ void updateTenant(Tenant tenant) throws PersistenceException;
- public abstract void deleteTenant(TenantId tenantId) throws PersistenceException;
+ void deleteTenant(TenantId tenantId) throws PersistenceException;
- public abstract Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException;
+ Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException;
- public abstract List<Tenant> listTenants();
+ List<Tenant> listTenants();
// --------- Applications
// ONLY call this from ApplicationController.store()
- public abstract void store(Application application);
+ void store(Application application);
- public abstract void deleteApplication(ApplicationId applicationId);
+ void deleteApplication(ApplicationId applicationId);
- public abstract Optional<Application> getApplication(ApplicationId applicationId);
+ Optional<Application> getApplication(ApplicationId applicationId);
/** Returns all applications */
- public abstract List<Application> listApplications();
+ List<Application> listApplications();
/** Returns all applications of a tenant */
- public abstract List<Application> listApplications(TenantId tenantId);
-
- // --------- Rotations
-
- public abstract Set<RotationId> getRotations();
-
- public abstract Set<RotationId> getRotations(ApplicationId applicationId);
-
- public abstract boolean assignRotation(RotationId rotationId, ApplicationId applicationId);
-
- public abstract Set<RotationId> deleteRotations(ApplicationId applicationId);
+ List<Application> listApplications(TenantId tenantId);
/** Returns the given elements joined by dot "." */
- protected String path(Identifier... elements) {
+ default String path(Identifier... elements) {
return Joiner.on(".").join(elements);
}
- protected String path(String... elements) {
+ default String path(String... elements) {
return Joiner.on(".").join(elements);
}
- protected String path(ApplicationId applicationId) {
+ default String path(ApplicationId applicationId) {
return applicationId.tenant().value() + "." + applicationId.application().value() + "." + applicationId.instance().value();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index e5616f025ce..a3bb191fc38 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -39,6 +39,8 @@ public class CuratorDb {
private static final Path root = Path.fromString("/controller/v1");
+ private static final Path lockRoot = root.append("locks");
+
private static final Duration defaultLockTimeout = Duration.ofMinutes(5);
private final StringSetSerializer stringSetSerializer = new StringSetSerializer();
@@ -67,6 +69,10 @@ public class CuratorDb {
return lock(lockPath(id), timeout);
}
+ public Lock lockRotations() {
+ return lock(lockRoot.append("rotations"), defaultLockTimeout);
+ }
+
/** Create a reentrant lock */
private Lock lock(Path path, Duration timeout) {
Lock lock = locks.computeIfAbsent(path, (pathArg) -> new Lock(pathArg.getAbsolute(), curator));
@@ -75,18 +81,18 @@ public class CuratorDb {
}
public Lock lockInactiveJobs() {
- return lock(root.append("locks").append("inactiveJobsLock"), defaultLockTimeout);
+ return lock(lockRoot.append("inactiveJobsLock"), defaultLockTimeout);
}
public Lock lockJobQueues() {
- return lock(root.append("locks").append("jobQueuesLock"), defaultLockTimeout);
+ return lock(lockRoot.append("jobQueuesLock"), defaultLockTimeout);
}
public Lock lockMaintenanceJob(String jobName) {
// Use a short timeout such that if maintenance jobs are started at about the same time on different nodes
// and the maintenance job takes a long time to complete, only one of the nodes will run the job
// in each maintenance interval
- return lock(root.append("locks").append("maintenanceJobLocks").append(jobName), Duration.ofSeconds(1));
+ return lock(lockRoot.append("maintenanceJobLocks").append(jobName), Duration.ofSeconds(1));
}
public Lock lockProvisionState(String provisionStateId) {
@@ -94,11 +100,11 @@ public class CuratorDb {
}
public Lock lockVespaServerPool() {
- return lock(root.append("locks").append("vespaServerPoolLock"), Duration.ofSeconds(1));
+ return lock(lockRoot.append("vespaServerPoolLock"), Duration.ofSeconds(1));
}
public Lock lockOpenStackServerPool() {
- return lock(root.append("locks").append("openStackServerPoolLock"), Duration.ofSeconds(1));
+ return lock(lockRoot.append("openStackServerPoolLock"), Duration.ofSeconds(1));
}
// -------------- Read and write --------------------------------------------------
@@ -222,19 +228,15 @@ public class CuratorDb {
// -------------- Paths --------------------------------------------------
- private Path systemVersionPath() {
- return root.append("systemVersion");
- }
-
private Path lockPath(TenantId tenant) {
- Path lockPath = root.append("locks")
+ Path lockPath = lockRoot
.append(tenant.id());
curator.create(lockPath);
return lockPath;
}
private Path lockPath(ApplicationId application) {
- Path lockPath = root.append("locks")
+ Path lockPath = lockRoot
.append(application.tenant().value())
.append(application.application().value())
.append(application.instance().value());
@@ -243,7 +245,7 @@ public class CuratorDb {
}
private Path lockPath(String provisionId) {
- Path lockPath = root.append("locks")
+ Path lockPath = lockRoot
.append(provisionStatePath())
.append(provisionId);
curator.create(lockPath);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java
index ab240b9dea9..2c5d77c7773 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java
@@ -6,7 +6,6 @@ import com.yahoo.vespa.hosted.controller.AlreadyExistsException;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.NotExistsException;
import com.yahoo.vespa.hosted.controller.api.Tenant;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import java.util.ArrayList;
@@ -14,7 +13,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -22,11 +20,10 @@ import java.util.stream.Collectors;
*
* @author Stian Kristoffersen
*/
-public class MemoryControllerDb extends ControllerDb {
+public class MemoryControllerDb implements ControllerDb {
private final Map<TenantId, Tenant> tenants = new HashMap<>();
private final Map<String, Application> applications = new HashMap<>();
- private final Map<RotationId, ApplicationId> rotationAssignments = new HashMap<>();
@Override
public void createTenant(Tenant tenant) {
@@ -52,7 +49,7 @@ public class MemoryControllerDb extends ControllerDb {
}
@Override
- public Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException {
+ public Optional<Tenant> getTenant(TenantId tenantId) {
return Optional.ofNullable(tenants.get(tenantId));
}
@@ -88,36 +85,4 @@ public class MemoryControllerDb extends ControllerDb {
.collect(Collectors.toList());
}
- @Override
- public Set<RotationId> getRotations() {
- return rotationAssignments.keySet();
- }
-
- @Override
- public Set<RotationId> getRotations(ApplicationId applicationId) {
- return rotationAssignments.entrySet().stream()
- .filter(entry -> entry.getValue().equals(applicationId))
- .map(Map.Entry::getKey)
- .collect(Collectors.toSet());
- }
-
- @Override
- public boolean assignRotation(RotationId rotationId, ApplicationId applicationId) {
- if (rotationAssignments.containsKey(rotationId)) {
- return false;
- } else {
- rotationAssignments.put(rotationId, applicationId);
- return true;
- }
- }
-
- @Override
- public Set<RotationId> deleteRotations(ApplicationId applicationId) {
- Set<RotationId> rotations = getRotations(applicationId);
- for (RotationId rotation : rotations) {
- rotationAssignments.remove(rotation);
- }
- return rotations;
- }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
index e8b68d0c55a..e67b96c22ad 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
@@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.http.HttpRequest.Method;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
@@ -54,11 +54,10 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
return createDiscoveryResponse(proxyRequest);
}
- Environment environment = Environment.from(proxyRequest.getEnvironment());
- RegionName region = RegionName.from(proxyRequest.getRegion());
+ ZoneId zoneId = ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion());
// Make a local copy of the list as we want to manipulate it in case of ping problems.
- final List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerUris(environment, region));
+ List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerUris(zoneId));
StringBuilder errorBuilder = new StringBuilder();
if (queueFirstServerIfDown(allServers)) {
@@ -83,8 +82,8 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
ObjectMapper mapper = new ObjectMapper();
DiscoveryResponseStructure responseStructure = new DiscoveryResponseStructure();
- List<Zone> zones = zoneRegistry.zones();
- for (Zone zone : zones) {
+ List<ZoneId> zones = zoneRegistry.zones();
+ for (ZoneId zone : zones) {
if (!"".equals(proxyRequest.getEnvironment()) &&
!proxyRequest.getEnvironment().equals(zone.environment().value())) {
continue;
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 d7324450d4c..d15686077c6 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,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
@@ -21,7 +21,6 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.AlreadyExistsException;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -53,7 +52,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
@@ -66,9 +64,12 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentCost;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.Path;
@@ -93,7 +94,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
-import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Level;
@@ -241,7 +241,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
String userIdString = request.getProperty("userOverride");
if (userIdString == null)
userIdString = userFrom(request)
- .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride"));
+ .map(UserId::id)
+ .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride"));
UserId userId = new UserId(userIdString);
List<Tenant> tenants = controller.tenants().asList(userId);
@@ -376,13 +377,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Compile version. The version that should be used when building an application
object.setString("compileVersion", application.oldestDeployedVersion().orElse(controller.systemVersion()).toFullString());
- // Rotations
+ // Rotation
Cursor globalRotationsArray = object.setArray("globalRotations");
- Set<URI> rotations = controller.getRotationUris(application.id());
- Map<String, RotationStatus> rotationHealthStatus =
- rotations.isEmpty() ? Collections.emptyMap() : controller.getHealthStatus(rotations.iterator().next().getHost());
- for (URI rotation : rotations)
- globalRotationsArray.addString(rotation.toString());
+ application.rotation().ifPresent(rotation -> {
+ globalRotationsArray.addString(rotation.url().toString());
+ object.setString("rotationId", rotation.id().asString());
+ });
// Deployments sorted according to deployment spec
List<Deployment> deployments = controller.applications().deploymentTrigger()
@@ -395,8 +395,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
deploymentObject.setString("environment", deployment.zone().environment().value());
deploymentObject.setString("region", deployment.zone().region().value());
deploymentObject.setString("instance", application.id().instance().value()); // pointless
- if ( ! rotations.isEmpty())
+ if (application.rotation().isPresent()) {
+ Map<String, RotationStatus> rotationHealthStatus = application.rotation()
+ .map(rotation -> controller.getHealthStatus(rotation.dnsName()))
+ .orElse(Collections.emptyMap());
setRotationStatus(deployment, rotationHealthStatus, deploymentObject);
+ }
if (recurseOverDeployments(request)) // List full deployment information when recursive.
toSlime(deploymentObject, new DeploymentId(application.id(), deployment.zone()), deployment, request);
@@ -423,11 +427,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.orElseThrow(() -> new NotExistsException(id + " not found"));
DeploymentId deploymentId = new DeploymentId(application.id(),
- new Zone(Environment.from(environment), RegionName.from(region)));
+ ZoneId.from(environment, region));
- Deployment deployment = application.deployments().get(deploymentId.zone());
+ Deployment deployment = application.deployments().get(deploymentId.zoneId());
if (deployment == null)
- throw new NotExistsException(application + " is not deployed in " + deploymentId.zone());
+ throw new NotExistsException(application + " is not deployed in " + deploymentId.zoneId());
Slime slime = new Slime();
toSlime(slime.setObject(), deploymentId, deployment, request);
@@ -443,18 +447,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
serviceUrlArray.addString(uri.toString());
}
- response.setString("nodes", withPath("/zone/v2/" + deploymentId.zone().environment() + "/" + deploymentId.zone().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString());
+ response.setString("nodes", withPath("/zone/v2/" + deploymentId.zoneId().environment() + "/" + deploymentId.zoneId().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString());
- URI elkUrl = controller.getElkUri(deploymentId);
- if (elkUrl != null)
- response.setString("elkUrl", elkUrl.toString());
+ controller.getLogServerUrl(deploymentId)
+ .ifPresent(elkUrl -> response.setString("elkUrl", elkUrl.toString()));
response.setString("yamasUrl", monitoringSystemUri(deploymentId).toString());
response.setString("version", deployment.version().toFullString());
response.setString("revision", deployment.revision().id());
response.setLong("deployTimeEpochMs", deployment.at().toEpochMilli());
- Optional<Duration> deploymentTimeToLive = controller.zoneRegistry().getDeploymentTimeToLive(deploymentId.zone().environment(), deploymentId.zone().region());
- deploymentTimeToLive.ifPresent(duration -> response.setLong("expiryTimeEpochMs", deployment.at().plus(duration).toEpochMilli()));
+ controller.zoneRegistry().getDeploymentTimeToLive(deploymentId.zoneId())
+ .ifPresent(deploymentTimeToLive -> response.setLong("expiryTimeEpochMs", deployment.at().plus(deploymentTimeToLive).toEpochMilli()));
controller.applications().get(deploymentId.applicationId()).flatMap(application -> application.deploymentJobs().projectId())
.ifPresent(i -> response.setString("screwdriverId", String.valueOf(i)));
@@ -489,9 +492,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private URI monitoringSystemUri(DeploymentId deploymentId) {
- return controller.zoneRegistry().getMonitoringSystemUri(deploymentId.zone().environment(),
- deploymentId.zone().region(),
- deploymentId.applicationId());
+ return controller.zoneRegistry().getMonitoringSystemUri(deploymentId);
}
private HttpResponse setGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region, boolean inService, HttpRequest request) {
@@ -507,14 +508,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Inspector requestData = toSlime(request.getData()).get();
String reason = mandatory("reason", requestData).asString();
- String agent = authorizer.getUserId(request).toString();
+ String agent = authorizer.getIdentity(request).getFullName();
long timestamp = controller.clock().instant().getEpochSecond();
EndpointStatus.Status status = inService ? EndpointStatus.Status.in : EndpointStatus.Status.out;
EndpointStatus endPointStatus = new EndpointStatus(status, reason, agent, timestamp);
// DeploymentId identifies the zone and application we are dealing with
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
- new Zone(Environment.from(environment), RegionName.from(region)));
+ ZoneId.from(environment, region));
try {
List<String> rotations = controller.applications().setGlobalRotationStatus(deploymentId, endPointStatus);
return new MessageResponse(String.format("Rotations %s successfully set to %s service", rotations.toString(), inService ? "in" : "out of"));
@@ -526,7 +527,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse getGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
- new Zone(Environment.from(environment), RegionName.from(region)));
+ ZoneId.from(environment, region));
Slime slime = new Slime();
Cursor c1 = slime.setObject().setArray("globalrotationoverride");
@@ -549,17 +550,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse rotationStatus(String tenantName, String applicationName, String instanceName, String environment, String region) {
-
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
- Set<URI> rotations = controller.getRotationUris(applicationId);
- if (rotations.isEmpty())
+ Application application = controller.applications().require(applicationId);
+ if (!application.rotation().isPresent()) {
throw new NotExistsException("global rotation does not exist for '" + environment + "." + region + "'");
+ }
Slime slime = new Slime();
Cursor response = slime.setObject();
- Map<String, RotationStatus> rotationHealthStatus = controller.getHealthStatus(rotations.iterator().next().getHost());
-
+ Map<String, RotationStatus> rotationHealthStatus = controller.getHealthStatus(application.rotation().get().dnsName());
for (String rotationEndpoint : rotationHealthStatus.keySet()) {
if (rotationEndpoint.contains(toDns(environment)) && rotationEndpoint.contains(toDns(region))) {
Cursor bcpStatusObject = response.setObject("bcpStatus");
@@ -572,15 +572,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse waitForConvergence(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
return new JacksonJsonResponse(controller.waitForConfigConvergence(new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
- new Zone(Environment.from(environment), RegionName.from(region))),
+ ZoneId.from(environment, region)),
asLong(request.getProperty("timeout"), 1000)));
}
private HttpResponse services(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
ApplicationView applicationView = controller.getApplicationView(tenantName, applicationName, instanceName, environment, region);
- ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.from(environment), RegionName.from(region)),
+ ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(environment, region),
new ApplicationId.Builder().tenant(tenantName).applicationName(applicationName).instanceName(instanceName).build(),
- controller.getConfigServerUris(Environment.from(environment), RegionName.from(region)),
+ controller.getConfigServerUris(ZoneId.from(environment, region)),
request.getUri());
response.setResponse(applicationView);
return response;
@@ -588,24 +588,24 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath, HttpRequest request) {
Map<?,?> result = controller.getServiceApiResponse(tenantName, applicationName, instanceName, environment, region, serviceName, restPath);
- ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.from(environment), RegionName.from(region)),
+ ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(environment, region),
new ApplicationId.Builder().tenant(tenantName).applicationName(applicationName).instanceName(instanceName).build(),
- controller.getConfigServerUris(Environment.from(environment), RegionName.from(region)),
+ controller.getConfigServerUris(ZoneId.from(environment, region)),
request.getUri());
response.setResponse(result, serviceName, restPath);
return response;
}
private HttpResponse createUser(HttpRequest request) {
- Optional<String> username = userFrom(request);
- if ( ! username.isPresent() ) throw new ForbiddenException("Not authenticated.");
+ Optional<UserId> user = userFrom(request);
+ if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated.");
try {
- controller.tenants().createUserTenant(username.get());
- return new MessageResponse("Created user '" + username.get() + "'");
+ controller.tenants().createUserTenant(user.get().id());
+ return new MessageResponse("Created user '" + user.get() + "'");
} catch (AlreadyExistsException e) {
// Ok
- return new MessageResponse("User '" + username + "' already exists");
+ return new MessageResponse("User '" + user + "' already exists");
}
}
@@ -711,7 +711,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
"Active versions: " + controller.versionStatus().versions());
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
- controller.applications().lockedOrThrow(id, application -> {
+ controller.applications().lockOrThrow(id, application -> {
if (application.deploying().isPresent())
throw new IllegalArgumentException("Can not start a deployment of " + application + " at this time: " +
application.deploying().get() + " is in progress");
@@ -729,7 +729,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if ( ! change.isPresent())
return new MessageResponse("No deployment in progress for " + application + " at this time");
- controller.applications().lockedOrThrow(id, lockedApplication ->
+ controller.applications().lockOrThrow(id, lockedApplication ->
controller.applications().deploymentTrigger().cancelChange(id));
return new MessageResponse("Cancelled " + change.get() + " for " + application);
@@ -738,12 +738,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
/** Schedule restart of deployment, or specific host in a deployment */
private HttpResponse restart(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
- new Zone(Environment.from(environment), RegionName.from(region)));
+ ZoneId.from(environment, region));
// TODO: Propagate all filters
- if (request.getProperty("hostname") != null)
- controller.applications().restartHost(deploymentId, new Hostname(request.getProperty("hostname")));
- else
- controller.applications().restart(deploymentId);
+ Optional<Hostname> hostname = Optional.ofNullable(request.getProperty("hostname")).map(Hostname::new);
+ controller.applications().restart(deploymentId, hostname);
// TODO: Change to return JSON
return new StringResponse("Requested restart of " + path(TenantResource.API_PATH, tenantName,
@@ -761,7 +759,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse log(String tenantName, String applicationName, String instanceName, String environment, String region) {
try {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
- new Zone(Environment.from(environment), RegionName.from(region)));
+ ZoneId.from(environment, region));
return new JacksonJsonResponse(controller.grabLog(deploymentId));
}
catch (RuntimeException e) {
@@ -773,7 +771,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse deploy(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
- Zone zone = new Zone(Environment.from(environment), RegionName.from(region));
+ ZoneId zone = ZoneId.from(environment, region);
Map<String, byte[]> dataParts = new MultipartParser().parse(request);
if ( ! dataParts.containsKey("deployOptions"))
@@ -783,17 +781,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Inspector deployOptions = SlimeUtils.jsonToSlime(dataParts.get("deployOptions")).get();
+ ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get("applicationZip"));
DeployAuthorizer deployAuthorizer = new DeployAuthorizer(controller.zoneRegistry(), athenzClientFactory);
Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new NotExistsException(new TenantId(tenantName)));
Principal principal = authorizer.getPrincipal(request);
- deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, applicationId);
+ deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, applicationId, applicationPackage);
// TODO: get rid of the json object
DeployOptions deployOptionsJsonClass = new DeployOptions(screwdriverBuildJobFromSlime(deployOptions.field("screwdriverBuildJob")),
optional("vespaVersion", deployOptions).map(Version::new),
deployOptions.field("ignoreValidationErrors").asBool(),
deployOptions.field("deployCurrentVersion").asBool());
- ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get("applicationZip"));
controller.applications().validate(applicationPackage.deploymentSpec());
ActivateResult result = controller.applications().deployApplication(applicationId,
zone,
@@ -824,7 +822,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse deactivate(String tenantName, String applicationName, String instanceName, String environment, String region) {
Application application = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName));
- Zone zone = new Zone(Environment.from(environment), RegionName.from(region));
+ ZoneId zone = ZoneId.from(environment, region);
Deployment deployment = application.deployments().get(zone);
if (deployment == null) {
// Attempt to deactivate application even if the deployment is not known by the controller
@@ -873,8 +871,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
- private Optional<String> userFrom(HttpRequest request) {
- return authorizer.getPrincipalIfAny(request).map(Principal::getName);
+ private Optional<UserId> userFrom(HttpRequest request) {
+ return authorizer.getPrincipalIfAny(request)
+ .map(AthenzPrincipal::getIdentity)
+ .filter(AthenzUser.class::isInstance)
+ .map(AthenzUser.class::cast)
+ .map(AthenzUser::getUserId);
}
private void toSlime(Cursor object, Tenant tenant, HttpRequest request, boolean listApplications) {
@@ -985,18 +987,22 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private void throwIfNotSuperUserOrPartOfOpsDbGroup(UserGroup userGroup, HttpRequest request) {
- UserId userId = authorizer.getUserId(request);
- if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(userId, userGroup) ) {
+ AthenzIdentity identity = authorizer.getIdentity(request);
+ if (!(identity instanceof AthenzUser)) {
+ throw new ForbiddenException("Identity not an user: " + identity.getFullName());
+ }
+ AthenzUser user = (AthenzUser) identity;
+ if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(user.getUserId(), userGroup) ) {
throw new ForbiddenException(String.format("User '%s' is not super user or part of the OpsDB user group '%s'",
- userId.id(), userGroup.id()));
+ user.getUserId().id(), userGroup.id()));
}
}
private void throwIfNotAthenzDomainAdmin(AthenzDomain tenantDomain, HttpRequest request) {
- UserId userId = authorizer.getUserId(request);
- if ( ! authorizer.isAthenzDomainAdmin(userId, tenantDomain)) {
+ AthenzIdentity identity = authorizer.getIdentity(request);
+ if ( ! authorizer.isAthenzDomainAdmin(identity, tenantDomain)) {
throw new ForbiddenException(
- String.format("The user '%s' is not admin in Athenz domain '%s'", userId.id(), tenantDomain.id()));
+ String.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), tenantDomain.id()));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java
index 0c808e30c2a..b7080a763f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java
@@ -10,16 +10,17 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
import com.yahoo.vespa.hosted.controller.common.ContextAttributes;
import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.SecurityContext;
-import java.security.Principal;
import java.util.Optional;
import java.util.logging.Logger;
@@ -54,27 +55,26 @@ public class Authorizer {
Optional<Tenant> tenant = controller.tenants().tenant(tenantId);
if ( ! tenant.isPresent()) return;
- UserId userId = getUserId(request);
- if (isTenantAdmin(userId, tenant.get())) return;
+ AthenzIdentity identity = getIdentity(request);
+ if (isTenantAdmin(identity, tenant.get())) return;
- throw loggedForbiddenException("User " + userId + " does not have write access to tenant " + tenantId);
+ throw loggedForbiddenException("User " + identity.getFullName() + " does not have write access to tenant " + tenantId);
}
- public UserId getUserId(HttpRequest request) {
- String name = getPrincipal(request).getName();
- if (name == null)
- throw loggedForbiddenException("Not authorized: User name is null");
- return new UserId(name);
+ public AthenzIdentity getIdentity(HttpRequest request) {
+ return getPrincipal(request).getIdentity();
}
/** Returns the principal or throws forbidden */ // TODO: Avoid REST exceptions
- public Principal getPrincipal(HttpRequest request) {
+ public AthenzPrincipal getPrincipal(HttpRequest request) {
return getPrincipalIfAny(request).orElseThrow(() -> Authorizer.loggedForbiddenException("User is not authenticated"));
}
/** Returns the principal if there is any */
- public Optional<Principal> getPrincipalIfAny(HttpRequest request) {
- return securityContextOf(request).map(SecurityContext::getUserPrincipal);
+ public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) {
+ return securityContextOf(request)
+ .map(SecurityContext::getUserPrincipal)
+ .map(AthenzPrincipal.class::cast);
}
public Optional<NToken> getNToken(HttpRequest request) {
@@ -93,26 +93,36 @@ public class Authorizer {
return new ForbiddenException(formattedMessage);
}
- private boolean isTenantAdmin(UserId userId, Tenant tenant) {
+ private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) {
switch (tenant.tenantType()) {
case ATHENS:
- return isAthenzTenantAdmin(userId, tenant.getAthensDomain().get());
- case OPSDB:
- return isGroupMember(userId, tenant.getUserGroup().get());
- case USER:
- return isUserTenantOwner(tenant.getId(), userId);
+ return isAthenzTenantAdmin(identity, tenant.getAthensDomain().get());
+ case OPSDB: {
+ if (!(identity instanceof AthenzUser)) {
+ return false;
+ }
+ AthenzUser user = (AthenzUser) identity;
+ return isGroupMember(user.getUserId(), tenant.getUserGroup().get());
+ }
+ case USER: {
+ if (!(identity instanceof AthenzUser)) {
+ return false;
+ }
+ AthenzUser user = (AthenzUser) identity;
+ return isUserTenantOwner(tenant.getId(), user.getUserId());
+ }
}
throw new IllegalArgumentException("Unknown tenant type: " + tenant.tenantType());
}
- private boolean isAthenzTenantAdmin(UserId userId, AthenzDomain tenantDomain) {
+ private boolean isAthenzTenantAdmin(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain) {
return athenzClientFactory.createZmsClientWithServicePrincipal()
- .hasTenantAdminAccess(AthenzUtils.createPrincipal(userId), tenantDomain);
+ .hasTenantAdminAccess(athenzIdentity, tenantDomain);
}
- public boolean isAthenzDomainAdmin(UserId userId, AthenzDomain tenantDomain) {
+ public boolean isAthenzDomainAdmin(AthenzIdentity identity, AthenzDomain tenantDomain) {
return athenzClientFactory.createZmsClientWithServicePrincipal()
- .isDomainAdmin(AthenzUtils.createPrincipal(userId), tenantDomain);
+ .isDomainAdmin(identity, tenantDomain);
}
public boolean isGroupMember(UserId userId, UserGroup userGroup) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java
index 71126259417..c7e03048ec8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java
@@ -6,15 +6,17 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.vespa.hosted.controller.api.Tenant;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
-import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsException;
+import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotAuthorizedException;
import java.security.Principal;
+import java.util.Objects;
import java.util.logging.Logger;
import static com.yahoo.vespa.hosted.controller.restapi.application.Authorizer.environmentRequiresAuthorization;
@@ -38,7 +40,21 @@ public class DeployAuthorizer {
public void throwIfUnauthorizedForDeploy(Principal principal,
Environment environment,
Tenant tenant,
- ApplicationId applicationId) {
+ ApplicationId applicationId,
+ ApplicationPackage applicationPackage) {
+ // Validate that domain in identity configuration (deployment.xml) is same as tenant domain
+ applicationPackage.deploymentSpec().athenzDomain().ifPresent(identityDomain -> {
+ AthenzDomain tenantDomain = tenant.getAthensDomain().orElseThrow(() -> new IllegalArgumentException("Identity provider only available to Athenz onboarded tenants"));
+ if (! Objects.equals(tenantDomain.id(), identityDomain.value())) {
+ throw new ForbiddenException(
+ String.format(
+ "Athenz domain in deployment.xml: [%s] must match tenant domain: [%s]",
+ identityDomain.value(),
+ tenantDomain.id()
+ ));
+ }
+ });
+
if (!environmentRequiresAuthorization(environment)) {
return;
}
@@ -70,7 +86,7 @@ public class DeployAuthorizer {
"Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " +
"Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " +
"'%1$s' is not added to the application's deployer role in Athenz domain '%3$s'.",
- athenzPrincipal.toYRN(), applicationId, tenantDomain.id());
+ athenzPrincipal.getIdentity().getFullName(), applicationId, tenantDomain.id());
}
}
}
@@ -91,7 +107,7 @@ public class DeployAuthorizer {
try {
return athenzClientFactory.createZmsClientWithServicePrincipal()
.hasApplicationAccess(
- principal,
+ principal.getIdentity(),
ApplicationAction.deploy,
domain,
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(applicationId.application().value()));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
index 6a448e475c5..0b0a2c3ad52 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.JsonFormat;
@@ -30,7 +30,7 @@ import java.util.regex.Pattern;
*/
class ServiceApiResponse extends HttpResponse {
- private final Zone zone;
+ private final ZoneId zone;
private final ApplicationId application;
private final List<URI> configServerURIs;
private final Slime slime;
@@ -40,7 +40,7 @@ class ServiceApiResponse extends HttpResponse {
private String serviceName = null;
private String restPath = null;
- public ServiceApiResponse(Zone zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) {
+ public ServiceApiResponse(ZoneId zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) {
super(200);
this.zone = zone;
this.application = application;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
index e350b98adb9..4d4f01bc1a6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
@@ -12,9 +12,7 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.LockedApplication;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
@@ -109,7 +107,7 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler {
.orElse(JobType.component);
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default");
- controller.applications().lockedOrThrow(applicationId, application -> {
+ controller.applications().lockOrThrow(applicationId, application -> {
// Since this is a manual operation we likely want it to trigger as soon as possible so we add it at to the
// front of the queue
application = controller.applications().deploymentTrigger().triggerAllowParallel(
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index 3a3fd445bcf..83b725ae4c4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
@@ -70,7 +70,7 @@ public class ZoneApiHandler extends LoggingRequestHandler {
private HttpResponse root(HttpRequest request) {
List<Environment> environments = zoneRegistry.zones().stream()
- .map(Zone::environment)
+ .map(ZoneId::environment)
.distinct()
.sorted(Comparator.comparing(Environment::value))
.collect(Collectors.toList());
@@ -89,7 +89,7 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse environment(HttpRequest request, Environment environment) {
- List<Zone> zones = zoneRegistry.zones().stream()
+ List<ZoneId> zones = zoneRegistry.zones().stream()
.filter(zone -> zone.environment() == environment)
.collect(Collectors.toList());
Slime slime = new Slime();
@@ -109,9 +109,7 @@ public class ZoneApiHandler extends LoggingRequestHandler {
private HttpResponse defaultRegion(HttpRequest request, Environment environment) {
RegionName region = zoneRegistry.getDefaultRegion(environment)
- .orElseThrow(() -> new IllegalArgumentException(
- "No default region for environment: " + environment
- ));
+ .orElseThrow(() -> new IllegalArgumentException("No default region for environment: " + environment));
Slime slime = new Slime();
Cursor root = slime.setObject();
root.setString("name", region.value());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index 529b2b25785..772dd1f6cb1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v2;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
@@ -76,14 +76,12 @@ public class ZoneApiHandler extends LoggingRequestHandler {
private HttpResponse proxy(HttpRequest request) {
Path path = new Path(request.getUri().getPath());
- if (!path.matches("/zone/v2/{environment}/{region}/{*}")) {
+ if ( ! path.matches("/zone/v2/{environment}/{region}/{*}")) {
return notFound(path);
}
- Environment environment = Environment.from(path.get("environment"));
- RegionName region = RegionName.from(path.get("region"));
- Optional<Zone> zone = zoneRegistry.getZone(environment, region);
- if (!zone.isPresent()) {
- throw new IllegalArgumentException("No such zone: " + environment.value() + "." + region.value());
+ ZoneId zoneId = ZoneId.from(path.get("environment"), path.get("region"));
+ if ( ! zoneRegistry.hasZone(zoneId)) {
+ throw new IllegalArgumentException("No such zone: " + zoneId.value());
}
try {
return proxy.handle(new ProxyRequest(request, "/zone/v2/"));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java
new file mode 100644
index 00000000000..a638756a600
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java
@@ -0,0 +1,49 @@
+// 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.rotation;
+
+import java.util.Objects;
+
+/**
+ * Represents a global routing rotation.
+ *
+ * @author mpolden
+ */
+public class Rotation {
+
+ private final RotationId id;
+ private final String name;
+
+ public Rotation(RotationId id, String name) {
+ this.id = Objects.requireNonNull(id);
+ this.name = Objects.requireNonNull(name);
+ }
+
+ /** The ID of the allocated rotation. This value is generated by global routing system */
+ public RotationId id() {
+ return id;
+ }
+
+ /** The global rotation FQDN */
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Rotation)) return false;
+ final Rotation rotation = (Rotation) o;
+ return id().equals(rotation.id()) && name().equals(rotation.name());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id(), name());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("rotation %s -> %s", id().asString(), name());
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java
new file mode 100644
index 00000000000..10b15488f6e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java
@@ -0,0 +1,42 @@
+// 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.rotation;
+
+import java.util.Objects;
+
+/**
+ * ID of a global rotation.
+ *
+ * @author mpolden
+ */
+public class RotationId {
+
+ private final String id;
+
+ public RotationId(String id) {
+ this.id = id;
+ }
+
+ /** Rotation ID, e.g. rotation-42.vespa.global.routing */
+ public String asString() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RotationId that = (RotationId) o;
+ return Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString() {
+ return "rotation ID " + id;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java
new file mode 100644
index 00000000000..508df263837
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java
@@ -0,0 +1,25 @@
+// 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.rotation;
+
+import com.yahoo.vespa.curator.Lock;
+
+import java.util.Objects;
+
+/**
+ * A lock for the rotation repository. This is a type-safe wrapper for a curator lock.
+ *
+ * @author mpolden
+ */
+public class RotationLock implements AutoCloseable {
+
+ private final Lock lock;
+
+ RotationLock(Lock lock) {
+ this.lock = Objects.requireNonNull(lock, "lock cannot be null");
+ }
+
+ @Override
+ public void close() {
+ lock.close();
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
new file mode 100644
index 00000000000..c0d3fd4758e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
@@ -0,0 +1,117 @@
+// 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.rotation;
+
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.ApplicationController;
+import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.collectingAndThen;
+
+/**
+ * The rotation repository offers global rotations to Vespa applications.
+ *
+ * The list of rotations comes from RotationsConfig, which is set in the controller's services.xml.
+ *
+ * @author Oyvind Gronnesby
+ * @author mpolden
+ */
+public class RotationRepository {
+
+ private static final Logger log = Logger.getLogger(RotationRepository.class.getName());
+
+ private final Map<RotationId, Rotation> allRotations;
+ private final ApplicationController applications;
+ private final CuratorDb curator;
+
+ public RotationRepository(RotationsConfig rotationsConfig, ApplicationController applications, CuratorDb curator) {
+ this.allRotations = from(rotationsConfig);
+ this.applications = applications;
+ this.curator = curator;
+ }
+
+ /** Acquire a exclusive lock for this */
+ public RotationLock lock() {
+ return new RotationLock(curator.lockRotations());
+ }
+
+ /**
+ * Returns a rotation for the given application
+ *
+ * If a rotation is already assigned to the application, that rotation will be returned.
+ * If no rotation is assigned, return an available rotation. The caller is responsible for assigning the rotation.
+ *
+ * @param application The application requesting a rotation
+ * @param lock Lock which must be acquired by the caller
+ */
+ public Rotation getRotation(Application application, RotationLock lock) {
+ if (application.rotation().isPresent()) {
+ return allRotations.get(application.rotation().get().id());
+ }
+ if (!application.deploymentSpec().globalServiceId().isPresent()) {
+ throw new IllegalArgumentException("global-service-id is not set in deployment spec");
+ }
+ long productionZones = application.deploymentSpec().zones().stream()
+ .filter(zone -> zone.deploysTo(Environment.prod))
+ // Global rotations don't work for nodes in corp network
+ .filter(zone -> !isCorp(zone))
+ .count();
+ if (productionZones < 2) {
+ throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined");
+ }
+ return findAvailableRotation(application, lock);
+ }
+
+ /**
+ * Returns all unassigned rotations
+ * @param lock Lock which must be acquired by the caller
+ */
+ public Map<RotationId, Rotation> availableRotations(@SuppressWarnings("unused") RotationLock lock) {
+ List<RotationId> assignedRotations = applications.asList().stream()
+ .filter(application -> application.rotation().isPresent())
+ .map(application -> application.rotation().get().id())
+ .collect(Collectors.toList());
+ Map<RotationId, Rotation> unassignedRotations = new LinkedHashMap<>(this.allRotations);
+ assignedRotations.forEach(unassignedRotations::remove);
+ return Collections.unmodifiableMap(unassignedRotations);
+ }
+
+ private Rotation findAvailableRotation(Application application, RotationLock lock) {
+ Map<RotationId, Rotation> availableRotations = availableRotations(lock);
+ if (availableRotations.isEmpty()) {
+ throw new IllegalStateException("Unable to assign global rotation to " + application.id()
+ + " - no rotations available");
+ }
+ // Return first available rotation
+ RotationId rotation = availableRotations.keySet().iterator().next();
+ log.info(String.format("Offering %s to application %s", rotation, application.id()));
+ return allRotations.get(rotation);
+ }
+
+ private static boolean isCorp(DeploymentSpec.DeclaredZone zone) {
+ return zone.region().isPresent() && zone.region().get().value().contains("corp");
+ }
+
+ /** Returns a immutable map of rotation ID to rotation sorted by rotation ID */
+ private static Map<RotationId, Rotation> from(RotationsConfig rotationConfig) {
+ return rotationConfig.rotations().entrySet().stream()
+ .map(entry -> new Rotation(new RotationId(entry.getKey()), entry.getValue().trim()))
+ .sorted(Comparator.comparing(rotation -> rotation.id().asString()))
+ .collect(collectingAndThen(Collectors.toMap(Rotation::id,
+ rotation -> rotation,
+ (k, v) -> v,
+ LinkedHashMap::new),
+ Collections::unmodifiableMap));
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index d152cf80472..876bd5fe029 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import com.yahoo.collections.ListMap;
import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.github.GitSha;
@@ -119,15 +120,17 @@ public class VersionStatus {
private static ListMap<Version, String> findConfigServerVersions(Controller controller) {
List<URI> configServers = controller.zoneRegistry().zones().stream()
- .flatMap(zone -> controller.getConfigServerUris(zone.environment(), zone.region()).stream())
- .collect(Collectors.toList());
+ // TODO: Filter properly.
+ .filter(zone -> ! zone.region().equals(RegionName.from("us-east-2a")))
+ .flatMap(zone -> controller.getConfigServerUris(zone).stream())
+ .collect(Collectors.toList());
ListMap<Version, String> versions = new ListMap<>();
for (URI configServer : configServers)
versions.put(controller.applications().configserverClient().version(configServer), configServer.getHost());
return versions;
}
-
+
private static Collection<DeploymentStatistics> computeDeploymentStatistics(Set<Version> infrastructureVersions,
List<Application> applications,
Instant jobTimeoutLimit) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java
deleted file mode 100644
index 9eef1dac70b..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java
+++ /dev/null
@@ -1,148 +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.hosted.rotation;
-
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.log.LogLevel;
-import com.yahoo.metrics.simple.Gauge;
-import com.yahoo.metrics.simple.MetricReceiver;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
-import com.yahoo.vespa.hosted.controller.api.ApplicationAlias;
-import com.yahoo.vespa.hosted.controller.persistence.ControllerDb;
-import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
-import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
-import org.jetbrains.annotations.NotNull;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * A rotation repository.
- *
- * @author Oyvind Gronnesby
- */
-// TODO: Fold this into ApplicationController+Application
-public class ControllerRotationRepository implements RotationRepository {
-
- private static final Logger log = Logger.getLogger(ControllerRotationRepository.class.getName());
-
- private static final String REMAINING_ROTATIONS_METRIC_NAME = "remaining_rotations";
- private final Gauge remainingRotations;
-
- private final ControllerDb controllerDb;
- private final Map<RotationId, Rotation> rotationsMap;
-
- public ControllerRotationRepository(RotationsConfig rotationConfig, ControllerDb controllerDb, MetricReceiver metricReceiver) {
- this.controllerDb = controllerDb;
- this.rotationsMap = buildRotationsMap(rotationConfig);
- this.remainingRotations = metricReceiver.declareGauge(REMAINING_ROTATIONS_METRIC_NAME);
- }
-
- private static Map<RotationId, Rotation> buildRotationsMap(RotationsConfig rotationConfig) {
- return rotationConfig.rotations().entrySet().stream()
- .map(entry -> {
- RotationId rotationId = new RotationId(entry.getKey());
- return new Rotation(rotationId, entry.getValue().trim());
- })
- .collect(Collectors.toMap(
- rotation -> rotation.rotationId,
- rotation -> rotation
- ));
- }
-
- @Override
- @NotNull
- public Set<Rotation> getOrAssignRotation(ApplicationId applicationId, DeploymentSpec deploymentSpec) {
- reportRemainingRotations();
-
- Set<RotationId> rotations = controllerDb.getRotations(applicationId);
-
- if (rotations.size() > 1) {
- log.warning(String.format("Application %s has %d > 1 rotation", applicationId, rotations.size()));
- }
-
- if (!rotations.isEmpty()) {
- return rotations.stream()
- .map(rotationsMap::get)
- .collect(Collectors.toSet());
- }
-
- if( ! deploymentSpec.globalServiceId().isPresent()) {
- return Collections.emptySet();
- }
-
- long productionZoneCount = deploymentSpec.zones().stream()
- .filter(zone -> zone.deploysTo(Environment.prod))
- .filter(zone -> ! isCorp(zone)) // Global rotations don't work for nodes in corp network
- .count();
-
- if (productionZoneCount >= 2) {
- return assignRotation(applicationId);
- }
- else {
- throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined");
- }
- }
-
- private boolean isCorp(DeploymentSpec.DeclaredZone zone) {
- return zone.region().isPresent() && zone.region().get().value().contains("corp");
- }
-
- @Override
- @NotNull
- public Set<URI> getRotationUris(ApplicationId applicationId) {
- Set<RotationId> rotations = controllerDb.getRotations(applicationId);
- if (rotations.isEmpty()) {
- return Collections.emptySet();
- }
- else {
- ApplicationAlias applicationAlias = new ApplicationAlias(applicationId);
- return Collections.singleton(applicationAlias.toHttpUri());
- }
- }
-
- private Set<Rotation> assignRotation(ApplicationId applicationId) {
- Set<RotationId> availableRotations = availableRotations();
- if (availableRotations.isEmpty()) {
- String message = "Unable to assign global rotation to "
- + applicationId + " - no rotations available";
- log.info(message);
- throw new RuntimeException(message);
- }
-
- for (RotationId rotationId : availableRotations) {
- if (controllerDb.assignRotation(rotationId, applicationId)) {
- log.info(String.format("Assigned rotation %s to application %s", rotationId, applicationId));
- Rotation rotation = this.rotationsMap.get(rotationId);
- return Collections.singleton(rotation);
- }
- }
-
- log.info(String.format("Rotation: No rotations assigned with %s rotations available", availableRotations.size()));
- return Collections.emptySet();
- }
-
- private Set<RotationId> availableRotations() {
- Set<RotationId> assignedRotations = controllerDb.getRotations();
- Set<RotationId> allRotations = new HashSet<>(rotationsMap.keySet());
- allRotations.removeAll(assignedRotations);
- return allRotations;
- }
-
- private void reportRemainingRotations() {
- try {
- int freeRotationsCount = availableRotations().size();
- log.log(LogLevel.INFO, "Rotation: {0} global rotations remaining", freeRotationsCount);
- remainingRotations.sample(freeRotationsCount);
- } catch (Exception e) {
- log.log(LogLevel.INFO, "Failed to report rotations metric", e);
- }
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java
deleted file mode 100644
index 4e333f0268b..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java
+++ /dev/null
@@ -1,54 +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.hosted.rotation;
-
-import com.google.common.collect.ImmutableSet;
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
-import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
-import org.jetbrains.annotations.NotNull;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-/**
- * A rotation repository backed by in-memory data structures
- *
- * @author bratseth
- */
-public class MemoryRotationRepository implements RotationRepository {
-
- private final Map<ApplicationId, Set<Rotation>> rotations = new HashMap<>();
-
- @NotNull
- @Override
- public Set<Rotation> getOrAssignRotation(ApplicationId application, DeploymentSpec deploymentSpec) {
- if (rotations.containsKey(application)) {
- return rotations.get(application);
- }
- Set<Rotation> rotations = ImmutableSet.of(new Rotation(
- new RotationId("generated-by-routing-service-" + UUID.randomUUID().toString()),
- "fake-global-rotation-" + application.toShortString())
- );
- this.rotations.put(application, rotations);
- return rotations;
- }
-
- @NotNull
- @Override
- public Set<URI> getRotationUris(ApplicationId applicationId) {
- Set<Rotation> rotations = this.rotations.get(applicationId);
- if (rotations == null) {
- return Collections.emptySet();
- }
- return rotations.stream()
- .map(rotation -> URI.create("http://" + rotation.rotationName))
- .collect(Collectors.toSet());
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java
deleted file mode 100644
index b1f7b33e58e..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java
+++ /dev/null
@@ -1,48 +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.hosted.rotation;
-
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
-import org.jetbrains.annotations.NotNull;
-
-import java.net.URI;
-import java.util.Set;
-
-/**
- * A rotation repository assigns global rotations to Vespa applications. It does not take into account
- * whether an application qualifies or not, but it assumes that each application should get only
- * one.
- *
- * The list of rotations comes from the RotationsConfig, set in the controller's services.xml.
- * Assignments are persisted with the RotationId as the primary key. When we assign the
- * rotation to an application we try to put the mapping RotationId -&gt; Application. If a
- * mapping already exists for that RotationId, the assignment will fail.
- *
- * @author Oyvind Gronnesby
- */
-public interface RotationRepository {
-
- // TODO: Change to use provision.ApplicationId
- // TODO: Move the persistence into ControllerDb (done), and then collapse the 2 implementations and the interface into one
-
- /**
- * If any rotations are assigned to the application, these will be returned.
- * If no rotations are assigned, assign one rotation to the application and return that.
- *
- * @param applicationId ID of the application to get or assign rotation for
- * @param deploymentSpec Spec of current application being deployed
- * @return Set of rotations assigned (may be empty)
- */
- @NotNull
- Set<Rotation> getOrAssignRotation(ApplicationId applicationId, DeploymentSpec deploymentSpec);
-
- /**
- * Get the external visible rotation URIs for this application.
- *
- * @param applicationId ID of the application to get or assign rotation for
- */
- @NotNull
- Set<URI> getRotationUris(ApplicationId applicationId);
-
-}
diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def
index 6d10f3dee28..068b1d353ba 100644
--- a/controller-server/src/main/resources/configdefinitions/athenz.def
+++ b/controller-server/src/main/resources/configdefinitions/athenz.def
@@ -17,6 +17,12 @@ domain string
userAuthenticationPassThruAttribute string
# TODO Remove once migrated to Okta
+# Path to Athenz CA JKS trust store
+athenzCaTrustStore string
+
+# Certificate DNS domain
+certDnsDomain string
+
# Athenz service name for controller identity
service.name string
@@ -28,3 +34,6 @@ service.privateKeyVersion int
# Name of Athenz service private key secret
service.privateKeySecretName string
+
+# Expiry of service principal token and certificate
+service.credentialsExpiryMinutes int default=43200 # 30 days
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java
index 9228e83bbc6..bf7f19a996c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java
@@ -22,7 +22,6 @@ import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import com.yahoo.vespa.serviceview.bindings.ClusterView;
import com.yahoo.vespa.serviceview.bindings.ServiceView;
-import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -43,16 +42,14 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
private final Map<ApplicationId, Boolean> applicationActivated = new HashMap<>();
private final Map<String, EndpointStatus> endpoints = new HashMap<>();
private final Map<URI, Version> versions = new HashMap<>();
- private Version defaultVersion = new Version(6, 1, 0);
- /** The exception to throw on the next prepare run, or null to continue normally */
+ private Version defaultVersion = new Version(6, 1, 0);
private RuntimeException prepareException = null;
-
- private Optional<Version> lastPrepareVersion = Optional.empty();
+ private Version lastPrepareVersion = null;
/** The version given in the previous prepare call, or empty if no call has been made */
public Optional<Version> lastPrepareVersion() {
- return lastPrepareVersion;
+ return Optional.ofNullable(lastPrepareVersion);
}
/** Return map of applications that may have been activated */
@@ -60,6 +57,7 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
return Collections.unmodifiableMap(applicationActivated);
}
+ /** The exception to throw on the next prepare run, or null to continue normally */
public void throwOnNextPrepare(RuntimeException prepareException) {
this.prepareException = prepareException;
}
@@ -71,10 +69,16 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
public Map<URI, Version> versions() {
return versions;
}
+
+ /** Set the default config server version */
+ public void setDefaultVersion(Version version) {
+ this.defaultVersion = version;
+ }
@Override
- public PreparedApplication prepare(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationCnames, Set<Rotation> rotations, byte[] content) {
- lastPrepareVersion = deployOptions.vespaVersion.map(Version::new);
+ public PreparedApplication prepare(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationCnames,
+ Set<Rotation> rotations, byte[] content) {
+ lastPrepareVersion = deployOptions.vespaVersion.map(Version::new).orElse(null);
if (prepareException != null) {
RuntimeException prepareException = this.prepareException;
this.prepareException = null;
@@ -108,23 +112,20 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
public PrepareResponse prepareResponse() {
PrepareResponse prepareResponse = new PrepareResponse();
prepareResponse.message = "foo";
- prepareResponse.configChangeActions = new ConfigChangeActions(Collections.emptyList(), Collections.emptyList());
+ prepareResponse.configChangeActions = new ConfigChangeActions(Collections.emptyList(),
+ Collections.emptyList());
prepareResponse.tenant = new TenantId("tenant");
return prepareResponse;
}
};
}
-
- /** Set the default config server version */
- public void setDefaultVersion(Version version) { this.defaultVersion = version; }
@Override
public List<String> getNodeQueryHost(DeploymentId deployment, String type) {
if (applicationInstances.containsKey(deployment.applicationId())) {
return Collections.singletonList(applicationInstances.get(deployment.applicationId()));
- } else {
- return Collections.emptyList();
}
+ return Collections.emptyList();
}
@Override
@@ -151,7 +152,8 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
// Returns a canned example response
@Override
- public ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, String environment, String region) {
+ public ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName,
+ String environment, String region) {
ApplicationView applicationView = new ApplicationView();
ClusterView cluster = new ClusterView();
cluster.name = "cluster1";
@@ -172,7 +174,8 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
// Returns a canned example response
@Override
- public Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath) {
+ public Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName,
+ String environment, String region, String serviceName, String restPath) {
Map<String,List<?>> root = new HashMap<>();
List<Map<?,?>> resources = new ArrayList<>();
Map<String,String> resource = new HashMap<>();
@@ -199,7 +202,7 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS
}
@Override
- public NodeList getNodeList(DeploymentId deployment) throws IOException {
+ public NodeList getNodeList(DeploymentId deployment) {
NodeList list = new NodeList();
list.nodes = new ArrayList<>();
NodeList.Node hostA = new NodeList.Node();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java
index cc915d4d9a1..02b33e4640a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor;
-import com.yahoo.vespa.hosted.controller.proxy.ProxyException;
import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest;
import com.yahoo.vespa.hosted.controller.restapi.StringResponse;
@@ -21,7 +20,7 @@ public class ConfigServerProxyMock extends AbstractComponent implements ConfigSe
private volatile String requestBody = null;
@Override
- public HttpResponse handle(ProxyRequest proxyRequest) throws ProxyException {
+ public HttpResponse handle(ProxyRequest proxyRequest) {
lastReceived = proxyRequest;
// Copy request body as the input stream is drained once the request completes
requestBody = asString(proxyRequest.getData());
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 d0c1fd95427..58c74c9d6d2 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
@@ -10,10 +10,8 @@ import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.slime.Slime;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.config.SlimeUtils;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.Tenant;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
@@ -24,7 +22,9 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
+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.ApplicationRevision;
import com.yahoo.vespa.hosted.controller.application.Change;
@@ -32,12 +32,13 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.BuildSystem;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.persistence.ApplicationSerializer;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
@@ -297,10 +298,14 @@ public class ControllerTest {
// staging deployment
long app1ProjectId = 22;
- ApplicationId app1 = tester.createAndDeploy("tenant1", "domain1", "application1", Environment.staging, app1ProjectId).id();
+ ApplicationId app1 = tester.createAndDeploy("tenant1", "domain1",
+ "application1", Environment.staging,
+ app1ProjectId).id();
// pull-request deployment - uses different instance id
- ApplicationId app1pr = tester.createAndDeploy("tenant1", "domain1", "application1", "default-pr1", Environment.staging, app1ProjectId, null).id();
+ ApplicationId app1pr = tester.createAndDeploy("tenant1", "domain1",
+ "application1", "default-pr1",
+ Environment.staging, app1ProjectId, null).id();
assertTrue(applications.get(app1).isPresent());
assertEquals(app1, applications.get(app1).get().id());
@@ -315,6 +320,20 @@ public class ControllerTest {
assertEquals(app1, applications.get(app1).get().id());
assertTrue(applications.get(app1pr).isPresent());
assertEquals(app1pr, applications.get(app1pr).get().id());
+
+ // Deleting application also removes PR instance
+ ApplicationId app2 = tester.createAndDeploy("tenant1", "domain1",
+ "application2", Environment.staging,
+ 33).id();
+ tester.controller().applications().deleteApplication(app1, Optional.of(new NToken("ntoken")));
+ assertEquals("All instances deleted", 0,
+ tester.controller().applications().asList(app1.tenant()).stream()
+ .filter(app -> app.id().application().equals(app1.application()))
+ .count());
+ assertEquals("Other application survives", 1,
+ tester.controller().applications().asList(app1.tenant()).stream()
+ .filter(app -> app.id().application().equals(app2.application()))
+ .count());
}
@Test
@@ -494,7 +513,7 @@ public class ControllerTest {
public void testGlobalRotations() throws IOException {
// Setup tester and app def
ControllerTester tester = new ControllerTester();
- Zone zone = Zone.defaultZone();
+ ZoneId zone = ZoneId.from(Environment.defaultEnvironment(), RegionName.defaultName());
ApplicationId appId = tester.applicationId("tenant", "app1", "default");
DeploymentId deployId = new DeploymentId(appId, zone);
@@ -523,11 +542,11 @@ public class ControllerTest {
TenantId tenant = tester.createTenant("tenant1", "domain1", 11L);
Application app = tester.createApplication(tenant, "app1", "default", 1);
- tester.controller().applications().lockedOrThrow(app.id(), application -> {
+ tester.controller().applications().lockOrThrow(app.id(), application -> {
application = application.withDeploying(Optional.of(new Change.VersionChange(Version.fromString("6.3"))));
applications.store(application);
try {
- tester.deploy(app, new Zone(Environment.prod, RegionName.from("us-east-3")));
+ tester.deploy(app, ZoneId.from("prod", "us-east-3"));
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertEquals("Rejecting deployment of application 'tenant1.app1' to zone prod.us-east-3 as version change to 6.3 is not tested", e.getMessage());
@@ -601,16 +620,108 @@ public class ControllerTest {
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
+ .globalServiceId("foo")
.region("us-west-1")
.region("us-central-1") // Two deployments should result in DNS alias being registered once
.build();
tester.deployCompletely(application, applicationPackage);
assertEquals(1, tester.controllerTester().nameService().records().size());
- Optional<Record> record = tester.controllerTester().nameService().findRecord(Record.Type.CNAME, "app1.tenant1.global.vespa.yahooapis.com");
+ Optional<Record> record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")
+ );
assertTrue(record.isPresent());
- assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name());
- assertEquals("fake-global-rotation-tenant1.app1", record.get().value());
+ assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ }
+
+ @Test
+ public void testUpdatesExistingDnsAlias() {
+ DeploymentTester tester = new DeploymentTester();
+
+ // Application 1 is deployed and deleted
+ {
+ Application app1 = tester.createApplication("app1", "tenant1", 1, 1L);
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-west-1")
+ .region("us-central-1") // Two deployments should result in DNS alias being registered once
+ .build();
+
+ tester.deployCompletely(app1, applicationPackage);
+ assertEquals(1, tester.controllerTester().nameService().records().size());
+ Optional<Record> record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")
+ );
+ assertTrue(record.isPresent());
+ assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+
+ // Application is deleted and rotation is unassigned
+ applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .allow(ValidationId.deploymentRemoval)
+ .build();
+ tester.notifyJobCompletion(component, app1, true);
+ tester.deployAndNotify(app1, applicationPackage, true, systemTest);
+ tester.applications().deactivate(app1, ZoneId.from(Environment.test, RegionName.from("us-east-1")));
+ tester.applications().deactivate(app1, ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
+ tester.applications().deleteApplication(app1.id(), Optional.of(new NToken("ntoken")));
+ try (RotationLock lock = tester.applications().rotationRepository().lock()) {
+ assertTrue("Rotation is unassigned",
+ tester.applications().rotationRepository().availableRotations(lock)
+ .containsKey(new RotationId("rotation-id-01")));
+ }
+
+ // Record remains
+ record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")
+ );
+ assertTrue(record.isPresent());
+ }
+
+ // Application 2 is deployed and assigned same rotation as application 1 had before deletion
+ {
+ Application app2 = tester.createApplication("app2", "tenant2", 1, 1L);
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-west-1")
+ .region("us-central-1")
+ .build();
+ tester.deployCompletely(app2, applicationPackage);
+ assertEquals(2, tester.controllerTester().nameService().records().size());
+ Optional<Record> record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app2.tenant2.global.vespa.yahooapis.com")
+ );
+ assertTrue(record.isPresent());
+ assertEquals("app2.tenant2.global.vespa.yahooapis.com", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ }
+
+ // Application 1 is recreated, deployed and assigned a new rotation
+ {
+ Application app1 = tester.createApplication("app1", "tenant1", 1, 1L);
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-west-1")
+ .region("us-central-1")
+ .build();
+ tester.deployCompletely(app1, applicationPackage);
+ app1 = tester.applications().require(app1.id());
+ assertEquals("rotation-id-02", app1.rotation().get().id().asString());
+
+ // Existing DNS record is updated to point to the newly assigned rotation
+ assertEquals(2, tester.controllerTester().nameService().records().size());
+ Optional<Record> record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")
+ );
+ assertTrue(record.isPresent());
+ assertEquals("rotation-fqdn-02.", record.get().data().asString());
+ }
+
}
@Test
@@ -626,7 +737,7 @@ public class ControllerTest {
Application app = tester.createApplication("app1", "tenant1", 1, 2L);
// Direct deploy is allowed when project ID is missing
- Zone zone = new Zone(Environment.prod, RegionName.from("cd-us-central-1"));
+ ZoneId zone = ZoneId.from("prod", "cd-us-central-1");
// Same options as used in our integration tests
DeployOptions options = new DeployOptions(Optional.empty(), Optional.empty(), false,
false);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 8f9c22f8b81..52e1b3ae400 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -7,7 +7,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.slime.Slime;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.curator.Lock;
@@ -41,7 +41,7 @@ import com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
-import com.yahoo.vespa.hosted.rotation.MemoryRotationRepository;
+import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import java.util.Optional;
@@ -63,22 +63,31 @@ public final class ControllerTester {
private final GitHubMock gitHub;
private final CuratorDb curator;
private final MemoryNameService nameService;
+ private final RotationsConfig rotationsConfig;
private Controller controller;
public ControllerTester() {
this(new MemoryControllerDb(), new AthenzDbMock(), new ManualClock(), new ConfigServerClientMock(),
- new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), new MemoryNameService());
+ new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), defaultRotationsConfig(),
+ new MemoryNameService());
}
public ControllerTester(ManualClock clock) {
this(new MemoryControllerDb(), new AthenzDbMock(), clock, new ConfigServerClientMock(),
- new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), new MemoryNameService());
+ new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), defaultRotationsConfig(),
+ new MemoryNameService());
+ }
+
+ public ControllerTester(RotationsConfig rotationsConfig) {
+ this(new MemoryControllerDb(), new AthenzDbMock(), new ManualClock(), new ConfigServerClientMock(),
+ new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), rotationsConfig, new MemoryNameService());
}
private ControllerTester(ControllerDb db, AthenzDbMock athenzDb, ManualClock clock,
ConfigServerClientMock configServer, ZoneRegistryMock zoneRegistry,
- GitHubMock gitHub, CuratorDb curator, MemoryNameService nameService) {
+ GitHubMock gitHub, CuratorDb curator, RotationsConfig rotationsConfig,
+ MemoryNameService nameService) {
this.db = db;
this.athenzDb = athenzDb;
this.clock = clock;
@@ -87,7 +96,8 @@ public final class ControllerTester {
this.gitHub = gitHub;
this.curator = curator;
this.nameService = nameService;
- this.controller = createController(db, curator, configServer, clock, gitHub, zoneRegistry,
+ this.rotationsConfig = rotationsConfig;
+ this.controller = createController(db, curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry,
athenzDb, nameService);
}
@@ -109,7 +119,8 @@ public final class ControllerTester {
/** Create a new controller instance. Useful to verify that controller state is rebuilt from persistence */
public final void createNewController() {
- controller = createController(db, curator, configServer, clock, gitHub, zoneRegistry, athenzDb, nameService);
+ controller = createController(db, curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb,
+ nameService);
}
/** Creates the given tenant and application and deploys it */
@@ -119,7 +130,7 @@ public final class ControllerTester {
/** Creates the given tenant and application and deploys it */
public Application createAndDeploy(String tenantName, String domainName, String applicationName,
- String instanceName, Zone zone, long projectId, Long propertyId) {
+ String instanceName, ZoneId zone, long projectId, Long propertyId) {
TenantId tenant = createTenant(tenantName, domainName, propertyId);
Application application = createApplication(tenant, applicationName, instanceName, projectId);
deploy(application, zone);
@@ -133,7 +144,7 @@ public final class ControllerTester {
}
/** Creates the given tenant and application and deploys it */
- public Application createAndDeploy(String tenantName, String domainName, String applicationName, Zone zone, long projectId, Long propertyId) {
+ public Application createAndDeploy(String tenantName, String domainName, String applicationName, ZoneId zone, long projectId, Long propertyId) {
return createAndDeploy(tenantName, domainName, applicationName, "default", zone, projectId, propertyId);
}
@@ -152,11 +163,14 @@ public final class ControllerTester {
return application;
}
- public Zone toZone(Environment environment) {
+ public ZoneId toZone(Environment environment) {
switch (environment) {
- case dev: case test: return new Zone(environment, RegionName.from("us-east-1"));
- case staging: return new Zone(environment, RegionName.from("us-east-3"));
- default: return new Zone(environment, RegionName.from("us-west-1"));
+ case dev: case test:
+ return ZoneId.from(environment, RegionName.from("us-east-1"));
+ case staging:
+ return ZoneId.from(environment, RegionName.from("us-east-3"));
+ default:
+ return ZoneId.from(environment, RegionName.from("us-west-1"));
}
}
@@ -181,20 +195,20 @@ public final class ControllerTester {
public Application createApplication(TenantId tenant, String applicationName, String instanceName, long projectId) {
ApplicationId applicationId = applicationId(tenant.id(), applicationName, instanceName);
controller().applications().createApplication(applicationId, Optional.of(TestIdentities.userNToken));
- controller().applications().lockedOrThrow(applicationId, lockedApplication ->
+ controller().applications().lockOrThrow(applicationId, lockedApplication ->
controller().applications().store(lockedApplication.withProjectId(projectId)));
return controller().applications().require(applicationId);
}
- public void deploy(Application application, Zone zone) {
+ public void deploy(Application application, ZoneId zone) {
deploy(application, zone, new ApplicationPackage(new byte[0]));
}
- public void deploy(Application application, Zone zone, ApplicationPackage applicationPackage) {
+ public void deploy(Application application, ZoneId zone, ApplicationPackage applicationPackage) {
deploy(application, zone, applicationPackage, false);
}
- public void deploy(Application application, Zone zone, ApplicationPackage applicationPackage, boolean deployCurrentVersion) {
+ public void deploy(Application application, ZoneId zone, ApplicationPackage applicationPackage, boolean deployCurrentVersion) {
ScrewdriverId app1ScrewdriverId = new ScrewdriverId(String.valueOf(application.deploymentJobs().projectId().get()));
GitRevision app1RevisionId = new GitRevision(new GitRepository("repo"), new GitBranch("master"), new GitCommit("commit1"));
controller().applications().deployApplication(application.id(),
@@ -214,13 +228,13 @@ public final class ControllerTester {
return new LockedApplication(application, new Lock("/test", new MockCurator()));
}
- private static Controller createController(ControllerDb db, CuratorDb curator,
+ private static Controller createController(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig,
ConfigServerClientMock configServerClientMock, ManualClock clock,
GitHubMock gitHubClientMock, ZoneRegistryMock zoneRegistryMock,
AthenzDbMock athensDb, MemoryNameService nameService) {
Controller controller = new Controller(db,
curator,
- new MemoryRotationRepository(),
+ rotationsConfig,
gitHubClientMock,
new MemoryEntityService(),
new MockOrganization(clock),
@@ -237,4 +251,13 @@ public final class ControllerTester {
return controller;
}
+ private static RotationsConfig defaultRotationsConfig() {
+ RotationsConfig.Builder builder = new RotationsConfig.Builder();
+ for (int i = 1; i <= 10; i++) {
+ String id = String.format("%02d", i);
+ builder = builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id);
+ }
+ return new RotationsConfig(builder);
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java
index 355b63335c0..085819b433d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java
@@ -9,9 +9,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.filter.AthenzTestUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
/**
* @author Tony Vaagenes
@@ -34,8 +32,6 @@ public class TestIdentities {
public static Tenant tenant = Tenant.createOpsDbTenant(tenantId, userGroup1, property);
- public static NToken userNToken = new NToken.Builder(
- "U1", AthenzUtils.createPrincipal(userId), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0")
- .build();
+ public static NToken userNToken = new NToken("dummy");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java
index 18332942c24..2317b7bc6f1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java
@@ -3,15 +3,16 @@ package com.yahoo.vespa.hosted.controller;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import java.net.URI;
import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -24,19 +25,19 @@ import java.util.Optional;
*/
public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry {
- private final Map<Zone, Duration> deploymentTimeToLive = new HashMap<>();
+ private final Map<ZoneId, Duration> deploymentTimeToLive = new HashMap<>();
private final Map<Environment, RegionName> defaultRegionForEnvironment = new HashMap<>();
- private List<Zone> zones = new ArrayList<>();
+ private List<ZoneId> zones = new ArrayList<>();
private SystemName system = SystemName.main;
@Inject
public ZoneRegistryMock() {
- this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("corp-us-east-1")));
- this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("us-east-3")));
- this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("us-west-1")));
+ this.zones.add(ZoneId.from("prod", "corp-us-east-1"));
+ this.zones.add(ZoneId.from("prod", "us-east-3"));
+ this.zones.add(ZoneId.from("prod", "us-west-1"));
}
- public ZoneRegistryMock setDeploymentTimeToLive(Zone zone, Duration duration) {
+ public ZoneRegistryMock setDeploymentTimeToLive(ZoneId zone, Duration duration) {
deploymentTimeToLive.put(zone, duration);
return this;
}
@@ -46,7 +47,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
return this;
}
- public ZoneRegistryMock setZones(List<Zone> zones) {
+ public ZoneRegistryMock setZones(List<ZoneId> zones) {
this.zones = zones;
return this;
}
@@ -62,33 +63,47 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
}
@Override
- public List<Zone> zones() {
+ public List<ZoneId> zones() {
return Collections.unmodifiableList(zones);
}
@Override
- public Optional<Zone> getZone(Environment environment, RegionName region) {
- return zones().stream().filter(z -> z.environment().equals(environment) &&
- z.region().equals(region)).findFirst();
+ public boolean hasZone(ZoneId zoneId) {
+ return zones.contains(zoneId);
}
@Override
- public List<URI> getConfigServerUris(Environment environment, RegionName region) {
- return getZone(environment, region)
- .map(z -> URI.create(String.format("http://cfg.%s.%s.test", environment.value(), region.value())))
- .map(Collections::singletonList)
- .orElse(Collections.emptyList());
+ public List<URI> getConfigServerUris(ZoneId zoneId) {
+ return Collections.singletonList(URI.create(String.format("http://cfg.%s.test", zoneId.value())));
}
@Override
- public Optional<URI> getLogServerUri(Environment environment, RegionName region) {
- return getZone(environment, region)
- .map(z -> URI.create(String.format("http://log.%s.%s.test", environment.value(), region.value())));
+ public List<URI> getConfigServerSecureUris(ZoneId zoneId) {
+ return Collections.singletonList(URI.create(String.format("https://cfg.%s.test:4443", zoneId.value())));
}
@Override
- public Optional<Duration> getDeploymentTimeToLive(Environment environment, RegionName region) {
- return Optional.ofNullable(deploymentTimeToLive.get(new Zone(environment, region)));
+ public Optional<URI> getLogServerUri(DeploymentId deploymentId) {
+ if ( ! hasZone(deploymentId.zoneId()))
+ return Optional.empty();
+
+ String kibanaQuery = "/#/discover?_g=()&_a=(columns:!(_source)," +
+ "index:'logstash-*',interval:auto," +
+ "query:(query_string:(analyze_wildcard:!t,query:'" +
+ "HV-tenant:%22" + deploymentId.applicationId().tenant().value() + "%22%20" +
+ "AND%20HV-application:%22" + deploymentId.applicationId().application().value() + "%22%20" +
+ "AND%20HV-region:%22" + deploymentId.zoneId().region().value() + "%22%20" +
+ "AND%20HV-instance:%22" + deploymentId.applicationId().instance().value() + "%22%20" +
+ "AND%20HV-environment:%22" + deploymentId.zoneId().environment().value() + "%22'))," +
+ "sort:!('@timestamp',desc))";
+
+ URI kibanaPath = URI.create(kibanaQuery);
+ return Optional.of(URI.create(String.format("http://log.%s.test", deploymentId.zoneId().value())).resolve(kibanaPath));
+ }
+
+ @Override
+ public Optional<Duration> getDeploymentTimeToLive(ZoneId zoneId) {
+ return Optional.ofNullable(deploymentTimeToLive.get(zoneId));
}
@Override
@@ -97,9 +112,9 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
}
@Override
- public URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application) {
- return URI.create("http://monitoring-system.test/?environment=" + environment.value() + "&region="
- + name.value() + "&application=" + application.toShortString());
+ public URI getMonitoringSystemUri(DeploymentId deploymentId) {
+ return URI.create("http://monitoring-system.test/?environment=" + deploymentId.zoneId().environment().value() + "&region="
+ + deploymentId.zoneId().region().value() + "&application=" + deploymentId.applicationId().toShortString());
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java
index 20db038485d..ffb78b7342a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java
@@ -7,10 +7,10 @@ import com.yahoo.jdisc.handler.ReadableContentChannel;
import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
import org.junit.Before;
import org.junit.Test;
@@ -35,7 +35,7 @@ import static org.mockito.Mockito.when;
*/
public class AthenzPrincipalFilterTest {
- private static final NToken NTOKEN = createDummyToken();
+ private static final NToken NTOKEN = new NToken("dummy");
private static final String ATHENZ_PRINCIPAL_HEADER = "Athenz-Principal-Auth";
private NTokenValidator validator;
@@ -44,13 +44,13 @@ public class AthenzPrincipalFilterTest {
@Before
public void before() {
validator = mock(NTokenValidator.class);
- principal = AthenzUtils.createPrincipal(new UserId("bob"));
+ principal = new AthenzPrincipal(AthenzUser.fromUserId(new UserId("bob")), NTOKEN);
}
@Test
public void valid_ntoken_is_accepted() throws Exception {
DiscFilterRequest request = mock(DiscFilterRequest.class);
- when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken());
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
when(validator.validate(NTOKEN)).thenReturn(principal);
@@ -78,7 +78,7 @@ public class AthenzPrincipalFilterTest {
@Test
public void invalid_token_is_unauthorized() throws Exception {
DiscFilterRequest request = mock(DiscFilterRequest.class);
- when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken());
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
when(validator.validate(NTOKEN)).thenThrow(new InvalidTokenException("Invalid token"));
@@ -92,12 +92,6 @@ public class AthenzPrincipalFilterTest {
assertThat(responseHandler.getResponseContent(), containsString("Invalid token"));
}
- private static NToken createDummyToken() {
- return new NToken.Builder(
- "U1", AthenzUtils.createPrincipal(new UserId("bob")), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0")
- .build();
- }
-
private static class ResponseHandlerMock implements ResponseHandler {
public Response response;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java
index 70ba504df03..907fabe9d75 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java
@@ -1,21 +1,25 @@
// 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.athenz.filter;
+import com.yahoo.athenz.auth.token.PrincipalToken;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
-import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.time.Instant;
import java.util.Optional;
-import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE;
-import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.createPrincipal;
+import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE;
import static org.junit.Assert.assertEquals;
/**
@@ -25,7 +29,7 @@ public class NTokenValidatorTest {
private static final KeyPair TRUSTED_KEY = AthenzTestUtils.generateRsaKeypair();
private static final KeyPair UNKNOWN_KEY = AthenzTestUtils.generateRsaKeypair();
- private static final AthenzPrincipal PRINCIPAL = createPrincipal(new UserId("myuser"));
+ private static final AthenzIdentity IDENTITY = AthenzUser.fromUserId(new UserId("myuser"));
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
@@ -33,15 +37,15 @@ public class NTokenValidatorTest {
@Test
public void valid_token_is_accepted() throws NoSuchAlgorithmException, InvalidTokenException {
NTokenValidator validator = new NTokenValidator(createKeystore());
- NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0");
+ NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "0");
AthenzPrincipal principal = validator.validate(token);
- assertEquals("user.myuser", principal.toYRN());
+ assertEquals("user.myuser", principal.getIdentity().getFullName());
}
@Test
public void invalid_signature_is_not_accepted() throws InvalidTokenException {
NTokenValidator validator = new NTokenValidator(createKeystore());
- NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), UNKNOWN_KEY, "0");
+ NToken token = createNToken(IDENTITY, Instant.now(), UNKNOWN_KEY.getPrivate(), "0");
exceptionRule.expect(InvalidTokenException.class);
exceptionRule.expectMessage("NToken is expired or has invalid signature");
validator.validate(token);
@@ -50,7 +54,7 @@ public class NTokenValidatorTest {
@Test
public void expired_token_is_not_accepted() throws InvalidTokenException {
NTokenValidator validator = new NTokenValidator(createKeystore());
- NToken token = createNToken(PRINCIPAL, 1234 /*long time ago*/, TRUSTED_KEY, "0");
+ NToken token = createNToken(IDENTITY, Instant.ofEpochMilli(1234) /*long time ago*/, TRUSTED_KEY.getPrivate(), "0");
exceptionRule.expect(InvalidTokenException.class);
exceptionRule.expectMessage("NToken is expired or has invalid signature");
validator.validate(token);
@@ -59,7 +63,7 @@ public class NTokenValidatorTest {
@Test
public void unknown_keyId_is_not_accepted() throws InvalidTokenException {
NTokenValidator validator = new NTokenValidator(createKeystore());
- NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "unknown-key-id");
+ NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "unknown-key-id");
exceptionRule.expect(InvalidTokenException.class);
exceptionRule.expectMessage("NToken has an unknown keyId");
validator.validate(token);
@@ -69,7 +73,7 @@ public class NTokenValidatorTest {
public void failing_to_find_key_should_throw_exception() throws InvalidTokenException {
ZmsKeystore keystore = (athensService, keyId) -> { throw new RuntimeException(); };
NTokenValidator validator = new NTokenValidator(keystore);
- NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0");
+ NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "0");
exceptionRule.expect(InvalidTokenException.class);
exceptionRule.expectMessage("Failed to retrieve public key");
validator.validate(token);
@@ -82,14 +86,17 @@ public class NTokenValidatorTest {
: Optional.empty();
}
- private static NToken createNToken(AthenzPrincipal principal, long issueTime, KeyPair keyPair, String keyId) {
- return new NToken.Builder("U1", principal, keyPair.getPrivate(), keyId)
+ private static NToken createNToken(AthenzIdentity identity, Instant issueTime, PrivateKey privateKey, String keyId) {
+ PrincipalToken token = new PrincipalToken.Builder("U1", identity.getDomain().id(), identity.getName())
+ .keyId(keyId)
.salt("1234")
- .hostname("host")
+ .host("host")
.ip("1.2.3.4")
- .issueTime(issueTime / 1000)
+ .issueTime(issueTime.getEpochSecond())
.expirationWindow(1000)
.build();
+ token.sign(privateKey);
+ return new NToken(token.getSignedToken());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
index 72bfa238094..3311cffa078 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.provision.AthenzDomain;
+import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.Environment;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
@@ -26,9 +28,11 @@ public class ApplicationPackageBuilder {
private String upgradePolicy = null;
private Environment environment = Environment.prod;
+ private String globalServiceId = null;
private final StringBuilder environmentBody = new StringBuilder();
private final StringBuilder validationOverridesBody = new StringBuilder();
private final StringBuilder blockChange = new StringBuilder();
+ private String athenzIdentityAttributes = null;
private String searchDefinition = "search test { }";
public ApplicationPackageBuilder upgradePolicy(String upgradePolicy) {
@@ -41,6 +45,11 @@ public class ApplicationPackageBuilder {
return this;
}
+ public ApplicationPackageBuilder globalServiceId(String globalServiceId) {
+ this.globalServiceId = globalServiceId;
+ return this;
+ }
+
public ApplicationPackageBuilder region(String regionName) {
environmentBody.append(" <region active='true'>");
environmentBody.append(regionName);
@@ -83,6 +92,11 @@ public class ApplicationPackageBuilder {
return this;
}
+ public ApplicationPackageBuilder athenzIdentity(AthenzDomain domain, AthenzService service) {
+ this.athenzIdentityAttributes = String.format("athenz-domain='%s' athenz-service='%s'", domain.value(), service.value());
+ return this;
+ }
+
/** Sets the content of the search definition test.sd */
public ApplicationPackageBuilder searchDefinition(String testSearchDefinition) {
this.searchDefinition = testSearchDefinition;
@@ -90,7 +104,12 @@ public class ApplicationPackageBuilder {
}
private byte[] deploymentSpec() {
- StringBuilder xml = new StringBuilder("<deployment version='1.0'>\n");
+ StringBuilder xml = new StringBuilder();
+ xml.append("<deployment version='1.0' ");
+ if(athenzIdentityAttributes != null) {
+ xml.append(athenzIdentityAttributes);
+ }
+ xml.append(">\n");
if (upgradePolicy != null) {
xml.append("<upgrade policy='");
xml.append(upgradePolicy);
@@ -99,6 +118,11 @@ public class ApplicationPackageBuilder {
xml.append(blockChange);
xml.append(" <");
xml.append(environment.value());
+ if (globalServiceId != null) {
+ xml.append(" global-service-id='");
+ xml.append(globalServiceId);
+ xml.append("'");
+ }
xml.append(">\n");
xml.append(environmentBody);
xml.append(" </");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
index 2dc6471effb..a58d2d0fa39 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.hosted.controller.integration;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import java.util.HashMap;
import java.util.Map;
@@ -18,12 +18,12 @@ public class MockMetricsService implements com.yahoo.vespa.hosted.controller.api
}
@Override
- public DeploymentMetrics getDeploymentMetrics(ApplicationId application, Zone zone) {
+ public DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone) {
return new DeploymentMetrics(1, 2, 3, 4, 5);
}
@Override
- public Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, Zone zone) {
+ public Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, ZoneId zone) {
Map<String, SystemMetrics> result = new HashMap<>();
SystemMetrics system = new SystemMetrics(55.54, 69.90, 34.59);
result.put("default", system);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
index ef0b05f9bb2..47d62f93def 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -35,7 +35,7 @@ public class DeploymentExpirerTest {
@Test
public void testDeploymentExpiry() throws IOException, InterruptedException {
tester.controllerTester().zoneRegistry().setDeploymentTimeToLive(
- new Zone(Environment.dev, RegionName.from("us-east-1")),
+ ZoneId.from(Environment.dev, RegionName.from("us-east-1")),
Duration.ofDays(14)
);
DeploymentExpirer expirer = new DeploymentExpirer(tester.controller(), Duration.ofDays(10),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
new file mode 100644
index 00000000000..8647b87133e
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
@@ -0,0 +1,77 @@
+// 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.config.application.api.ValidationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
+import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.component;
+import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.systemTest;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class DnsMaintainerTest {
+
+ @Test
+ @Ignore // TODO: Enable once DnsMaintainer actually removes records
+ public void removes_record_for_unassigned_rotation() {
+ DeploymentTester tester = new DeploymentTester();
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
+
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-west-1")
+ .region("us-central-1")
+ .build();
+
+ // Deploy application
+ tester.deployCompletely(application, applicationPackage);
+ assertEquals(1, tester.controllerTester().nameService().records().size());
+ Optional<Record> record = tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")
+ );
+ assertTrue(record.isPresent());
+ assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+
+ // Remove application
+ applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .allow(ValidationId.deploymentRemoval)
+ .build();
+ tester.notifyJobCompletion(component, application, true);
+ tester.deployAndNotify(application, applicationPackage, true, systemTest);
+ tester.applications().deactivate(application, ZoneId.from(Environment.test, RegionName.from("us-east-1")));
+ tester.applications().deactivate(application, ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
+ tester.applications().deleteApplication(application.id(), Optional.of(new NToken("ntoken")));
+
+ // DnsMaintainer removes record
+ DnsMaintainer dnsMaintainer = new DnsMaintainer(tester.controller(), Duration.ofHours(12),
+ new JobControl(new MockCuratorDb()),
+ tester.controllerTester().nameService());
+ dnsMaintainer.maintain();
+ assertFalse("DNS record removed", tester.controllerTester().nameService().findRecord(
+ Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")).isPresent());
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index 8839f6a5a18..ac282422c89 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ControllerTester;
@@ -692,7 +692,7 @@ public class UpgraderTest {
// Dev deployment which should be ignored
Application dev0 = tester.createApplication("dev0", "tenant1", 7, 1L);
- tester.controllerTester().deploy(dev0, new Zone(Environment.dev, RegionName.from("dev-region")));
+ tester.controllerTester().deploy(dev0, ZoneId.from(Environment.dev, RegionName.from("dev-region")));
// New version is released and canaries upgrade
version = Version.fromString("5.1");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 2c1471b29b6..b281b513f4a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -8,7 +8,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
@@ -25,6 +25,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import org.junit.Test;
import java.io.IOException;
@@ -47,8 +48,8 @@ public class ApplicationSerializerTest {
private static final ApplicationSerializer applicationSerializer = new ApplicationSerializer();
- private static final Zone zone1 = new Zone(Environment.from("prod"), RegionName.from("us-west-1"));
- private static final Zone zone2 = new Zone(Environment.from("prod"), RegionName.from("us-east-3"));
+ private static final ZoneId zone1 = ZoneId.from("prod", "us-west-1");
+ private static final ZoneId zone2 = ZoneId.from("prod", "us-east-3");
@Test
public void testSerialization() {
@@ -86,7 +87,8 @@ public class ApplicationSerializerTest {
Optional.of(new Change.VersionChange(Version.fromString("6.7"))),
true,
Optional.of(IssueId.from("1234")),
- new MetricsService.ApplicationMetrics(0.5, 0.9));
+ new MetricsService.ApplicationMetrics(0.5, 0.9),
+ Optional.of(new RotationId("my-rotation")));
Application serialized = applicationSerializer.fromSlime(applicationSerializer.toSlime(original));
@@ -115,6 +117,7 @@ public class ApplicationSerializerTest {
assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId());
assertEquals(original.deploying(), serialized.deploying());
+ assertEquals(original.rotation().get().id(), serialized.rotation().get().id());
// Test cluster utilization
assertEquals(0, serialized.deployments().get(zone1).clusterUtils().size());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
index 189b3a97a80..c668bde0d40 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals;
public class VersionStatusSerializerTest {
@Test
- public void testSerialization() throws Exception {
+ public void testSerialization() {
List<VespaVersion> vespaVersions = new ArrayList<>();
DeploymentStatistics statistics = new DeploymentStatistics(
Version.fromString("5.0"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
index 6c5120df515..f3fa1e21eda 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
@@ -5,6 +5,7 @@ import com.yahoo.application.container.JDisc;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.TestIdentities;
@@ -20,12 +21,13 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock;
+import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.Upgrader;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
@@ -44,18 +46,16 @@ import java.util.Optional;
public class ContainerControllerTester {
private final ContainerTester containerTester;
- private final Controller controller;
private final Upgrader upgrader;
public ContainerControllerTester(JDisc container, String responseFilePath) {
containerTester = new ContainerTester(container, responseFilePath);
- controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller");
CuratorDb curatorDb = new MockCuratorDb();
curatorDb.writeUpgradesPerMinute(100);
- upgrader = new Upgrader(controller, Duration.ofDays(1), new JobControl(curatorDb), curatorDb);
+ upgrader = new Upgrader(controller(), Duration.ofDays(1), new JobControl(curatorDb), curatorDb);
}
- public Controller controller() { return controller; }
+ public Controller controller() { return containerTester.controller(); }
public Upgrader upgrader() { return upgrader; }
@@ -69,18 +69,18 @@ public class ContainerControllerTester {
public Application createApplication(String athensDomain, String tenant, String application) {
AthenzDomain domain1 = addTenantAthenzDomain(athensDomain, "mytenant");
- controller.tenants().addTenant(Tenant.createAthensTenant(new TenantId(tenant), domain1,
+ controller().tenants().addTenant(Tenant.createAthensTenant(new TenantId(tenant), domain1,
new Property("property1"),
Optional.of(new PropertyId("1234"))),
Optional.of(TestIdentities.userNToken));
ApplicationId app = ApplicationId.from(tenant, application, "default");
- return controller.applications().createApplication(app, Optional.of(TestIdentities.userNToken));
+ return controller().applications().createApplication(app, Optional.of(TestIdentities.userNToken));
}
- public Application deploy(Application application, ApplicationPackage applicationPackage, Zone zone, long projectId) {
+ public Application deploy(Application application, ApplicationPackage applicationPackage, ZoneId zone, long projectId) {
ScrewdriverId app1ScrewdriverId = new ScrewdriverId(String.valueOf(projectId));
GitRevision app1RevisionId = new GitRevision(new GitRepository("repo"), new GitBranch("master"), new GitCommit("commit1"));
- controller.applications().deployApplication(application.id(),
+ controller().applications().deployApplication(application.id(),
zone,
applicationPackage,
new DeployOptions(Optional.of(new ScrewdriverBuildJob(app1ScrewdriverId, app1RevisionId)), Optional.empty(), false, false));
@@ -106,7 +106,7 @@ public class ContainerControllerTester {
AthenzDomain athensDomain = new AthenzDomain(domainName);
AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain);
domain.markAsVespaTenant();
- domain.admin(new AthenzPrincipal(new AthenzDomain("domain"), new UserId(userName)));
+ domain.admin(AthenzUtils.createAthenzIdentity(new AthenzDomain("domain"), userName));
mock.getSetup().addDomain(domain);
return athensDomain;
}
@@ -121,4 +121,17 @@ public class ContainerControllerTester {
containerTester.assertResponse(request, expectedResponse, expectedStatusCode);
}
+ /*
+ * Authorize action on tenantDomain/application for a given screwdriverId
+ */
+ public void authorize(AthenzDomain tenantDomain, ScrewdriverId screwdriverId, ApplicationAction action, Application application) {
+ AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components()
+ .getComponent(AthenzClientFactoryMock.class.getName());
+
+ mock.getSetup()
+ .domains.get(tenantDomain)
+ .applications.get(new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.id().application().value()))
+ .addRoleMember(action, AthenzService.fromScrewdriverId(screwdriverId));
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index c0e8b48f821..95810e90cdb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -43,14 +43,16 @@ public class ContainerTester {
public JDisc container() { return container; }
+ public Controller controller() {
+ return (Controller) container.components().getComponent(Controller.class.getName());
+ }
+
public void updateSystemVersion() {
- Controller controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller");
- controller.updateVersionStatus(VersionStatus.compute(controller));
+ controller().updateVersionStatus(VersionStatus.compute(controller()));
}
public void updateSystemVersion(Version version) {
- Controller controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller");
- controller.updateVersionStatus(VersionStatus.compute(controller, version));
+ controller().updateVersionStatus(VersionStatus.compute(controller(), version));
}
public void assertResponse(Supplier<Request> request, File responseFile) throws IOException {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
index 044c5d75d12..631ceab98a5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
@@ -25,60 +25,70 @@ import static org.junit.Assert.assertEquals;
public class ControllerContainerTest {
protected JDisc container;
+
@Before
public void startContainer() { container = JDisc.fromServicesXml(controllerServicesXml, Networking.disable); }
+
@After
public void stopContainer() { container.close(); }
private final String controllerServicesXml =
- "<jdisc version='1.0'>" +
- " <config name='vespa.hosted.zone.config.zone'>" +
- " <system>main</system>" +
- " </config>" +
- " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.ConfigServerClientMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.ZoneRegistryMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.Controller'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.ConfigServerProxyMock'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.integration.MockMetricsService'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.maintenance.JobControl'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.restapi.application.MockAuthorizer'/>" +
- " <component id='com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator'/>" +
- " <component id='com.yahoo.vespa.hosted.rotation.MemoryRotationRepository'/>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.RootHandler'>" +
- " <binding>http://*/</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>" +
- " <binding>http://*/application/v4/*</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>" +
- " <binding>http://*/deployment/v1/*</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.controller.ControllerApiHandler'>" +
- " <binding>http://*/controller/v1/*</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.screwdriver.ScrewdriverApiHandler'>" +
- " <binding>http://*/screwdriver/v1/*</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>" +
- " <binding>http://*/zone/v1</binding>" +
- " <binding>http://*/zone/v1/*</binding>" +
- " </handler>" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'>" +
- " <binding>http://*/zone/v2</binding>" +
- " <binding>http://*/zone/v2/*</binding>" +
- " </handler>" +
+ "<jdisc version='1.0'>\n" +
+ " <config name='vespa.hosted.zone.config.zone'>\n" +
+ " <system>main</system>\n" +
+ " </config>\n" +
+ " <config name=\"vespa.hosted.rotation.config.rotations\">\n" +
+ " <rotations>\n" +
+ " <item key=\"rotation-id-1\">rotation-fqdn-1</item>\n" +
+ " <item key=\"rotation-id-2\">rotation-fqdn-2</item>\n" +
+ " <item key=\"rotation-id-3\">rotation-fqdn-3</item>\n" +
+ " <item key=\"rotation-id-4\">rotation-fqdn-4</item>\n" +
+ " <item key=\"rotation-id-5\">rotation-fqdn-5</item>\n" +
+ " </rotations>\n" +
+ " </config>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.ConfigServerClientMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.ZoneRegistryMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.Controller'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.ConfigServerProxyMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.integration.MockMetricsService'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.maintenance.JobControl'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.restapi.application.MockAuthorizer'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator'/>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.RootHandler'>\n" +
+ " <binding>http://*/</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>\n" +
+ " <binding>http://*/application/v4/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>\n" +
+ " <binding>http://*/deployment/v1/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.controller.ControllerApiHandler'>\n" +
+ " <binding>http://*/controller/v1/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.screwdriver.ScrewdriverApiHandler'>\n" +
+ " <binding>http://*/screwdriver/v1/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>\n" +
+ " <binding>http://*/zone/v1</binding>\n" +
+ " <binding>http://*/zone/v1/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'>\n" +
+ " <binding>http://*/zone/v2</binding>\n" +
+ " <binding>http://*/zone/v2/*</binding>\n" +
+ " </handler>\n" +
"</jdisc>";
protected void assertResponse(Request request, int responseStatus, String responseMessage) throws IOException {
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 bf4586f9fd0..f48f6b02bd2 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
@@ -9,6 +9,7 @@ import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ConfigServerClientMock;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
@@ -21,10 +22,12 @@ import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils;
-import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock;
+import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
@@ -57,17 +60,23 @@ import static com.yahoo.application.container.handler.Request.Method.PUT;
/**
* @author bratseth
* @author mpolden
+ * @author bjorncs
*/
public class ApplicationApiTest extends ControllerContainerTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/";
+
private static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
+ .globalServiceId("foo")
.region("corp-us-east-1")
+ .region("us-east-3")
+ .region("us-west-1")
.build();
- private static final String athenzUserDomain = "domain1";
- private static final String athenzScrewdriverDomain = AthenzUtils.SCREWDRIVER_DOMAIN.id();
+ private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1");
+ private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345");
+ private static final UserId USER_ID = new UserId("myuser");
@Test
public void testApplicationApi() throws Exception {
@@ -75,7 +84,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
ContainerTester tester = controllerTester.containerTester();
tester.updateSystemVersion();
- addTenantAthenzDomain(athenzUserDomain, "mytenant"); // (Necessary but not provided in this API)
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // (Necessary but not provided in this API)
// GET API root
tester.assertResponse(request("/application/v4/", GET),
@@ -91,14 +100,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("cookiefreshness.json"));
// POST (add) a tenant without property ID
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// PUT (modify) a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// GET the authenticated user (with associated tenants)
- tester.assertResponse(request("/application/v4/user", GET),
+ tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID),
new File("user.json"));
// GET all tenants
tester.assertResponse(request("/application/v4/tenant/", GET),
@@ -106,15 +117,17 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Add another Athens domain, so we can try to create more tenants
- addTenantAthenzDomain("domain2", "mytenant"); // New domain to test tenant w/property ID
+ createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); // New domain to test tenant w/property ID
// Add property info for that property id, as well, in the mock organization.
addPropertyData((MockOrganization) controllerTester.controller().organization(), "1234");
// POST (add) a tenant with property ID
tester.assertResponse(request("/application/v4/tenant/tenant2", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"),
new File("tenant-without-applications-with-id.json"));
// PUT (modify) a tenant with property ID
tester.assertResponse(request("/application/v4/tenant/tenant2", PUT)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"),
new File("tenant-without-applications-with-id.json"));
// GET a tenant with property ID
@@ -124,15 +137,18 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Test legacy OpsDB tenants
// POST (add) an OpsDB tenant with property ID
tester.assertResponse(request("/application/v4/tenant/tenant3", POST)
+ .userIdentity(USER_ID)
.data("{\"userGroup\":\"group1\",\"property\":\"property1\",\"propertyId\":\"1234\"}"),
new File("opsdb-tenant-with-id-without-applications.json"));
// PUT (modify) the OpsDB tenant to set another property
tester.assertResponse(request("/application/v4/tenant/tenant3", PUT)
+ .userIdentity(USER_ID)
.data("{\"userGroup\":\"group1\",\"property\":\"property2\",\"propertyId\":\"4321\"}"),
new File("opsdb-tenant-with-new-id-without-applications.json"));
// POST (create) an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ .userIdentity(USER_ID),
new File("application-reference.json"));
// GET a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", GET),
@@ -143,11 +159,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("application-list.json"));
// POST triggering of a full deployment to an application (if version is omitted, current system version is used)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST)
+ .userIdentity(USER_ID)
.data("6.1.0"),
new File("application-deployment.json"));
// DELETE (cancel) ongoing change
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE)
+ .userIdentity(USER_ID),
new File("application-deployment-cancelled.json"));
// DELETE (cancel) again is a no-op
@@ -158,14 +176,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty());
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
+ .userIdentity(USER_ID),
new File("deploy-result.json"));
// POST (deploy) an application to a zone. This simulates calls done by our tenant pipeline.
ApplicationId id = ApplicationId.from("tenant1", "application1", "default");
long screwdriverProjectId = 123;
- addScrewdriverUserToDomain("screwdriveruser1", "domain1"); // (Necessary but not provided in this API)
+ addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
+ ATHENZ_TENANT_DOMAIN,
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API)
// Trigger deployment
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST)
@@ -175,7 +195,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... systemtest
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
.data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId)))
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE),
"Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default");
@@ -184,7 +204,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... staging
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST)
.data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId)))
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE),
"Deactivated tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default");
@@ -193,7 +213,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... prod zone
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/", POST)
.data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId)))
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
controllerTester.notifyJobCompletion(id, screwdriverProjectId, false, DeploymentJobs.JobType.productionCorpUsEast1);
@@ -211,22 +231,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "default"));
// GET at root, with "&recursive=deployment", returns info about all tenants, their applications and their deployments
tester.assertResponse(request("/application/v4/", GET)
- .domain("domain1").user("mytenant")
+ .userIdentity(USER_ID)
.recursive("deployment"),
new File("recursive-root.json"));
// GET at root, with "&recursive=tenant", returns info about all tenants, with limmited info about their applications.
tester.assertResponse(request("/application/v4/", GET)
- .domain("domain1").user("mytenant")
+ .userIdentity(USER_ID)
.recursive("tenant"),
new File("recursive-until-tenant-root.json"));
// GET at a tenant, with "&recursive=true", returns full info about their applications and their deployments
tester.assertResponse(request("/application/v4/tenant/tenant1/", GET)
- .domain("domain1").user("mytenant")
+ .userIdentity(USER_ID)
.recursive("true"),
new File("tenant1-recursive.json"));
// GET at an application, with "&recursive=true", returns full info about its deployments
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/", GET)
- .domain("domain1").user("mytenant")
+ .userIdentity(USER_ID)
.recursive("true"),
new File("application1-recursive.json"));
@@ -260,18 +280,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default", DELETE),
"Deactivated tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default");
- // DELETE an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE),
- "");
- // DELETE a tenant
- tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE),
- new File("tenant-without-applications.json"));
-
// PUT (create) the authenticated user
byte[] data = new byte[0];
tester.assertResponse(request("/application/v4/user?user=newuser&domain=by", PUT)
.data(data)
- .domain(athenzUserDomain).user("newuser"),
+ .userIdentity(new UserId("newuser")),
new File("create-user-response.json"));
// OPTIONS return 200 OK
tester.assertResponse(request("/application/v4/", Request.Method.OPTIONS),
@@ -287,11 +300,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// SET global rotation override status
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", PUT)
+ .userIdentity(USER_ID)
.data("{\"reason\":\"because i can\"}"),
new File("global-rotation-put.json"));
// DELETE global rotation override status
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", DELETE)
+ .userIdentity(USER_ID)
.data("{\"reason\":\"because i can\"}"),
new File("global-rotation-delete.json"));
@@ -300,11 +315,18 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/promote", POST),
"{\"message\":\"Successfully copied environment hosted-instance_tenant1_application1_placeholder_component_default to hosted-instance_tenant1_application1_us-west-1_prod_default\"}");
+ // DELETE an application
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID),
+ "");
+ // DELETE a tenant
+ tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID),
+ new File("tenant-without-applications.json"));
+
controllerTester.controller().deconstruct();
}
private void addIssues(ContainerControllerTester tester, ApplicationId id) {
- tester.controller().applications().lockedOrThrow(id, application ->
+ tester.controller().applications().lockOrThrow(id, application ->
tester.controller().applications().store(application
.withDeploymentIssueId(IssueId.from("123"))
.withOwnershipIssueId(IssueId.from("321"))));
@@ -316,23 +338,28 @@ public class ApplicationApiTest extends ControllerContainerTest {
ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
ContainerTester tester = controllerTester.containerTester();
tester.updateSystemVersion();
- addTenantAthenzDomain(athenzUserDomain, "mytenant");
- addScrewdriverUserToDomain("screwdriveruser1", "domain1");
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
// Create tenant
- tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// Create application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ .userIdentity(USER_ID),
new File("application-reference.json"));
+ // Grant deploy access
+ addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
+ ATHENZ_TENANT_DOMAIN,
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
+
// POST (deploy) an application to a prod zone - allowed when project ID is not specified
HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty());
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
}
@@ -342,18 +369,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
ContainerTester tester = controllerTester.containerTester();
tester.updateSystemVersion();
- addTenantAthenzDomain(athenzUserDomain, "mytenant");
- addScrewdriverUserToDomain("screwdriveruser1", "domain1");
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
// Create tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// Create application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ .userIdentity(USER_ID),
new File("application-reference.json"));
+ // Give Screwdriver project deploy access
+ addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
+
// Deploy
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.region("us-east-3")
@@ -367,12 +398,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// us-east-3
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST)
.data(deployData)
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3);
// New zone is added before us-east-3
applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
// These decides the ordering of deploymentJobs and instances in the response
.region("us-west-1")
.region("us-east-3")
@@ -383,13 +415,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// us-west-1
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST)
.data(deployData)
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsWest1);
// us-east-3
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST)
- .data(deployData).domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .data(deployData).screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3);
@@ -402,7 +434,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
public void testErrorResponses() throws Exception {
ContainerTester tester = new ContainerTester(container, responseFiles);
tester.updateSystemVersion();
- addTenantAthenzDomain("domain1", "mytenant");
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
// PUT (update) non-existing tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
@@ -427,28 +459,33 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (add) a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// POST (add) another tenant under the same domain
tester.assertResponse(request("/application/v4/tenant/tenant2", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create tenant 'tenant2': The Athens domain 'domain1' is already connected to tenant 'tenant1'\"}",
400);
// Add the same tenant again
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
+ .userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}",
400);
// POST (create) an (empty) application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ .userIdentity(USER_ID),
new File("application-reference.json"));
// Create the same application again
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"An application with id 'tenant1.application1' already exists\"}",
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ .userIdentity(USER_ID),
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1': Application already exists\"}",
400);
ConfigServerClientMock configServer = (ConfigServerClientMock)container.components().getComponent("com.yahoo.vespa.hosted.controller.ConfigServerClientMock");
@@ -458,44 +495,48 @@ public class ApplicationApiTest extends ControllerContainerTest {
HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty());
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
+ .userIdentity(USER_ID),
new File("deploy-failure.json"), 400);
// POST (deploy) an application without available capacity
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.OUT_OF_CAPACITY, null));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
+ .userIdentity(USER_ID),
new File("deploy-out-of-capacity.json"), 400);
// POST (deploy) an application where activation fails
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to activate application", ConfigServerException.ErrorCode.ACTIVATION_CONFLICT, null));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
+ .userIdentity(USER_ID),
new File("deploy-activation-conflict.json"), 409);
// POST (deploy) an application where we get an internal server error
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Internal server error", ConfigServerException.ErrorCode.INTERNAL_SERVER_ERROR, null));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
+ .userIdentity(USER_ID),
new File("deploy-internal-server-error.json"), 500);
// DELETE tenant which has an application
- tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE),
+ tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
+ .userIdentity(USER_ID),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not delete tenant 'tenant1': This tenant has active applications\"}",
400);
// DELETE application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
+ .userIdentity(USER_ID),
"");
// DELETE application again - should produce 404
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
+ .userIdentity(USER_ID),
"{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete application 'tenant1.application1': Application not found\"}",
404);
// DELETE tenant
- tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE),
+ tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
+ .userIdentity(USER_ID),
new File("tenant-without-applications.json"));
// DELETE tenant again - should produce 404
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE),
@@ -511,48 +552,46 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testAuthorization() throws Exception {
ContainerTester tester = new ContainerTester(container, responseFiles);
- String authorizedUser = "mytenant";
- String unauthorizedUser = "othertenant";
+ UserId authorizedUser = USER_ID;
+ UserId unauthorizedUser = new UserId("othertenant");
// Mutation without an authorized user is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
- .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .domain("domain1").user(null),
+ .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
"{\"error-code\":\"FORBIDDEN\",\"message\":\"User is not authenticated\"}",
403);
// ... but read methods are allowed
tester.assertResponse(request("/application/v4/tenant/", GET)
- .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .domain("domain1").user(null),
+ .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
"[]",
200);
- addTenantAthenzDomain("domain1", "mytenant");
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
// Creating a tenant for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .domain("domain1").user(unauthorizedUser),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'othertenant' is not admin in Athenz domain 'domain1'\"}",
+ .userIdentity(unauthorizedUser),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'user.othertenant' is not admin in Athenz domain 'domain1'\"}",
403);
// (Create it with the right tenant id)
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .domain("domain1").user(authorizedUser),
+ .userIdentity(authorizedUser),
new File("tenant-without-applications.json"),
200);
// Creating an application for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
- .domain("domain1").user(unauthorizedUser),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}",
+ .userIdentity(unauthorizedUser),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}",
403);
// (Create it with the right tenant id)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
- .domain("domain1").user(authorizedUser),
+ .userIdentity(authorizedUser),
new File("application-reference.json"),
200);
@@ -560,44 +599,96 @@ public class ApplicationApiTest extends ControllerContainerTest {
HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty());
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST)
.data(entity)
- .domain(athenzUserDomain).user("mytenant"),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'mytenant' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'domain1'.\"}",
+ .userIdentity(USER_ID),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'user.myuser' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'user'.\"}",
403);
// Deleting an application for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
- .domain("domain1").user(unauthorizedUser),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}",
+ .userIdentity(unauthorizedUser),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}",
403);
// (Deleting it with the right tenant id)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
- .domain("domain1").user(authorizedUser),
+ .userIdentity(authorizedUser),
"",
200);
// Updating a tenant for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .domain("domain1").user(unauthorizedUser),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}",
+ .userIdentity(unauthorizedUser),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}",
403);
// Change Athens domain
- addTenantAthenzDomain("domain2", "mytenant");
+ createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID);
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}")
- .domain("domain1").user(authorizedUser),
+ .userIdentity(authorizedUser),
"{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}",
200);
// Deleting a tenant for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
- .domain("domain1").user(unauthorizedUser),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}",
+ .userIdentity(unauthorizedUser),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}",
403);
}
+ @Test
+ public void deployment_fails_on_illegal_domain_in_deployment_spec() throws IOException {
+ ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
+ ContainerTester tester = controllerTester.containerTester();
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .upgradePolicy("default")
+ .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("invalid.domain"), com.yahoo.config.provision.AthenzService.from("service"))
+ .environment(Environment.prod)
+ .region("us-west-1")
+ .build();
+ long screwdriverProjectId = 123;
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
+
+ Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1");
+ ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
+ controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application);
+
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
+ .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId)))
+ .screwdriverIdentity(screwdriverId),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Athenz domain in deployment.xml: [invalid.domain] must match tenant domain: [domain1]\"}",
+ 403);
+
+ }
+
+ @Test
+ public void deployment_succeeds_when_correct_domain_is_used() throws IOException {
+ ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
+ ContainerTester tester = controllerTester.containerTester();
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .upgradePolicy("default")
+ .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
+ .environment(Environment.prod)
+ .region("us-west-1")
+ .build();
+ long screwdriverProjectId = 123;
+ ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
+
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
+
+ Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1");
+ controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application);
+
+ // Allow systemtest to succeed by notifying completion of system test
+ controllerTester.notifyJobCompletion(application.id(), screwdriverProjectId, true, DeploymentJobs.JobType.component);
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
+ .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId)))
+ .screwdriverIdentity(screwdriverId),
+ new File("deploy-result.json"));
+
+ }
+
private HttpEntity createApplicationDeployData(ApplicationPackage applicationPackage, Optional<Long> screwdriverJobId) {
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("deployOptions", deployOptions(screwdriverJobId), ContentType.APPLICATION_JSON);
@@ -634,8 +725,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
private final String path;
private final Request.Method method;
private byte[] data = new byte[0];
- private String domain = "domain1";
- private String user = "mytenant";
+ private AthenzIdentity identity;
private String contentType = "application/json";
private String recursive;
@@ -655,8 +745,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
return data(out.toByteArray()).contentType(data.getContentType().getValue());
}
- private RequestBuilder domain(String domain) { this.domain = domain; return this; }
- private RequestBuilder user(String user) { this.user = user; return this; }
+ private RequestBuilder userIdentity(UserId userId) { this.identity = AthenzUser.fromUserId(userId); return this; }
+ private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = AthenzService.fromScrewdriverId(screwdriverId); return this; }
private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; }
private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; }
@@ -664,10 +754,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
public Request get() {
Request request = new Request("http://localhost:8080" + path +
// user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters
- "?domain=" + domain + (user == null ? "" : "&user=" + user) +
- (recursive == null ? "" : "&recursive=" + recursive),
+ (recursive == null ? "" : "?recursive=" + recursive),
data, method);
request.getHeaders().put("Content-Type", contentType);
+ if (identity != null) {
+ request.getHeaders().put("Athenz-Identity-Domain", identity.getDomain().id());
+ request.getHeaders().put("Athenz-Identity-Name", identity.getName());
+ }
return request;
}
}
@@ -681,26 +774,27 @@ public class ApplicationApiTest extends ControllerContainerTest {
* In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the
* mock setup to replicate the action.
*/
- private AthenzDomain addTenantAthenzDomain(String domainName, String userName) {
+ private void createAthenzDomainWithAdmin(AthenzDomain domain, UserId userId) {
AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components()
.getComponent(AthenzClientFactoryMock.class.getName());
- AthenzDomain athensDomain = new AthenzDomain(domainName);
- AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain);
- domain.markAsVespaTenant();
- domain.admin(AthenzUtils.createPrincipal(new UserId(userName)));
- mock.getSetup().addDomain(domain);
- return athensDomain;
+ AthenzDbMock.Domain domainMock = new AthenzDbMock.Domain(domain);
+ domainMock.markAsVespaTenant();
+ domainMock.admin(AthenzUser.fromUserId(userId));
+ mock.getSetup().addDomain(domainMock);
}
/**
* In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the
* mock setup to replicate the action.
*/
- private void addScrewdriverUserToDomain(String screwdriverUserId, String domainName) {
+ private void addScrewdriverUserToDeployRole(ScrewdriverId screwdriverId,
+ AthenzDomain domain,
+ com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId applicationId) {
AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components()
.getComponent(AthenzClientFactoryMock.class.getName());
- AthenzDbMock.Domain domain = mock.getSetup().domains.get(new AthenzDomain(domainName));
- domain.admin(new AthenzPrincipal(new AthenzDomain(athenzScrewdriverDomain), new UserId(screwdriverUserId)));
+ AthenzIdentity screwdriverIdentity = AthenzService.fromScrewdriverId(screwdriverId);
+ AthenzDbMock.Application athenzApplication = mock.getSetup().domains.get(domain).applications.get(applicationId);
+ athenzApplication.addRoleMember(ApplicationAction.deploy, screwdriverIdentity);
}
private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application, long projectId,
@@ -715,7 +809,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
application.tenant().value(), application.application().value());
tester.assertResponse(request(testPath, POST)
.data(deployData)
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request(testPath, DELETE),
"Deactivated " + testPath.replaceFirst("/application/v4/", ""));
@@ -726,7 +820,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
application.tenant().value(), application.application().value());
tester.assertResponse(request(stagingPath, POST)
.data(deployData)
- .domain(athenzScrewdriverDomain).user("screwdriveruser1"),
+ .screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request(stagingPath, DELETE),
"Deactivated " + stagingPath.replaceFirst("/application/v4/", ""));
@@ -742,7 +836,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
*/
private void setDeploymentMaintainedInfo(ContainerControllerTester controllerTester) {
for (Application application : controllerTester.controller().applications().asList()) {
- controllerTester.controller().applications().lockedOrThrow(application.id(), lockedApplication -> {
+ controllerTester.controller().applications().lockOrThrow(application.id(), lockedApplication -> {
lockedApplication = lockedApplication.with(new ApplicationMetrics(0.5, 0.7));
for (Deployment deployment : application.deployments().values()) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java
index e5898b7a593..988304be600 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java
@@ -5,11 +5,11 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.TestIdentities;
import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.athenz.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken;
import javax.ws.rs.core.SecurityContext;
import java.security.Principal;
@@ -20,6 +20,7 @@ import java.util.Optional;
* This is necessary because filters are not currently executed when executing requests with Application.
*
* @author bratseth
+ * @author bjorncs
*/
@SuppressWarnings("unused") // injected
public class MockAuthorizer extends Authorizer {
@@ -30,10 +31,14 @@ public class MockAuthorizer extends Authorizer {
/** Returns a principal given by the request parameters 'domain' and 'user' */
@Override
- public Optional<Principal> getPrincipalIfAny(HttpRequest request) {
- if (request.getProperty("user") == null) return Optional.empty();
- return Optional.of(new AthenzPrincipal(new AthenzDomain(request.getProperty("domain")),
- new UserId(request.getProperty("user"))));
+ public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) {
+ String domain = request.getHeader("Athenz-Identity-Domain");
+ String name = request.getHeader("Athenz-Identity-Name");
+ if (domain == null || name == null) return Optional.empty();
+ return Optional.of(
+ new AthenzPrincipal(
+ AthenzUtils.createAthenzIdentity(new AthenzDomain(domain), name),
+ new NToken("dummy")));
}
/** Returns the hardcoded NToken of {@link TestIdentities#userId} */
@@ -42,12 +47,6 @@ public class MockAuthorizer extends Authorizer {
return Optional.of(TestIdentities.userNToken);
}
- private static class MockPrincipal implements Principal {
-
- @Override
- public String getName() { return TestIdentities.userId.id(); }
-
- }
@Override
protected Optional<SecurityContext> securityContextOf(HttpRequest request) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java
index 6cf90905679..4c25bf6fe61 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.io.IOUtils;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
@@ -32,10 +32,10 @@ public class ServiceApiResponseTest {
@Test
public void testServiceViewResponse() throws URISyntaxException, IOException {
- ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.prod, RegionName.from("us-west-1")),
- ApplicationId.from("tenant1", "application1", "default"),
- Collections.singletonList(new URI("config-server1")),
- new URI("http://server1:4080/request/path?foo=bar"));
+ ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
+ ApplicationId.from("tenant1", "application1", "default"),
+ Collections.singletonList(new URI("config-server1")),
+ new URI("http://server1:4080/request/path?foo=bar"));
ApplicationView applicationView = new ApplicationView();
ClusterView clusterView = new ClusterView();
clusterView.type = "container";
@@ -63,7 +63,7 @@ public class ServiceApiResponseTest {
@Test
public void testServiceViewResponseWithURLs() throws URISyntaxException, IOException {
- ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.prod, RegionName.from("us-west-1")),
+ ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
ApplicationId.from("tenant2", "application2", "default"),
Collections.singletonList(new URI("http://cfg1.test/")),
new URI("http://cfg1.test/serviceview/v1/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/service/searchnode-9dujk1pa0vufxrj6n4yvmi8uc/state/v1"));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
index 6442ddf5c02..e46c755e8bf 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
@@ -205,8 +205,9 @@
],
"compileVersion": "(ignore)",
"globalRotations": [
- "http://fake-global-rotation-tenant1.application1"
+ "http://application1.tenant1.global.vespa.yahooapis.com:4080/"
],
+ "rotationId": "rotation-id-1",
"instances": [
{
"environment": "prod",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
index fdd3dcc4d5c..b5ed2d407df 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
@@ -146,8 +146,9 @@
],
"compileVersion": "(ignore)",
"globalRotations": [
- "http://fake-global-rotation-tenant1.application1"
+ "http://application1.tenant1.global.vespa.yahooapis.com:4080/"
],
+ "rotationId": "rotation-id-1",
"instances": [
{
"environment": "dev",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
index 41556c04209..caca0ad8970 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
@@ -146,8 +146,9 @@
],
"compileVersion": "6.1.0",
"globalRotations": [
- "http://fake-global-rotation-tenant1.application1"
+ "http://application1.tenant1.global.vespa.yahooapis.com:4080/"
],
+ "rotationId": "rotation-id-1",
"instances": [
@include(dev-us-west-1.json),
@include(prod-corp-us-east-1.json)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json
index d3927cbcfcf..79b9a785801 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json
@@ -1,5 +1,5 @@
{
- "user": "mytenant",
+ "user": "myuser",
"tenants": @include(tenant-list.json),
"tenantExists": false
} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 354bab4379c..8f8b76c83c6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -19,6 +19,9 @@
"name": "DeploymentMetricsMaintainer"
},
{
+ "name": "DnsMaintainer"
+ },
+ {
"name": "MetricsReporter"
},
{
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
index 55a4b46f4a7..d16a0222e4a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
@@ -6,7 +6,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
@@ -95,14 +95,12 @@ public class DeploymentApiTest extends ControllerContainerTest {
private void deployCompletely(Application application, ApplicationPackage applicationPackage, long projectId,
boolean success) {
tester.notifyJobCompletion(application.id(), projectId, true, component);
- tester.deploy(application, applicationPackage, new Zone(Environment.test,
- RegionName.from("us-east-1")), projectId);
+ tester.deploy(application, applicationPackage, ZoneId.from(Environment.test, RegionName.from("us-east-1")), projectId);
tester.notifyJobCompletion(application.id(), projectId, true, systemTest);
- tester.deploy(application, applicationPackage, new Zone(Environment.staging,
- RegionName.from("us-east-3")), projectId);
+ tester.deploy(application, applicationPackage, ZoneId.from(Environment.staging, RegionName.from("us-east-3")), projectId);
tester.notifyJobCompletion(application.id(), projectId, success, stagingTest);
if (success) {
- tester.deploy(application, applicationPackage, new Zone(Environment.prod,RegionName.from("corp-us-east-1")),
+ tester.deploy(application, applicationPackage, ZoneId.from(Environment.prod, RegionName.from("corp-us-east-1")),
projectId);
tester.notifyJobCompletion(application.id(), projectId, true, productionCorpUsEast1);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java
index e6b3eacd44e..1269bb23105 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java
@@ -7,9 +7,8 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.config.SlimeUtils;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
@@ -40,8 +39,8 @@ import static org.junit.Assert.assertTrue;
public class ScrewdriverApiTest extends ControllerContainerTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/responses/";
- private static final Zone testZone = new Zone(Environment.test, RegionName.from("us-east-1"));
- private static final Zone stagingZone = new Zone(Environment.staging, RegionName.from("us-east-3"));
+ private static final ZoneId testZone = ZoneId.from(Environment.test, RegionName.from("us-east-1"));
+ private static final ZoneId stagingZone = ZoneId.from(Environment.staging, RegionName.from("us-east-3"));
@Test
public void testGetReleaseStatus() throws Exception {
@@ -148,7 +147,7 @@ public class ScrewdriverApiTest extends ControllerContainerTest {
tester.containerTester().updateSystemVersion();
Application app = tester.createApplication();
- tester.controller().applications().lockedOrThrow(app.id(), application ->
+ tester.controller().applications().lockOrThrow(app.id(), application ->
tester.controller().applications().store(application.withProjectId(1)));
// Unknown application
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
index a00665b77cb..2d92d10b661 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.vespa.hosted.controller.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
@@ -21,12 +21,12 @@ import java.util.List;
public class ZoneApiTest extends ControllerContainerTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/";
- private static final List<Zone> zones = Arrays.asList(
- new Zone(Environment.prod, RegionName.from("us-north-1")),
- new Zone(Environment.dev, RegionName.from("us-north-2")),
- new Zone(Environment.test, RegionName.from("us-north-3")),
- new Zone(Environment.staging, RegionName.from("us-north-4"))
- );
+ private static final List<ZoneId> zones = Arrays.asList(
+ ZoneId.from(Environment.prod, RegionName.from("us-north-1")),
+ ZoneId.from(Environment.dev, RegionName.from("us-north-2")),
+ ZoneId.from(Environment.test, RegionName.from("us-north-3")),
+ ZoneId.from(Environment.staging, RegionName.from("us-north-4"))
+ );
private ContainerControllerTester tester;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
index 63899d808f9..9c20c470cf8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
@@ -4,7 +4,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Request.Method;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.ZoneId;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.hosted.controller.ConfigServerProxyMock;
import com.yahoo.vespa.hosted.controller.ZoneRegistryMock;
@@ -26,12 +26,12 @@ import static org.junit.Assert.assertFalse;
public class ZoneApiTest extends ControllerContainerTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/";
- private static final List<Zone> zones = Arrays.asList(
- new Zone(Environment.prod, RegionName.from("us-north-1")),
- new Zone(Environment.dev, RegionName.from("us-north-2")),
- new Zone(Environment.test, RegionName.from("us-north-3")),
- new Zone(Environment.staging, RegionName.from("us-north-4"))
- );
+ private static final List<ZoneId> zones = Arrays.asList(
+ ZoneId.from(Environment.prod, RegionName.from("us-north-1")),
+ ZoneId.from(Environment.dev, RegionName.from("us-north-2")),
+ ZoneId.from(Environment.test, RegionName.from("us-north-3")),
+ ZoneId.from(Environment.staging, RegionName.from("us-north-4"))
+ );
private ContainerControllerTester tester;
private ConfigServerProxyMock proxy;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java
new file mode 100644
index 00000000000..c259ae0ca60
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java
@@ -0,0 +1,175 @@
+// 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.rotation;
+
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.application.ApplicationRotation;
+import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.net.URI;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author Oyvind Gronnesby
+ * @author mpolden
+ */
+public class RotationTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private final RotationsConfig rotationsConfig = new RotationsConfig(
+ new RotationsConfig.Builder()
+ .rotations("foo-1", "foo-1.com")
+ .rotations("foo-2", "foo-2.com")
+ );
+
+ private final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig(
+ new RotationsConfig.Builder()
+ .rotations("foo-1", "\n foo-1.com \n")
+ .rotations("foo-2", "foo-2.com")
+ );
+
+ private final ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
+ .region("us-east-3")
+ .region("us-west-1")
+ .build();
+
+ private DeploymentTester tester;
+ private RotationRepository repository;
+ private Application application;
+
+ @Before
+ public void before() {
+ tester = new DeploymentTester(new ControllerTester(rotationsConfig));
+ repository = tester.controller().applications().rotationRepository();
+ application = tester.createApplication("app1", "tenant1", 11L,1L);
+ }
+
+ @Test
+ public void assigns_and_reuses_rotation() {
+ // Deploying assigns a rotation
+ tester.deployCompletely(application, applicationPackage);
+ Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com");
+
+ application = tester.applications().require(application.id());
+ assertEquals(expected.id(), application.rotation().get().id());
+ assertEquals(URI.create("http://app1.tenant1.global.vespa.yahooapis.com:4080/"),
+ application.rotation().get().url());
+ try (RotationLock lock = repository.lock()) {
+ Rotation rotation = repository.getRotation(tester.applications().require(application.id()), lock);
+ assertEquals(expected, rotation);
+ }
+
+ // Deploying once more assigns same rotation
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
+ .region("us-east-3")
+ .region("us-west-1")
+ .searchDefinition("search foo { }") // Update application package so there is something to deploy
+ .build();
+ tester.deployCompletely(application, applicationPackage);
+ assertEquals(expected.id(), tester.applications().require(application.id()).rotation().get().id());
+ }
+
+ @Test
+ public void strips_whitespace_in_rotation_fqdn() {
+ DeploymentTester tester = new DeploymentTester(new ControllerTester(rotationsConfigWhitespaces));
+ RotationRepository repository = tester.controller().applications().rotationRepository();
+ Application application = tester.createApplication("app2", "tenant2", 22L,
+ 2L);
+ tester.deployCompletely(application, applicationPackage);
+ application = tester.applications().require(application.id());
+
+ try (RotationLock lock = repository.lock()) {
+ Rotation rotation = repository.getRotation(application, lock);
+ Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com");
+ assertEquals(assignedRotation, rotation);
+ }
+ }
+
+
+ @Test
+ public void out_of_rotations() {
+ // Assigns 1 rotation
+ tester.deployCompletely(application, applicationPackage);
+
+ // Assigns 1 more
+ Application application2 = tester.createApplication("app2", "tenant2", 22L,
+ 2L);
+ tester.deployCompletely(application2, applicationPackage);
+
+ // We're now out of rotations
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("no rotations available");
+ Application application3 = tester.createApplication("app3", "tenant3", 33L,
+ 3L);
+ tester.deployCompletely(application3, applicationPackage);
+ }
+
+ @Test
+ public void too_few_zones() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
+ .region("us-east-3")
+ .build();
+ Application application = tester.createApplication("app2", "tenant2", 22L,
+ 2L);
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("less than 2 prod zones are defined");
+ tester.deployCompletely(application, applicationPackage);
+ }
+
+ @Test
+ public void no_rotation_assigned_for_application_without_service_id() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .region("us-east-3")
+ .region("us-west-1")
+ .build();
+ tester.deployCompletely(application, applicationPackage);
+ Application app = tester.applications().require(application.id());
+ Optional<ApplicationRotation> rotation = app.rotation();
+ assertFalse(rotation.isPresent());
+ }
+
+ @Test
+ public void application_with_only_one_non_corp_region() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
+ .region("us-east-3")
+ .region("corp-us-east-1")
+ .build();
+ Application application = tester.createApplication("app2", "tenant2", 22L,
+ 2L);
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("less than 2 prod zones are defined");
+ tester.deployCompletely(application, applicationPackage);
+ }
+
+ @Test
+ public void application_with_corp_region_and_two_non_corp_region() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .globalServiceId("foo")
+ .region("us-east-3")
+ .region("corp-us-east-1")
+ .region("us-west-1")
+ .build();
+ Application application = tester.createApplication("app2", "tenant2", 22L,
+ 2L);
+ tester.deployCompletely(application, applicationPackage);
+ assertEquals(new RotationId("foo-1"), tester.applications().require(application.id())
+ .rotation().get().id());
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java
deleted file mode 100644
index 561799529f9..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java
+++ /dev/null
@@ -1,205 +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.hosted.rotation;
-
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.metrics.simple.MetricReceiver;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
-import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
-import com.yahoo.vespa.hosted.controller.persistence.ControllerDb;
-import com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb;
-import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import java.io.StringReader;
-import java.net.URI;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author Oyvind Gronnesby
- */
-public class ControllerRotationRepositoryTest {
-
- private final RotationsConfig rotationsConfig = new RotationsConfig(
- new RotationsConfig.Builder()
- .rotations("foo-1", "foo-1.com")
- .rotations("foo-2", "foo-2.com")
- );
- private final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig(
- new RotationsConfig.Builder()
- .rotations("foo-1", "\n foo-1.com \n")
- .rotations("foo-2", "foo-2.com")
- );
- private final ControllerDb controllerDb = new MemoryControllerDb();
- private final ApplicationId applicationId = ApplicationId.from("msbe", "tumblr-search", "default");
-
- @Rule public ExpectedException thrown = ExpectedException.none();
-
- private final DeploymentSpec deploymentSpec = DeploymentSpec.fromXml(
- new StringReader(
- "<deployment>" +
- " <prod global-service-id='foo'>" +
- " <region active='true'>us-east</region>" +
- " <region active='true'>us-west</region>" +
- " </prod>" +
- "</deployment>"
- )
- );
-
- private final DeploymentSpec deploymentSpecOneRegion = DeploymentSpec.fromXml(
- new StringReader(
- "<deployment>" +
- " <prod global-service-id='nalle'>" +
- " <region active='true'>us-east</region>" +
- " </prod>" +
- "</deployment>"
- )
- );
-
- private final DeploymentSpec deploymentSpecNoServiceId = DeploymentSpec.fromXml(
- new StringReader(
- "<deployment>" +
- " <prod>" +
- " <region active='true'>us-east</region>" +
- " <region active='true'>us-west</region>" +
- " </prod>" +
- "</deployment>"
- )
- );
-
- private final DeploymentSpec deploymentSpecOnlyOneNonCorpRegion = DeploymentSpec.fromXml(
- new StringReader(
- "<deployment>" +
- " <prod global-service-id='nalle'>" +
- " <region active='true'>us-east</region>" +
- " <region active='true'>corp-us-west</region>" +
- " </prod>" +
- "</deployment>"
- )
- );
-
- private final DeploymentSpec deploymentSpecWithAdditionalCorpZone = DeploymentSpec.fromXml(
- new StringReader(
- "<deployment>" +
- " <prod global-service-id='nalle'>" +
- " <region active='true'>us-east</region>" +
- " <region active='true'>corp-us-west</region>" +
- " <region active='true'>us-west</region>" +
- " </prod>" +
- "</deployment>"
- )
- );
-
- private ControllerRotationRepository repository;
- private ControllerRotationRepository repositoryWhitespaces;
-
-
- @Before
- public void setup_repository() {
- repository = new ControllerRotationRepository(rotationsConfig, controllerDb, MetricReceiver.nullImplementation);
- repositoryWhitespaces = new ControllerRotationRepository(rotationsConfigWhitespaces, controllerDb, MetricReceiver.nullImplementation);
- controllerDb.assignRotation(new RotationId("foo-1"), applicationId);
- }
-
- @Test
- public void application_with_rotation_reused() {
- Set<Rotation> rotations = repository.getOrAssignRotation(applicationId, deploymentSpec);
- Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com");
- assertContainsOnly(assignedRotation, rotations);
- }
-
- @Test
- public void names_stripped() {
- Set<Rotation> rotations = repositoryWhitespaces.getOrAssignRotation(applicationId, deploymentSpec);
- Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com");
- assertContainsOnly(assignedRotation, rotations);
- }
-
- @Test
- public void application_without_rotation() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
- Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpec);
- Rotation assignedRotation = new Rotation(new RotationId("foo-2"), "foo-2.com");
- assertContainsOnly(assignedRotation, rotations);
- }
-
- @Test
- public void application_without_rotation_but_none_left() {
- application_without_rotation(); // run this test to assign last rotation
- ApplicationId third = ApplicationId.from("thirdtenant", "thirdapplication", "default");
-
- thrown.expect(RuntimeException.class);
- thrown.expectMessage("no rotations available");
-
- repository.getOrAssignRotation(third, deploymentSpec);
- }
-
- @Test
- public void application_without_rotation_but_does_not_qualify() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
-
- thrown.expect(RuntimeException.class);
- thrown.expectMessage("less than 2 prod zones are defined");
-
- repository.getOrAssignRotation(other, deploymentSpecOneRegion);
- }
-
- @Test
- public void application_with_rotation_but_does_not_qualify() {
- Set<Rotation> rotations = repository.getOrAssignRotation(applicationId, deploymentSpecOneRegion);
- Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com");
- assertContainsOnly(assignedRotation, rotations);
- }
-
- @Test
- public void application_with_rotation_is_listed() {
- repository.getOrAssignRotation(applicationId, deploymentSpec);
- Set<URI> uris = repository.getRotationUris(applicationId);
- assertEquals(Collections.singleton(URI.create("http://tumblr-search.msbe.global.vespa.yahooapis.com:4080/")), uris);
- }
-
- @Test
- public void application_without_rotation_is_empty() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
- Set<URI> uris = repository.getRotationUris(other);
- assertTrue(uris.isEmpty());
- }
-
- @Test
- public void application_without_serviceid_and_two_regions() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
- Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpecNoServiceId);
- assertTrue(rotations.isEmpty());
- }
-
- @Test
- public void application_with_only_one_non_corp_region() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
-
- thrown.expect(RuntimeException.class);
- thrown.expectMessage("less than 2 prod zones are defined");
-
- repository.getOrAssignRotation(other, deploymentSpecOnlyOneNonCorpRegion);
- }
-
- @Test
- public void application_with_corp_region_and_two_non_corp_region() {
- ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default");
- Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpecWithAdditionalCorpZone);
- assertContainsOnly(new Rotation(new RotationId("foo-2"), "foo-2.com"), rotations);
- }
-
- private static <T> void assertContainsOnly(T item, Collection<T> items) {
- assertTrue("Collection contains only " + item.toString(),
- items.size() == 1 && items.contains(item));
- }
-
-}
diff --git a/defaults/pom.xml b/defaults/pom.xml
index 2f3f06780e7..4d1a08aacc2 100644
--- a/defaults/pom.xml
+++ b/defaults/pom.xml
@@ -5,9 +5,10 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
- <groupId>com.yahoo.vespa</groupId>
- <artifactId>parent</artifactId>
- <version>6-SNAPSHOT</version>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>defaults</artifactId>
diff --git a/dist/build-rpm.sh b/dist/build-rpm.sh
new file mode 100755
index 00000000000..e86eebe9380
--- /dev/null
+++ b/dist/build-rpm.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+
+set -e
+
+Usage() {
+ cat <<EOF
+Usage: ${0##*/} [OPTIONS]...SPECFILE
+Run rpmbuild with the given specfile macros, creating TOPDIR if necessary.
+
+Options:
+ -b BUILDDIR Overrides %_builddir.
+ -d DIST The %dist to build for (e.g. .el7 for RHEL 7). Can be specified
+ multiple time to build multiple RPMs. The default %dist is used
+ if no -d options have been specified.
+ -h Print this help text and exit.
+ -t TOPDIR Overrides %_topdir.
+ -v VERSION [Required] The version of the RPM.
+EOF
+
+ exit 1
+}
+
+Fail() {
+ printf "%s\n" "$*"
+ exit 1
+}
+
+Run() {
+ local command="$1"
+ shift
+ printf "%q" "$command"
+
+ local arg
+ for arg in "$@"; do
+ printf " %q" "$arg"
+ done
+ printf "\n"
+
+ "$command" "$@"
+}
+
+Main() {
+ local -a dists=()
+ local version= topdir= builddir=
+ while (( $# > 0 )); do
+ case "$1" in
+ -b|--builddir)
+ builddir="$2"
+ shift 2
+ if ! test -d "$builddir"; then
+ Fail "BUILDDIR '$builddir' does not exist"
+ # Make builddir an absolute path
+ elif ! builddir=$(readlink -e "$builddir"); then
+ Fail "Failed to resolve BUILDDIR '$builddir'"
+ fi
+ ;;
+ -d|--dist)
+ local dist="$2"
+ shift 2
+ case "$dist" in
+ .el6|.el7) : ;;
+ *) Fail "Bad DIST value '$dist'" ;;
+ esac
+ dists+=("$dist")
+ ;;
+ -t|--topdir)
+ topdir="$2"
+ shift 2
+ if ! [[ "$topdir" =~ ^/ ]]; then
+ Fail "TOPDIR must be an absolute path"
+ fi
+ ;;
+ -v|--version)
+ version="$2"
+ shift 2
+ if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ Fail "VERSION must be a version of the form X.Y.Z"
+ fi
+ ;;
+ *) break ;;
+ esac
+ done
+
+ if (( $# == 0 )); then
+ Fail "Missing SPECFILE"
+ elif (( $# > 1 )); then
+ Fail "Too many arguments"
+ else
+ case "$1" in
+ help|-h|--help) Usage ;;
+ esac
+ fi
+ local specfile="$1"
+
+ local -a defines=()
+
+ if test -n "$builddir"; then
+ defines+=(--define "_builddir $builddir")
+ fi
+
+ if test -n "$topdir"; then
+ if ! mkdir -p "$topdir"; then
+ Fail "Failed to create TOPDIR directory '$topdir'"
+ fi
+ defines+=(--define "_topdir $topdir")
+ fi
+
+ if test -n "$version"; then
+ defines+=(--define "version $version")
+ else
+ Fail "VERSION is required"
+ fi
+
+ if (( ${#dists[@]} == 0 )); then
+ Run rpmbuild -bb "${defines[@]}" "$specfile"
+ else
+ local dist
+ for dist in "${dists[@]}"; do
+ Run rpmbuild -bb "${defines[@]}" --define "dist $dist" "$specfile"
+ done
+ fi
+}
+
+Main "$@"
diff --git a/dist/vespa.spec b/dist/vespa.spec
index ca6a9504401..17a9aeb03f4 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -18,11 +18,11 @@ Source0: vespa-%{version}.tar.gz
%if 0%{?centos}
BuildRequires: epel-release
BuildRequires: centos-release-scl
-BuildRequires: devtoolset-6-gcc-c++
-BuildRequires: devtoolset-6-libatomic-devel
-BuildRequires: devtoolset-6-binutils
+BuildRequires: devtoolset-7-gcc-c++
+BuildRequires: devtoolset-7-libatomic-devel
+BuildRequires: devtoolset-7-binutils
BuildRequires: rh-maven33
-%define _devtoolset_enable /opt/rh/devtoolset-6/enable
+%define _devtoolset_enable /opt/rh/devtoolset-7/enable
%define _rhmaven33_enable /opt/rh/rh-maven33/enable
%endif
%if 0%{?fedora}
@@ -68,6 +68,8 @@ BuildRequires: make
BuildRequires: vespa-cppunit-devel >= 1.12.1-6
BuildRequires: vespa-libtorrent-devel >= 1.0.11-6
BuildRequires: systemd
+BuildRequires: flex >= 2.5.0
+BuildRequires: bison >= 3.0.0
%if 0%{?centos}
Requires: epel-release
%endif
@@ -195,13 +197,18 @@ exit 0
%postun
%systemd_postun_with_restart vespa.service
%systemd_postun_with_restart vespa-configserver.service
-rm -f /etc/profile.d/vespa.sh
-userdel vespa
+if [ $1 -eq 0 ]; then # this is an uninstallation
+ rm -f /etc/profile.d/vespa.sh
+ userdel vespa
+fi
%files
%defattr(-,vespa,vespa,-)
%doc
%{_prefix}/*
+%config(noreplace) %{_prefix}/conf/logd/logd.cfg
+%config(noreplace) %{_prefix}/conf/vespa/default-env.txt
+%config(noreplace) %{_prefix}/etc/vespamalloc.conf
%attr(644,root,root) /usr/lib/systemd/system/vespa.service
%attr(644,root,root) /usr/lib/systemd/system/vespa-configserver.service
diff --git a/docker-api/pom.xml b/docker-api/pom.xml
index fc374a12dd2..449a94fb621 100644
--- a/docker-api/pom.xml
+++ b/docker-api/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>docker-api</artifactId>
diff --git a/docproc/pom.xml b/docproc/pom.xml
index 09635201e51..1043de32128 100644
--- a/docproc/pom.xml
+++ b/docproc/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>docproc</artifactId>
<packaging>jar</packaging>
diff --git a/docprocs/pom.xml b/docprocs/pom.xml
index 1b209ec39ee..16259a861c7 100644
--- a/docprocs/pom.xml
+++ b/docprocs/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>docprocs</artifactId>
<packaging>container-plugin</packaging>
diff --git a/document/pom.xml b/document/pom.xml
index 81b21487314..10f71218422 100644
--- a/document/pom.xml
+++ b/document/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>document</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/document/src/main/java/com/yahoo/document/idstring/IdIdString.java b/document/src/main/java/com/yahoo/document/idstring/IdIdString.java
index 7fcb530b347..44ff08c73f0 100644
--- a/document/src/main/java/com/yahoo/document/idstring/IdIdString.java
+++ b/document/src/main/java/com/yahoo/document/idstring/IdIdString.java
@@ -62,6 +62,9 @@ public class IdIdString extends IdString {
if (hasSetLocation) {
throw new IllegalArgumentException("Illegal key combination in " + keyValues);
}
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException("ID location value for 'n=' key is empty");
+ }
location = Long.parseLong(value);
hasSetLocation = true;
hasNumber = true;
@@ -70,6 +73,9 @@ public class IdIdString extends IdString {
if (hasSetLocation) {
throw new IllegalArgumentException("Illegal key combination in " + keyValues);
}
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException("ID location value for 'g=' key is empty");
+ }
location = makeLocation(value);
hasSetLocation = true;
hasGroup = true;
diff --git a/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java b/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java
index 79a10bc72e4..bd769889363 100644
--- a/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java
+++ b/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java
@@ -1,9 +1,12 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document;
-import com.yahoo.document.*;
import com.yahoo.document.idstring.*;
import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
import java.math.BigInteger;
@@ -12,16 +15,20 @@ import java.util.regex.Pattern;
import java.util.Arrays;
import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
-public class DocumentIdTestCase extends junit.framework.TestCase {
+public class DocumentIdTestCase {
DocumentTypeManager manager = new DocumentTypeManager();
- public DocumentIdTestCase(String name) {
- super(name);
- }
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
- protected void setUp() {
+ @Before
+ public void setUp() {
DocumentType testDocType = new DocumentType("testdoc");
testDocType.addHeaderField("intattr", DataType.INT);
@@ -33,6 +40,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
manager.registerDocumentType(testDocType);
}
+ @Test
public void testCompareTo() {
DocumentId docId1 = new Document(manager.getDocumentType("testdoc"), new DocumentId("doc:testdoc:http://www.uio.no/")).getId();
DocumentId docId2 = new Document(manager.getDocumentType("testdoc"), new DocumentId("doc:testdoc:http://www.uio.no/")).getId();
@@ -56,6 +64,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
}
}
+ @Test
public void testValidInvalidUriSchemes() {
try {
//valid URIs
@@ -92,9 +101,23 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
checkInvalidUri("id:namespace:type:n=0,g=foo:foo");
}
+ @Test
+ public void empty_user_location_value_throws_exception() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("ID location value for 'n=' key is empty");
+ new DocumentId("id:namespace:type:n=:foo");
+ }
+
+ @Test
+ public void empty_group_location_value_throws_exception() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("ID location value for 'g=' key is empty");
+ new DocumentId("id:namespace:type:g=:foo");
+ }
//Compares globalId with C++ implementation located in
// ~document-HEAD/document/src/tests/cpp-globalidbucketids.txt
+ @Test
public void testCalculateGlobalId() throws IOException{
String file = "src/tests/cpp-globalidbucketids.txt";
@@ -135,6 +158,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
//Compares bucketId with C++ implementation located in
// ~document-HEAD/document/src/tests/cpp-globalidbucketids.txt
+ @Test
public void testGetBucketId() throws IOException{
String file = "src/tests/cpp-globalidbucketids.txt";
BufferedReader fr = new BufferedReader(new FileReader(file));
@@ -153,6 +177,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
fr.close();
}
+ @Test
public void testGroupdoc() {
try {
//valid
@@ -166,11 +191,13 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
}
}
+ @Test
public void testInvalidGroupdoc() {
checkInvalidUri("grouppdoc:blabla:something");
checkInvalidUri("groupdoc:blablasomething");
}
+ @Test
public void testUriNamespace() {
DocumentId docId = new DocumentId("doc:bar:foo");
assertEquals("doc:bar:foo", docId.toString());
@@ -217,6 +244,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
assertEquals(1268182861, ((OrderDocIdString)docId.getScheme()).getOrdering());
}
+ @Test
public void testIdStrings() {
DocumentId docId;
docId = new DocumentId(new DocIdString("test", "baaaa"));
@@ -240,6 +268,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
assertEquals("type", docId.getDocType());
}
+ @Test
public void testIdStringFeatures() {
DocumentId none = new DocumentId("id:ns:type::foo");
assertFalse(none.getScheme().hasGroup());
@@ -276,6 +305,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
assertEquals(42, order.getScheme().getNumber());
}
+ @Test
public void testHashCodeOfGids() {
DocumentId docId0 = new DocumentId("doc:blabla:0");
byte[] docId0Gid = docId0.getGlobalId();
@@ -295,6 +325,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
assertEquals(Arrays.hashCode(docId0Gid), Arrays.hashCode(docId0CopyGid));
}
+ @Test
public void testDocumentIdCanOnlyContainTextCharacters() throws UnsupportedEncodingException {
assertExceptionWhenConstructing(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 0, 99}, // "id:a:b::0x0c"
"illegal code point 0x0");
@@ -313,6 +344,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
}
}
+ @Test
public void testSerializedDocumentIdCanContainNonTextCharacter() throws UnsupportedEncodingException {
String strId = new String(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 7, 99}); // "id:a:b::0x7c"
DocumentId docId = DocumentId.createFromSerialized(strId);
@@ -328,6 +360,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase {
}
}
+ @Test
public void testSerializedDocumentIdCannotContainZeroByte() throws UnsupportedEncodingException {
String strId = new String(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 0, 99}); // "id:a:b::0x0c"
try {
diff --git a/document/src/tests/bucketselectortest.cpp b/document/src/tests/bucketselectortest.cpp
index 0f8520745f1..e0857a32dba 100644
--- a/document/src/tests/bucketselectortest.cpp
+++ b/document/src/tests/bucketselectortest.cpp
@@ -85,8 +85,6 @@ void BucketSelectorTest::testSimple()
ASSERT_BUCKET_COUNT("id.bucket == 0x4000000000000258", 1u); // Bucket 600
ASSERT_BUCKET_COUNT("(testdoctype1 and id.bucket=0)", 1u);
- ASSERT_BUCKET_COUNT("searchcolumn.3 = 1", 21845u);
-
// Check that the correct buckets is found
ASSERT_BUCKET("id = \"userdoc:ns:123:foobar\"",
document::BucketId(58, 123));
diff --git a/document/src/tests/documentselectparsertest.cpp b/document/src/tests/documentselectparsertest.cpp
index c5715ae5114..db7b48cdc3a 100644
--- a/document/src/tests/documentselectparsertest.cpp
+++ b/document/src/tests/documentselectparsertest.cpp
@@ -1,6 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <vespa/document/repo/configbuilder.h>
@@ -12,12 +11,16 @@
#include <vespa/document/select/visitor.h>
#include <vespa/document/select/bodyfielddetector.h>
#include <vespa/document/select/valuenode.h>
+#include <vespa/document/select/valuenodes.h>
#include <vespa/document/select/branch.h>
#include <vespa/document/select/simpleparser.h>
#include <vespa/document/select/constant.h>
#include <vespa/document/select/invalidconstant.h>
#include <vespa/document/select/doctype.h>
#include <vespa/document/select/compare.h>
+#include <vespa/document/select/parse_utils.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <limits>
using namespace document::config_builder;
@@ -34,6 +37,15 @@ class DocumentSelectParserTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testThatComplexFieldValuesHaveCorrectFieldNames);
CPPUNIT_TEST(testBodyFieldDetection);
CPPUNIT_TEST(testDocumentUpdates);
+ CPPUNIT_TEST(test_syntax_error_reporting);
+ CPPUNIT_TEST(test_operator_precedence);
+ CPPUNIT_TEST(test_token_used_as_ident_preserves_casing);
+ CPPUNIT_TEST(test_ambiguous_field_spec_expression_is_handled_correctly);
+ CPPUNIT_TEST(test_can_build_field_value_from_field_expr_node);
+ CPPUNIT_TEST(test_can_build_function_call_from_field_expr_node);
+ CPPUNIT_TEST(test_function_call_on_doctype_throws_exception);
+ CPPUNIT_TEST(test_parse_utilities_handle_well_formed_input);
+ CPPUNIT_TEST(test_parse_utilities_handle_malformed_input);
CPPUNIT_TEST_SUITE_END();
BucketIdFactory _bucketIdFactory;
@@ -51,11 +63,13 @@ class DocumentSelectParserTest : public CppUnit::TestFixture {
const std::string& hstr);
std::unique_ptr<select::FieldValueNode>
- parseFieldValue(const std::string expression);
+ parseFieldValue(const std::string& expression);
template <typename ContainsType>
select::ResultList doParse(const vespalib::stringref& expr,
const ContainsType& t);
+
+ std::string parse_to_tree(const std::string& str);
public:
DocumentSelectParserTest()
@@ -88,7 +102,15 @@ public:
void testDocumentUpdates2();
void testDocumentUpdates3();
void testDocumentUpdates4();
- void testDocumentUpdates5();
+ void test_syntax_error_reporting();
+ void test_operator_precedence();
+ void test_token_used_as_ident_preserves_casing();
+ void test_ambiguous_field_spec_expression_is_handled_correctly();
+ void test_can_build_field_value_from_field_expr_node();
+ void test_can_build_function_call_from_field_expr_node();
+ void test_function_call_on_doctype_throws_exception();
+ void test_parse_utilities_handle_well_formed_input();
+ void test_parse_utilities_handle_malformed_input();
};
CPPUNIT_TEST_SUITE_REGISTRATION(DocumentSelectParserTest);
@@ -111,9 +133,9 @@ void DocumentSelectParserTest::setUp()
builder.document(-1673092522, "usergroup",
Struct("usergroup.header"),
Struct("usergroup.body"));
- _repo.reset(new DocumentTypeRepo(builder.config()));
+ _repo = std::make_unique<DocumentTypeRepo>(builder.config());
- _parser.reset(new select::Parser(*_repo, _bucketIdFactory));
+ _parser = std::make_unique<select::Parser>(*_repo, _bucketIdFactory);
}
Document::SP DocumentSelectParserTest::createDoc(
@@ -319,11 +341,45 @@ void verifyParse(const std::string& query, const char* expected = 0) {
}
}
+void DocumentSelectParserTest::test_syntax_error_reporting() {
+ createDocs();
+
+ verifyFailedParse("testdoctype1.headerval == aaa", "ParsingFailedException: "
+ "syntax error, unexpected end of input, expecting . at column 30 "
+ "when parsing selection 'testdoctype1.headerval == aaa'");
+ // TODO improve error reporting of broken escape sequences. Current error messages
+ // are not too helpful since we simply fail to parse the string token altogether.
+ verifyFailedParse("testdoctype1.headerval == \"tab\\x0notcomplete\"",
+ "ParsingFailedException: Unexpected character: '\\\"' at column 27 "
+ "when parsing selection 'testdoctype1.headerval == \"tab\\x0notcomplete\"'");
+ verifyFailedParse("testdoctype1.headerval == \"tab\\ysf\"",
+ "ParsingFailedException: Unexpected character: '\\\"' at column 27 "
+ "when parsing selection 'testdoctype1.headerval == \"tab\\ysf\"'");
+ // Test illegal operator
+ verifyFailedParse("testdoctype1.headerval <> 12", "ParsingFailedException: syntax error, "
+ "unexpected > at column 25 when parsing selection 'testdoctype1.headerval <> 12'");
+
+ // This will trigger a missing doctype error instead of syntax error, as "fal"
+ // will be reduced into a doctype rule.
+ verifyFailedParse("fal se", "ParsingFailedException: Document type 'fal' "
+ "not found at column 1 when parsing selection 'fal se'");
+
+ verifyFailedParse("mytype", "ParsingFailedException: Document type 'mytype' not found");
+
+ verifyFailedParse("mytype.foo.bar", "ParsingFailedException: Document type 'mytype' not found");
+
+ verifyFailedParse("testdoctype1 == 8", "ParsingFailedException: syntax error, unexpected ==, "
+ "expecting end of input at column 14 when parsing selection 'testdoctype1 == 8'");
+
+ verifyFailedParse("(1 + 2)", "ParsingFailedException: expected field spec, "
+ "doctype, bool or comparison at column 1 when parsing selection '(1 + 2)'");
+}
+
void DocumentSelectParserTest::testParseTerminals()
{
createDocs();
- // Test number value
+ // Test number value
verifyParse("", "true");
verifyParse("testdoctype1.headerval == 123");
verifyParse("testdoctype1.headerval == +123.53", "testdoctype1.headerval == 123.53");
@@ -332,10 +388,8 @@ void DocumentSelectParserTest::testParseTerminals()
"testdoctype1.headerval == 2.34124e+08");
verifyParse("testdoctype1.headerval == -234123.523E-3",
"testdoctype1.headerval == -234.124");
- verifyFailedParse("testdoctype1.headerval == aaa", "ParsingFailedException: "
- "Unexpected token at position 23 ('== aaa') in query "
- "'testdoctype1.headerval == aaa', at fullParse in ");
- // Test string value
+
+ // Test string value
verifyParse("testdoctype1.headerval == \"test\"");
std::unique_ptr<select::Node> node(
_parser->parse("testdoctype1.headerval == \"test\""));
@@ -345,64 +399,46 @@ void DocumentSelectParserTest::testParseTerminals()
dynamic_cast<const select::FieldValueNode&>(compnode.getLeft()));
const select::StringValueNode& vnode(
dynamic_cast<const select::StringValueNode&>(compnode.getRight()));
- /*
- CPPUNIT_ASSERT_EQUAL(vespalib::string("testdoctype1"),
- fnode.getDocType()->getName());
- */
+
CPPUNIT_ASSERT_EQUAL(vespalib::string("headerval"), fnode.getFieldName());
CPPUNIT_ASSERT_EQUAL(vespalib::string("test"), vnode.getValue());
- // Test whitespace
+ // Test whitespace
verifyParse("testdoctype1.headerval == \"te st \"");
verifyParse(" \t testdoctype1.headerval\t== \t \"test\"\t",
"testdoctype1.headerval == \"test\"");
- // Test escaping
+ // Test escaping
verifyParse("testdoctype1.headerval == \"tab\\ttest\"");
verifyParse("testdoctype1.headerval == \"tab\\x09test\"",
"testdoctype1.headerval == \"tab\\ttest\"");
verifyParse("testdoctype1.headerval == \"tab\\x055test\"");
- verifyFailedParse("testdoctype1.headerval == \"tab\\x0notcomplete\"",
- "ParsingFailedException: Unexpected token at position 23 "
- "('== \"tab\\x0') in query 'testdoctype1.headerval == \"tab\\x0notcomplete\"', "
- "at fullParse in ");
- verifyFailedParse("testdoctype1.headerval == \"tab\\ysf\"",
- "ParsingFailedException: Unexpected token at position 23 "
- "('== \"tab\\ys') in query 'testdoctype1.headerval == \"tab\\ysf\"', "
- "at fullParse in ");
node = _parser->parse("testdoctype1.headerval == \"\\tt\\x48 \\n\"");
select::Compare& escapednode(dynamic_cast<select::Compare&>(*node));
const select::StringValueNode& escval(
dynamic_cast<const select::StringValueNode&>(escapednode.getRight()));
CPPUNIT_ASSERT_EQUAL(vespalib::string("\ttH \n"), escval.getValue());
- // Test illegal operator
- verifyFailedParse("testdoctype1.headerval <> 12", "ParsingFailedException: Unexpected"
- " token at position 23 ('<> 12') in query 'testdoctype1.headerval <> 12', at");
- // Test <= <, > >=
+ // Test <= <, > >=
verifyParse("testdoctype1.headerval >= 123");
verifyParse("testdoctype1.headerval > 123");
verifyParse("testdoctype1.headerval <= 123");
verifyParse("testdoctype1.headerval < 123");
verifyParse("testdoctype1.headerval != 123");
- // Test defined
+ // Test defined
verifyParse("testdoctype1.headerval", "testdoctype1.headerval != null");
- // Test bools
- verifyParse("TRUE");
- verifyParse("FALSE");
+ // Test bools
+ verifyParse("TRUE", "true");
+ verifyParse("FALSE", "false");
verifyParse("true");
verifyParse("false");
- verifyParse("faLSe");
- verifyFailedParse("fal se", "ParsingFailedException: Unexpected token at "
- "position 4 ('se') in query 'fal se', at");
+ verifyParse("faLSe", "false");
- // Test document types
+ // Test document types
verifyParse("testdoctype1");
- verifyFailedParse("mytype", "ParsingFailedException: Document type mytype "
- "not found");
verifyParse("_test_doctype3_");
verifyParse("_test_doctype3_._only_in_child_ == 0");
- // Test document id with simple parser.
+ // Test document id with simple parser.
verifySimpleParse("id == \"userdoc:ns:mytest\"");
verifySimpleParse("id.namespace == \"myspace\"");
verifySimpleParse("id.scheme == \"userdoc\"");
@@ -411,7 +447,7 @@ void DocumentSelectParserTest::testParseTerminals()
verifySimpleParse("id.user == 1234");
verifySimpleParse("id.user == 0x12456ab", "id.user == 19158699");
- // Test document id
+ // Test document id
verifyParse("id == \"userdoc:ns:mytest\"");
verifyParse("id.namespace == \"myspace\"");
verifyParse("id.scheme == \"userdoc\"");
@@ -429,28 +465,23 @@ void DocumentSelectParserTest::testParseTerminals()
"id.bucket == -9223372036854775566");
verifyParse("id.gid == \"gid(0xd755743aea262650274d70f0)\"");
- // Test search column
- verifyParse("searchcolumn.10 == 2");
-
- // Test other operators
+ // Test other operators
verifyParse("id.scheme = \"*doc\"");
verifyParse("testdoctype1.hstringval =~ \"(john|barry|shrek)\"");
- // Verify functions
+ // Verify functions
verifyParse("id.hash() == 124");
verifyParse("id.specific.hash() == 124");
verifyParse("testdoctype1.hstringval.lowercase() == \"chang\"");
verifyParse("testdoctype1.hstringval.lowercase().hash() == 124");
- verifyFailedParse("testdoctype1 == 8", "ParsingFailedException: Unexpected token"
- " at position 13 ('== 8') in query 'testdoctype1 == 8', at fullParse in ");
verifyParse("testdoctype1.hintval > now()");
verifyParse("testdoctype1.hintval > now().abs()");
- // Value grouping
+ // Value grouping
verifyParse("(123) < (200)");
verifyParse("(\"hmm\") < (id.scheme)");
- // Arithmetics
+ // Arithmetics
verifyParse("1 + 2 > 1");
verifyParse("1 - 2 > 1");
verifyParse("1 * 2 > 1");
@@ -459,11 +490,11 @@ void DocumentSelectParserTest::testParseTerminals()
verifyParse("(1 + 2) * (4 - 2) == 1");
verifyParse("23 + 643 / 34 % 10 > 34");
- // CJK stuff
+ // CJK stuff
verifyParse("testdoctype1.hstringval = \"\xE4\xB8\xBA\xE4\xBB\x80\"",
"testdoctype1.hstringval = \"\\xe4\\xb8\\xba\\xe4\\xbb\\x80\"");
- // Strange doctype names
+ // Strange doctype names
verifyParse("notandor");
verifyParse("ornotand");
verifyParse("andornot");
@@ -475,16 +506,16 @@ void DocumentSelectParserTest::testParseBranches()
{
createDocs();
- verifyParse("TRUE or FALSE aNd FALSE oR TRUE");
- verifyParse("TRUE and FALSE or FALSE and TRUE");
- verifyParse("TRUE or FALSE and FALSE or TRUE");
- verifyParse("(TRUE or FALSE) and (FALSE or TRUE)");
+ verifyParse("TRUE or FALSE aNd FALSE oR TRUE", "true or false and false or true");
+ verifyParse("TRUE and FALSE or FALSE and TRUE", "true and false or false and true");
+ verifyParse("TRUE or FALSE and FALSE or TRUE", "true or false and false or true");
+ verifyParse("(TRUE or FALSE) and (FALSE or TRUE)", "(true or false) and (false or true)");
verifyParse("true or (not false) and not true");
- // Test number branching with node branches
+ // Test number branching with node branches
verifyParse("((243) < 300 and (\"FOO\").lowercase() == (\"foo\"))");
- // Strange doctype names
+ // Strange doctype names
verifyParse("notandor and ornotand");
verifyParse("ornotand or andornot");
verifyParse("not andornot");
@@ -554,7 +585,7 @@ void DocumentSelectParserTest::testOperators0()
std::cerr << ost.str() << "\n";
} // */
- // Check that comparison operators work.
+ // Check that comparison operators work.
PARSE("", *_doc[0], True);
PARSE("30 < 10", *_doc[0], False);
PARSE("10 < 30", *_doc[0], True);
@@ -593,7 +624,7 @@ void DocumentSelectParserTest::testOperators1()
{
createDocs();
- // Mix of types should within numbers, but otherwise not match
+ // Mix of types should within numbers, but otherwise not match
PARSE("30 < 10.2", *_doc[0], False);
PARSE("10.2 < 30", *_doc[0], True);
PARSE("30 < \"foo\"", *_doc[0], Invalid);
@@ -606,7 +637,7 @@ void DocumentSelectParserTest::testOperators1()
PARSE("14.3 == null", *_doc[0], False);
PARSE("null = 0", *_doc[0], False);
- // Field values
+ // Field values
PARSE("testdoctype1.headerval = 24", *_doc[0], True);
PARSE("testdoctype1.headerval = 24", *_doc[1], False);
PARSE("testdoctype1.headerval = 13", *_doc[0], False);
@@ -625,11 +656,11 @@ void DocumentSelectParserTest::testOperators1()
PARSE("testdoctype1.byteweightedset == 7", *_doc[1], False);
PARSE("testdoctype1.byteweightedset == 5", *_doc[1], True);
- // Document types
+ // Document types
PARSE("testdoctype1", *_doc[0], True);
PARSE("testdoctype2", *_doc[0], False);
- // Inherited doctypes
+ // Inherited doctypes
PARSE("testdoctype2", *_doc[4], True);
PARSE("testdoctype2", *_doc[3], False);
PARSE("testdoctype1", *_doc[4], True);
@@ -640,7 +671,7 @@ void DocumentSelectParserTest::testOperators2()
{
createDocs();
- // Id values
+ // Id values
PARSEI("id == \"doc:myspace:anything\"", *_doc[0], True);
PARSEI(" iD== \"doc:myspace:anything\" ", *_doc[0], True);
PARSEI("id == \"doc:myspa:nything\"", *_doc[0], False);
@@ -702,16 +733,13 @@ void DocumentSelectParserTest::testOperators3()
PARSEI("id.user = 1234", *_doc[8], True);
PARSEI("id.group == \"1234\"", *_doc[8], True);
PARSEI("id.group == \"mygroup\"", *_doc[9], True);
-
- // Searchcolumn policy
- PARSE("searchcolumn.10 == 8", *_doc[0], True);
}
void DocumentSelectParserTest::testOperators4()
{
createDocs();
- // Branch operators
+ // Branch operators
PARSEI("true and false", *_doc[0], False);
PARSEI("true and true", *_doc[0], True);
PARSEI("true or false", *_doc[0], True);
@@ -723,7 +751,7 @@ void DocumentSelectParserTest::testOperators4()
PARSEI("true and not false or false", *_doc[0], True);
PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_doc[0], True);
- // Invalid branching. testdoctype1.content = 1 is invalid
+ // Invalid branching. testdoctype1.content = 1 is invalid
PARSE("testdoctype1.content = 1 and true", *_doc[0], Invalid);
PARSE("testdoctype1.content = 1 or true", *_doc[0], True);
PARSE("testdoctype1.content = 1 and false", *_doc[0], False);
@@ -738,7 +766,7 @@ void DocumentSelectParserTest::testOperators5()
{
createDocs();
- // Functions
+ // Functions
PARSE("testdoctype1.hstringval.lowercase() == \"Yet\"", *_doc[3], False);
PARSE("testdoctype1.hstringval.lowercase() == \"yet\"", *_doc[3], True);
PARSE("testdoctype1.hfloatval.lowercase() == \"yet\"", *_doc[3], Invalid);
@@ -754,7 +782,7 @@ void DocumentSelectParserTest::testOperators5()
PARSE("now() < 1311862500", *_doc[10], False);
PARSE("now() > 1611862500", *_doc[10], False);
- // Arithmetics
+ // Arithmetics
PARSEI("id.specific.hash() % 10 = 8", *_doc[0], True);
PARSEI("id.specific.hash() % 10 = 2", *_doc[0], False);
PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_doc[0], True);
@@ -767,14 +795,18 @@ void DocumentSelectParserTest::testOperators6()
{
createDocs();
- // CJK
- // Assuming the characters " \ ? * is not used as part of CJK tokens
+ // CJK
+ // Assuming the characters " \ ? * is not used as part of CJK tokens
PARSE("testdoctype1.content=\"\xE4\xB8\xBA\xE4\xBB\x80\"", *_doc[3], True);
PARSE("testdoctype1.content=\"\xE4\xB7\xBA\xE4\xBB\x80\"", *_doc[3], False);
- // Structs and arrays
+ // Structs and arrays
PARSE("testdoctype1.mystruct", *_doc[0], False);
PARSE("testdoctype1.mystruct", *_doc[1], True);
+ PARSE("(testdoctype1.mystruct)", *_doc[0], False);
+ PARSE("(testdoctype1.mystruct)", *_doc[1], True);
+ PARSE("(((testdoctype1.mystruct)))", *_doc[0], False);
+ PARSE("(((testdoctype1.mystruct)))", *_doc[1], True);
PARSE("testdoctype1.mystruct", *_doc[2], False);
PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[0], True);
PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[1], True);
@@ -812,6 +844,7 @@ void DocumentSelectParserTest::testOperators7()
PARSE("testdoctype1.structarray", *_doc[1], True);
PARSE("testdoctype1.structarray.key == 15", *_doc[1], True);
PARSE("testdoctype1.structarray[1].key == 16", *_doc[1], True);
+ PARSE("testdoctype1.structarray[1].key", *_doc[1], True); // "key is set?" expr
PARSE("testdoctype1.structarray[1].key = 16", *_doc[1], True);
PARSE("testdoctype1.structarray.value == \"structval1\"", *_doc[0], False);
PARSE("testdoctype1.structarray[4].value == \"structval1\"", *_doc[0], False);
@@ -952,7 +985,6 @@ namespace {
void visitArithmeticValueNode(const select::ArithmeticValueNode &) override {}
void visitFunctionValueNode(const select::FunctionValueNode &) override {}
void visitIdValueNode(const select::IdValueNode &) override {}
- void visitSearchColumnValueNode(const select::SearchColumnValueNode &) override {}
void visitFieldValueNode(const select::FieldValueNode &) override {}
void visitFloatValueNode(const select::FloatValueNode &) override {}
void visitVariableValueNode(const select::VariableValueNode &) override {}
@@ -977,15 +1009,14 @@ void DocumentSelectParserTest::testVisitor()
TestVisitor v;
root->visit(v);
+
std::string expected =
- "OR(CONSTANT(true), "
- "AND(DOCTYPE(testdoctype1), "
- "AND(OR(NOT(COMPARE(id.user = 12)), "
- "COMPARE(testdoctype1.hstringval = \"ola\")), "
- "COMPARE(testdoctype1.headerval != null)"
- ")"
- ")"
- ")";
+ "OR(CONSTANT(true), "
+ "AND(AND(DOCTYPE(testdoctype1), "
+ "OR(NOT(COMPARE(id.user = 12)), "
+ "COMPARE(testdoctype1.hstringval = \"ola\"))), "
+ "COMPARE(testdoctype1.headerval != null)))";
+
CPPUNIT_ASSERT_EQUAL(expected, v.getVisitString());
}
@@ -1093,13 +1124,15 @@ void DocumentSelectParserTest::testDocumentUpdates0()
PARSEI("\"\" =~ \"\"", *_update[0], True);
PARSEI("30 = 10", *_update[0], False);
PARSEI("30 = 30", *_update[0], True);
+ PARSEI("(30 = 10)", *_update[0], False);
+ PARSEI("(30 = 30)", *_update[0], True);
}
void DocumentSelectParserTest::testDocumentUpdates1()
{
createDocs();
- // Mix of types should within numbers, but otherwise not match
+ // Mix of types should within numbers, but otherwise not match
PARSEI("30 < 10.2", *_update[0], False);
PARSEI("10.2 < 30", *_update[0], True);
PARSEI("30 < \"foo\"", *_update[0], Invalid);
@@ -1112,17 +1145,18 @@ void DocumentSelectParserTest::testDocumentUpdates1()
PARSEI("14.3 == null", *_update[0], False);
PARSEI("null = 0", *_update[0], False);
- // Field values
+ // Field values
PARSE("testdoctype1.headerval = 24", *_update[0], Invalid);
PARSE("testdoctype1.hfloatval = 2.0", *_update[0], Invalid);
PARSE("testdoctype1.content = \"bar\"", *_update[0], Invalid);
PARSE("testdoctype1.hstringval == testdoctype1.content", *_update[0], Invalid);
- // Document types
+ // Document types
PARSE("testdoctype1", *_update[0], True);
+ PARSE("(testdoctype1)", *_update[0], True);
PARSE("testdoctype2", *_update[0], False);
- // Inherited doctypes
+ // Inherited doctypes
PARSE("testdoctype2", *_update[4], True);
PARSE("testdoctype2", *_update[3], False);
PARSE("testdoctype1", *_update[4], True);
@@ -1133,7 +1167,7 @@ void DocumentSelectParserTest::testDocumentUpdates2()
{
createDocs();
- // Id values
+ // Id values
PARSEI("id == \"doc:myspace:anything\"", *_update[0], True);
PARSEI(" iD== \"doc:myspace:anything\" ", *_update[0], True);
PARSEI("id == \"doc:myspa:nything\"", *_update[0], False);
@@ -1159,7 +1193,7 @@ void DocumentSelectParserTest::testDocumentUpdates3()
{
createDocs();
- // Branch operators
+ // Branch operators
PARSEI("true and false", *_update[0], False);
PARSEI("true and true", *_update[0], True);
PARSEI("true or false", *_update[0], True);
@@ -1171,7 +1205,7 @@ void DocumentSelectParserTest::testDocumentUpdates3()
PARSEI("true and not false or false", *_update[0], True);
PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_update[0], True);
- // Invalid branching. testdoctype1.content = 1 is invalid
+ // Invalid branching. testdoctype1.content = 1 is invalid
PARSE("testdoctype1.content = 1 and true", *_update[0], Invalid);
PARSE("testdoctype1.content = 1 or true", *_update[0], True);
PARSE("testdoctype1.content = 1 and false", *_update[0], False);
@@ -1186,7 +1220,7 @@ void DocumentSelectParserTest::testDocumentUpdates4()
{
createDocs();
- // Functions
+ // Functions
PARSEI("\"bar\".hash() == -2012135647395072713", *_update[0], True);
PARSEI("\"bar\".hash().abs() == 2012135647395072713", *_update[0], True);
PARSEI("null.hash() == 123", *_update[0], Invalid);
@@ -1195,7 +1229,7 @@ void DocumentSelectParserTest::testDocumentUpdates4()
PARSEI("\"foo\".hash() == 123", *_update[0], False);
PARSEI("(234).hash() == 123", *_update[0], False);
- // Arithmetics
+ // Arithmetics
PARSEI("id.specific.hash() % 10 = 8", *_update[0], True);
PARSEI("id.specific.hash() % 10 = 2", *_update[0], False);
PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_update[0], True);
@@ -1221,7 +1255,7 @@ void DocumentSelectParserTest::testUtf8()
}
std::unique_ptr<select::FieldValueNode>
-DocumentSelectParserTest::parseFieldValue(const std::string expression) {
+DocumentSelectParserTest::parseFieldValue(const std::string& expression) {
return std::unique_ptr<select::FieldValueNode>(dynamic_cast<select::FieldValueNode *>(
dynamic_cast<const select::Compare &>(*_parser->parse(expression)).getLeft().clone().release()));
}
@@ -1246,4 +1280,296 @@ void DocumentSelectParserTest::testThatComplexFieldValuesHaveCorrectFieldNames()
parseFieldValue("testdoctype1.headerval.meow.meow{test}")->getRealFieldName());
}
+namespace {
+
+class OperatorVisitor : public select::Visitor {
+private:
+ std::ostringstream data;
+public:
+ void visitConstant(const select::Constant& node) override {
+ data << node;
+ }
+
+ void
+ visitInvalidConstant(const select::InvalidConstant& node) override {
+ (void) node;
+ assert(false);
+ }
+
+ void visitDocumentType(const select::DocType& node) override {
+ data << "(DOCTYPE " << node << ")";
+ }
+
+ void visitComparison(const select::Compare& node) override {
+ data << '(' << node.getOperator() << ' ';
+ node.getLeft().visit(*this);
+ data << ' ';
+ node.getRight().visit(*this);
+ data << ')';
+ }
+
+ void visitAndBranch(const select::And& node) override {
+ data << "(AND ";
+ node.getLeft().visit(*this);
+ data << " ";
+ node.getRight().visit(*this);
+ data << ")";
+ }
+
+ void visitOrBranch(const select::Or& node) override {
+ data << "(OR ";
+ node.getLeft().visit(*this);
+ data << " ";
+ node.getRight().visit(*this);
+ data << ")";
+ }
+
+ void visitNotBranch(const select::Not& node) override {
+ data << "(NOT ";
+ node.getChild().visit(*this);
+ data << ")";
+ }
+
+ void visitArithmeticValueNode(const select::ArithmeticValueNode& node) override {
+ data << '(' << node.getOperatorName() << ' ';
+ node.getLeft().visit(*this);
+ data << ' ';
+ node.getRight().visit(*this);
+ data << ')';
+ }
+ void visitFunctionValueNode(const select::FunctionValueNode& node) override {
+ data << '(' << node.getFunctionName() << ' ';
+ node.getChild().visit(*this);
+ data << ')';
+ }
+ void visitIdValueNode(const select::IdValueNode& node) override {
+ data << "(ID " << node.toString() << ')';
+ }
+ void visitFieldValueNode(const select::FieldValueNode& node) override {
+ data << "(FIELD " << node.getDocType() << ' ' << node.getFieldName() << ')';
+ }
+ void visitFloatValueNode(const select::FloatValueNode& node) override {
+ data << node.getValue();
+ }
+ void visitVariableValueNode(const select::VariableValueNode& node) override {
+ data << "(VAR " << node.getVariableName() << ')';
+ }
+ void visitIntegerValueNode(const select::IntegerValueNode& node) override {
+ data << node.getValue();
+ }
+ void visitCurrentTimeValueNode(const select::CurrentTimeValueNode&) override {}
+ void visitStringValueNode(const select::StringValueNode& str) override {
+ data << '"' << str.getValue() << '"';
+ }
+ void visitNullValueNode(const select::NullValueNode&) override {
+ data << "null";
+ }
+ void visitInvalidValueNode(const select::InvalidValueNode&) override {
+ data << "INVALID!";
+ }
+
+ std::string visit_string() { return data.str(); }
+};
+
+template <typename NodeType>
+std::string node_to_string(const NodeType& node) {
+ OperatorVisitor v;
+ node.visit(v);
+ return v.visit_string();
+}
+
+}
+
+std::string DocumentSelectParserTest::parse_to_tree(const std::string& str) {
+ std::unique_ptr<select::Node> root(_parser->parse(str));
+ return node_to_string(*root);
+}
+
+void DocumentSelectParserTest::test_operator_precedence() {
+ createDocs();
+ using namespace std::string_literals;
+
+ CPPUNIT_ASSERT_EQUAL("(AND true false)"s, parse_to_tree("true and false"));
+ CPPUNIT_ASSERT_EQUAL("(AND (NOT false) true)"s, parse_to_tree("not false and true"));
+ CPPUNIT_ASSERT_EQUAL("(NOT (AND false true))"s, parse_to_tree("not (false and true)"));
+ CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE testdoctype1))"s, parse_to_tree("not testdoctype1"));
+ CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE (testdoctype1)))"s, parse_to_tree("not (testdoctype1)"));
+ CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE (testdoctype1)))"s, parse_to_tree("(not (testdoctype1))"));
+ CPPUNIT_ASSERT_EQUAL("(OR (== 1 2) (== 3 4))"s, parse_to_tree("1==2 or 3==4"));
+ CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1 2) 3) 0)"s, parse_to_tree("1+2+3 != 0"));
+ CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1.1 2.2) 3.3) 4.4)"s, parse_to_tree("1.1+2.2+3.3 != 4.4"));
+ CPPUNIT_ASSERT_EQUAL("(!= (- (- 1 2) 3) 0)"s, parse_to_tree("1-2-3 != 0"));
+ CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1 2) 3) 0)"s, parse_to_tree("1 + 2 + 3 != 0"));
+ CPPUNIT_ASSERT_EQUAL("(!= (+ 1 (* 2 3)) 0)"s, parse_to_tree("1 + 2 * 3 != 0"));
+ CPPUNIT_ASSERT_EQUAL("(!= (- (/ (* 1 2) 3) 4) 0)"s, parse_to_tree("1 * 2 / 3 - 4 != 0"));
+ CPPUNIT_ASSERT_EQUAL("(!= (/ (* 1 2) (- 3 4)) 0)"s, parse_to_tree("1 * 2 / (3 - 4) != 0"));
+ CPPUNIT_ASSERT_EQUAL("(OR (AND true (NOT (== 1 2))) false)"s,
+ parse_to_tree("true and not 1 == 2 or false"));
+ CPPUNIT_ASSERT_EQUAL("(AND (AND (AND (< 1 2) (> 3 4)) (<= 5 6)) (>= 7 8))"s,
+ parse_to_tree("1 < 2 and 3 > 4 and 5 <= 6 and 7 >= 8"));
+ CPPUNIT_ASSERT_EQUAL("(OR (AND (AND (< 1 2) (> 3 4)) (<= 5 6)) (>= 7 8))"s,
+ parse_to_tree("1 < 2 and 3 > 4 and 5 <= 6 or 7 >= 8"));
+ CPPUNIT_ASSERT_EQUAL("(OR (AND (< 1 2) (> 3 4)) (AND (<= 5 6) (>= 7 8)))"s,
+ parse_to_tree("1 < 2 and 3 > 4 or 5 <= 6 and 7 >= 8"));
+ // Unary plus is simply ignored by the parser.
+ CPPUNIT_ASSERT_EQUAL("(== 1 -2)"s, parse_to_tree("+1==-2"));
+ CPPUNIT_ASSERT_EQUAL("(== 1.23 -2.56)"s, parse_to_tree("+1.23==-2.56"));
+ CPPUNIT_ASSERT_EQUAL("(== (+ 1 2) (- 3 -4))"s, parse_to_tree("1 + +2==3 - -4"));
+ CPPUNIT_ASSERT_EQUAL("(== (+ 1 2) (- 3 -4))"s, parse_to_tree("1++2==3--4"));
+
+ // Due to the way parentheses are handled by the AST, ((foo)) always gets
+ // reduced down to (foo).
+ CPPUNIT_ASSERT_EQUAL("(DOCTYPE (testdoctype1))"s, parse_to_tree("(((testdoctype1)))"));
+ CPPUNIT_ASSERT_EQUAL("(AND (DOCTYPE (testdoctype1)) (DOCTYPE (testdoctype2)))"s,
+ parse_to_tree("((((testdoctype1))) and ((testdoctype2)))"));
+
+ CPPUNIT_ASSERT_EQUAL("(== (ID id) \"foo\")"s, parse_to_tree("id == 'foo'"));
+ CPPUNIT_ASSERT_EQUAL("(== (ID id.group) \"foo\")"s, parse_to_tree("id.group == 'foo'"));
+ // id_spec function apply
+ CPPUNIT_ASSERT_EQUAL("(== (hash (ID id)) 12345)"s, parse_to_tree("id.hash() == 12345"));
+ // Combination of id_spec function apply and arith_expr function apply
+ CPPUNIT_ASSERT_EQUAL("(== (abs (hash (ID id))) 12345)"s, parse_to_tree("id.hash().abs() == 12345"));
+}
+
+void DocumentSelectParserTest::test_token_used_as_ident_preserves_casing() {
+ createDocs();
+ using namespace std::string_literals;
+
+ // TYPE, SCHEME, ORDER etc are tokens that may also be used as identifiers
+ // without introducing parsing ambiguities. In this context their original
+ // casing should be preserved.
+ CPPUNIT_ASSERT_EQUAL("(== (VAR Type) 123)"s, parse_to_tree("$Type == 123"));
+ CPPUNIT_ASSERT_EQUAL("(== (VAR giD) 123)"s, parse_to_tree("$giD == 123"));
+ CPPUNIT_ASSERT_EQUAL("(== (VAR ORDER) 123)"s, parse_to_tree("$ORDER == 123"));
+}
+
+void DocumentSelectParserTest::test_ambiguous_field_spec_expression_is_handled_correctly() {
+ createDocs();
+ using namespace std::string_literals;
+ // In earlier revisions of LR(1)-grammar, this triggered a reduce/reduce conflict between
+ // logical_expr and arith_expr for the sequence '(' field_spec ')', which failed to
+ // parse in an expected manner. Test that we don't get regressions here.
+ CPPUNIT_ASSERT_EQUAL("(!= (FIELD testdoctype1 foo) null)"s, parse_to_tree("(testdoctype1.foo)"));
+ CPPUNIT_ASSERT_EQUAL("(AND (!= (FIELD testdoctype1 foo) null) (!= (FIELD testdoctype1 bar) null))"s,
+ parse_to_tree("(testdoctype1.foo) AND (testdoctype1.bar)"));
+}
+
+void DocumentSelectParserTest::test_can_build_field_value_from_field_expr_node() {
+ using select::FieldExprNode;
+ {
+ // Simple field expression
+ auto lhs = std::make_unique<FieldExprNode>("mydoctype");
+ auto root = std::make_unique<FieldExprNode>(std::move(lhs), "foo");
+ auto fv = root->convert_to_field_value();
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("mydoctype"), fv->getDocType());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("foo"), fv->getFieldName());
+ }
+ {
+ // Nested field expression
+ auto lhs1 = std::make_unique<FieldExprNode>("mydoctype");
+ auto lhs2 = std::make_unique<FieldExprNode>(std::move(lhs1), "foo");
+ auto root = std::make_unique<FieldExprNode>(std::move(lhs2), "bar");
+ auto fv = root->convert_to_field_value();
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("mydoctype"), fv->getDocType());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("foo.bar"), fv->getFieldName());
+ }
+}
+
+void DocumentSelectParserTest::test_can_build_function_call_from_field_expr_node() {
+ using select::FieldExprNode;
+ {
+ // doctype.foo.lowercase()
+ // Note that calling lowercase() directly on the doctype is not supported
+ // (see test_function_call_on_doctype_throws_exception)
+ auto lhs1 = std::make_unique<FieldExprNode>("mydoctype");
+ auto lhs2 = std::make_unique<FieldExprNode>(std::move(lhs1), "foo");
+ auto root = std::make_unique<FieldExprNode>(std::move(lhs2), "lowercase");
+ auto func = root->convert_to_function_call();
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("lowercase"), func->getFunctionName());
+ // TODO vespalib::string?
+ CPPUNIT_ASSERT_EQUAL(std::string("(FIELD mydoctype foo)"), node_to_string(func->getChild()));
+ }
+}
+
+void DocumentSelectParserTest::test_function_call_on_doctype_throws_exception() {
+ using select::FieldExprNode;
+ auto lhs = std::make_unique<FieldExprNode>("mydoctype");
+ auto root = std::make_unique<FieldExprNode>(std::move(lhs), "lowercase");
+ try {
+ root->convert_to_function_call();
+ } catch (const vespalib::IllegalArgumentException& e) {
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("Cannot call function 'lowercase' directly on document type"),
+ e.getMessage());
+ }
+}
+
+namespace {
+
+void check_parse_i64(vespalib::stringref str, bool expect_ok, int64_t expected_output) {
+ int64_t out = 0;
+ bool ok = select::util::parse_i64(str.data(), str.size(), out);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for i64 input " + str, expect_ok, ok);
+ if (expect_ok) {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for i64 input " + str, expected_output, out);
+ }
+}
+
+void check_parse_hex_i64(vespalib::stringref str, bool expect_ok, int64_t expected_output) {
+ int64_t out = 0;
+ bool ok = select::util::parse_hex_i64(str.data(), str.size(), out);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for hex i64 input " + str, expect_ok, ok);
+ if (expect_ok) {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for hex i64 input " + str, expected_output, out);
+ }
+}
+
+void check_parse_double(vespalib::stringref str, bool expect_ok, double expected_output) {
+ double out = 0;
+ bool ok = select::util::parse_double(str.data(), str.size(), out);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for hex i64 input " + str, expect_ok, ok);
+ if (expect_ok) {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for double input " + str, expected_output, out);
+ }
+}
+
+}
+
+void DocumentSelectParserTest::test_parse_utilities_handle_well_formed_input() {
+ check_parse_i64("0", true, 0);
+ check_parse_i64("1", true, 1);
+ check_parse_i64("9223372036854775807", true, INT64_MAX);
+
+ // Note: 0x prefix is _not_ included
+ check_parse_hex_i64("0", true, 0);
+ check_parse_hex_i64("1", true, 1);
+ check_parse_hex_i64("f", true, 15);
+ check_parse_hex_i64("F", true, 15);
+ check_parse_hex_i64("ffffffff", true, UINT32_MAX);
+ check_parse_hex_i64("7FFFFFFFFFFFFFFF", true, INT64_MAX);
+ // We actually parse as u64 internally, then convert
+ check_parse_hex_i64("ffffffffffffffff", true, -1);
+
+ check_parse_double("1.0", true, 1.0);
+ check_parse_double("1.", true, 1.0);
+ check_parse_double("1.79769e+308", true, 1.79769e+308); // DBL_MAX
+}
+
+void DocumentSelectParserTest::test_parse_utilities_handle_malformed_input() {
+ check_parse_i64("9223372036854775808", false, 0); // INT64_MAX + 1
+ check_parse_i64("18446744073709551615", false, 0); // UINT64_MAX
+ check_parse_i64("", false, 0);
+ check_parse_i64("bjarne", false, 0);
+ check_parse_i64("1x", false, 0);
+
+ check_parse_hex_i64("", false, 0);
+ check_parse_hex_i64("g", false, 0);
+ check_parse_hex_i64("0x1", false, 0);
+ check_parse_hex_i64("ffffffffffffffff1", false, 0);
+
+ check_parse_double("1.x", false, 0.0);
+ // TODO double outside representable range returns Inf, but we probably would
+ // like this to trigger a parse failure?
+ check_parse_double("1.79769e+309", true, std::numeric_limits<double>::infinity());
+}
+
} // document
diff --git a/document/src/vespa/document/bucket/bucketselector.cpp b/document/src/vespa/document/bucket/bucketselector.cpp
index 5ded691269a..ceb231a483c 100644
--- a/document/src/vespa/document/bucket/bucketselector.cpp
+++ b/document/src/vespa/document/bucket/bucketselector.cpp
@@ -137,27 +137,6 @@ using namespace document::select;
}
}
- void compare(const select::SearchColumnValueNode& node,
- const select::ValueNode& valnode,
- const select::Operator& op) {
- if (op == FunctionOperator::EQ || op == document::select::GlobOperator::GLOB) {
- int bucketCount = 1 << 16;
- const IntegerValueNode* val(
- dynamic_cast<const IntegerValueNode*>(&valnode));
-
- int64_t rval = val->getValue();
-
- for (int i = 0; i < bucketCount; i++) {
- int64_t column = node.getValue(BucketId(16, i));
- if (column == rval) {
- _buckets.push_back(BucketId(16, i));
- }
- }
-
- _unknown = false;
- }
- }
-
void visitComparison(const document::select::Compare& node) override {
if (node.getOperator() != document::select::FunctionOperator::EQ &&
node.getOperator() != document::select::GlobOperator::GLOB)
@@ -166,12 +145,8 @@ using namespace document::select;
}
const IdValueNode* lid(dynamic_cast<const IdValueNode*>(
&node.getLeft()));
- const SearchColumnValueNode* sc(dynamic_cast<const SearchColumnValueNode*>(
- &node.getLeft()));
if (lid) {
compare(*lid, node.getRight(), node.getOperator());
- } else if (sc) {
- compare(*sc, node.getRight(), node.getOperator());
} else {
const IdValueNode* rid(dynamic_cast<const IdValueNode*>(
&node.getRight()));
@@ -187,7 +162,6 @@ using namespace document::select;
void visitArithmeticValueNode(const ArithmeticValueNode &) override {}
void visitFunctionValueNode(const FunctionValueNode &) override {}
void visitIdValueNode(const IdValueNode &) override {}
- void visitSearchColumnValueNode(const SearchColumnValueNode &) override {}
void visitFieldValueNode(const FieldValueNode &) override {}
void visitFloatValueNode(const FloatValueNode &) override {}
void visitVariableValueNode(const VariableValueNode &) override {}
diff --git a/document/src/vespa/document/select/.gitignore b/document/src/vespa/document/select/.gitignore
index 5f004816692..919eb5c7ca9 100644
--- a/document/src/vespa/document/select/.gitignore
+++ b/document/src/vespa/document/select/.gitignore
@@ -2,3 +2,6 @@ Makefile
.depend*
.*.swp
*.So
+*.hxx
+*.cxx
+*.hh
diff --git a/document/src/vespa/document/select/CMakeLists.txt b/document/src/vespa/document/select/CMakeLists.txt
index 0e94fa0f530..6dadd35e98a 100644
--- a/document/src/vespa/document/select/CMakeLists.txt
+++ b/document/src/vespa/document/select/CMakeLists.txt
@@ -1,4 +1,14 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+find_package(BISON REQUIRED)
+find_package(FLEX REQUIRED)
+
+BISON_TARGET(DocSelParser grammar/parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.cxx)
+FLEX_TARGET(DocSelLexer grammar/lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/lexer.cxx)
+
+ADD_FLEX_BISON_DEPENDENCY(DocSelLexer DocSelParser)
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
vespa_add_library(document_select OBJECT
SOURCES
bodyfielddetector.cpp
@@ -13,7 +23,6 @@ vespa_add_library(document_select OBJECT
operator.cpp
orderingselector.cpp
orderingspecification.cpp
- parser.cpp
result.cpp
resultset.cpp
resultlist.cpp
@@ -22,6 +31,14 @@ vespa_add_library(document_select OBJECT
value.cpp
valuenode.cpp
valuenodes.cpp
+ parser.cpp
+ parse_utils.cpp
+ parsing_failed_exception.cpp
+ ${BISON_DocSelParser_OUTPUTS}
+ ${FLEX_DocSelLexer_OUTPUTS}
AFTER
document_documentconfig
)
+
+#TODO Remove once we have a recently new flex compiler. At least 2.5.38/39 or 2.6
+set_source_files_properties(${FLEX_DocSelLexer_OUTPUTS} PROPERTIES COMPILE_FLAGS -Wno-register)
diff --git a/document/src/vespa/document/select/branch.cpp b/document/src/vespa/document/select/branch.cpp
index 7f6ad252471..5b28fad0df8 100644
--- a/document/src/vespa/document/select/branch.cpp
+++ b/document/src/vespa/document/select/branch.cpp
@@ -9,7 +9,7 @@ namespace document {
namespace select {
And::And(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name)
- : Branch(name ? name : "AND"),
+ : Branch(name ? name : "and"),
_left(std::move(left)),
_right(std::move(right))
{
@@ -55,7 +55,7 @@ And::trace(const Context& context, std::ostream& out) const
}
Or::Or(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name)
- : Branch(name ? name : "OR"),
+ : Branch(name ? name : "or"),
_left(std::move(left)),
_right(std::move(right))
{
@@ -101,7 +101,7 @@ Or::trace(const Context& context, std::ostream& out) const
}
Not::Not(std::unique_ptr<Node> child, const char* name)
- : Branch(name ? name : "NOT"),
+ : Branch(name ? name : "not"),
_child(std::move(child))
{
assert(_child.get());
diff --git a/document/src/vespa/document/select/cloningvisitor.cpp b/document/src/vespa/document/select/cloningvisitor.cpp
index 4011cbdeea1..d695e3ec83d 100644
--- a/document/src/vespa/document/select/cloningvisitor.cpp
+++ b/document/src/vespa/document/select/cloningvisitor.cpp
@@ -162,7 +162,7 @@ CloningVisitor::visitConstant(const Constant &expr)
_priority = ConstPriority;
bool val = expr.getConstantValue();
_resultSet.add(val ? Result::True : Result::False);
- _node.reset(new Constant(val ? "true" : "false"));
+ _node.reset(new Constant(val));
}
@@ -199,16 +199,6 @@ CloningVisitor::visitIdValueNode(const IdValueNode &expr)
void
-CloningVisitor::visitSearchColumnValueNode(const SearchColumnValueNode &expr)
-{
- _constVal = false;
- ++_fieldNodes; // needs document id, thus needs document
- _valueNode = expr.clone();
- _priority = SearchColPriority;
-}
-
-
-void
CloningVisitor::visitFieldValueNode(const FieldValueNode &expr)
{
_constVal = false;
diff --git a/document/src/vespa/document/select/cloningvisitor.h b/document/src/vespa/document/select/cloningvisitor.h
index ff74af1201d..2c5f94c20a4 100644
--- a/document/src/vespa/document/select/cloningvisitor.h
+++ b/document/src/vespa/document/select/cloningvisitor.h
@@ -60,7 +60,6 @@ public:
void visitInvalidConstant(const InvalidConstant &expr) override;
void visitDocumentType(const DocType &expr) override;
void visitIdValueNode(const IdValueNode &expr) override;
- void visitSearchColumnValueNode(const SearchColumnValueNode &expr) override;
void visitFieldValueNode(const FieldValueNode &expr) override;
void visitFloatValueNode(const FloatValueNode &expr) override;
void visitVariableValueNode(const VariableValueNode &expr) override;
diff --git a/document/src/vespa/document/select/constant.cpp b/document/src/vespa/document/select/constant.cpp
index 02821180337..8d1445f40a0 100644
--- a/document/src/vespa/document/select/constant.cpp
+++ b/document/src/vespa/document/select/constant.cpp
@@ -7,28 +7,10 @@
namespace document::select {
-Constant::Constant(const vespalib::stringref & value)
- : Node(value),
- _value(false)
+Constant::Constant(bool value)
+ : Node(value ? "true" : "false"), // TODO remove required name from Node
+ _value(value)
{
- if (value.size() == 4 &&
- (value[0] & 0xdf) == 'T' &&
- (value[1] & 0xdf) == 'R' &&
- (value[2] & 0xdf) == 'U' &&
- (value[3] & 0xdf) == 'E')
- {
- _value = true;
- } else if (value.size() == 5 &&
- (value[0] & 0xdf) == 'F' &&
- (value[1] & 0xdf) == 'A' &&
- (value[2] & 0xdf) == 'L' &&
- (value[3] & 0xdf) == 'S' &&
- (value[4] & 0xdf) == 'E')
- {
- _value = false;
- } else {
- assert(false);
- }
}
ResultList
diff --git a/document/src/vespa/document/select/constant.h b/document/src/vespa/document/select/constant.h
index 08be5c95ec7..46a98ed7eaa 100644
--- a/document/src/vespa/document/select/constant.h
+++ b/document/src/vespa/document/select/constant.h
@@ -5,9 +5,8 @@
*
* @brief Class describing a constant in the select tree.
*
- * @author H�kon Humberset
+ * @author HÃ¥kon Humberset
* @date 2005-06-07
- * @version $Id$
*/
#pragma once
@@ -23,7 +22,7 @@ private:
bool _value;
public:
- explicit Constant(const vespalib::stringref & value);
+ explicit Constant(bool value);
ResultList contains(const Context&) const override {
return ResultList(Result::get(_value));
@@ -32,8 +31,8 @@ public:
ResultList trace(const Context&, std::ostream& trace) const override;
void print(std::ostream& out, bool verbose, const std::string& indent) const override;
void visit(Visitor& v) const override;
- bool getConstantValue() const { return _value; }
- Node::UP clone() const override { return wrapParens(new Constant(_name)); }
+ bool getConstantValue() const noexcept { return _value; }
+ Node::UP clone() const override { return wrapParens(new Constant(_value)); }
};
diff --git a/document/src/vespa/document/select/gid_filter.cpp b/document/src/vespa/document/select/gid_filter.cpp
index ce3045564ba..71a57a0886a 100644
--- a/document/src/vespa/document/select/gid_filter.cpp
+++ b/document/src/vespa/document/select/gid_filter.cpp
@@ -22,7 +22,6 @@ struct NoOpVisitor : Visitor {
void visitArithmeticValueNode(const ArithmeticValueNode&) override {}
void visitFunctionValueNode(const FunctionValueNode&) override {}
void visitIdValueNode(const IdValueNode&) override {}
- void visitSearchColumnValueNode(const SearchColumnValueNode&) override {}
void visitFieldValueNode(const FieldValueNode&) override {}
void visitFloatValueNode(const FloatValueNode&) override {}
void visitVariableValueNode(const VariableValueNode&) override {}
diff --git a/document/src/vespa/document/select/grammar/lexer.ll b/document/src/vespa/document/select/grammar/lexer.ll
new file mode 100644
index 00000000000..8cd5638c122
--- /dev/null
+++ b/document/src/vespa/document/select/grammar/lexer.ll
@@ -0,0 +1,182 @@
+ /* Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. */
+
+ /* We use the .*xx-suffix to denote a build-time generated file */
+%option outfile="lexer.cxx"
+%option header-file="lexer.hxx"
+
+%option c++
+ /* Uncomment to enable debug tracing of parsing */
+ /* %option debug */
+%option 8bit warn nodefault
+%option noyywrap nounput
+%option yyclass="document::select::DocSelScanner"
+
+ /* Used to track source locations, see https://github.com/bingmann/flex-bison-cpp-example/blob/master/src/scanner.ll */
+%{
+#define YY_USER_ACTION yyloc->columns(yyleng);
+%}
+
+%{
+
+#include "parser.hxx"
+#include <vespa/document/select/scanner.h>
+#include <vespa/document/select/parse_utils.h>
+#include <vespa/document/util/stringutil.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <string>
+#include <cstdlib>
+
+#undef YY_DECL
+#define YY_DECL int document::select::DocSelScanner::yylex( \
+ document::select::DocSelParser::semantic_type* yylval, \
+ document::select::DocSelParser::location_type* yyloc)
+
+using token = document::select::DocSelParser::token;
+using string = vespalib::string;
+
+// Inspired by https://coldfix.eu/2015/05/16/bison-c++11/
+
+#define YIELD_TOKEN(name, field_name, value) \
+ yylval->field_name = value; \
+ return token::T_##name;
+
+#define INT_TOKEN(name, value) YIELD_TOKEN(name, i64_val, value)
+#define STRING_TOKEN(name) YIELD_TOKEN(name, string_val, new string(yytext, yyleng))
+#define CONST_STR_TOKEN(name, value) YIELD_TOKEN(name, const_str_val, value)
+#define TAGGED_TOKEN INT_TOKEN
+
+#define NAMED_TOKEN(name) return token::T_##name;
+
+%}
+
+ /* Lexer fragments, used as part of token patterns */
+
+SIGN [+-]
+DECIMAL [0-9]+
+HEXDIGIT [0-9a-fA-F]
+HEX 0[xX]{HEXDIGIT}{1,16}
+OCTAL 0[0-7]*
+EXPONENT [eE][+-]?[0-9]+
+IDCHARS [a-zA-Z_][a-zA-Z_0-9_]*
+WS [ \f\r\t]
+
+ /* It is weird that you can't do \' inside "" and vice versa, but that's the StringUtil::unescape logic today... */
+DQ_STRING \"(\\([\\tnfr"]|x{HEXDIGIT}{2})|[^"\\])*\"
+SQ_STRING \'(\\([\\tnfr']|x{HEXDIGIT}{2})|[^'\\])*\'
+
+%%
+
+ /* Code to take place at the beginning of yylex() */
+%{
+ // TODO move to YY_USER_ACTION instead?
+ yyloc->step();
+%}
+
+ /* TODO support length suffixes? supported in JavaCC grammar, but not in legacy Spirit grammar... */
+{HEX} {
+ // TODO replace with std::from_string() once compiler support is there
+ if (!util::parse_hex_i64(yytext + 2, yyleng - 2, yylval->i64_val)) { // Skip 0[xX] prefix
+ throw_parser_syntax_error(*yyloc, "Not a valid 64-bit hex integer: " + std::string(yytext, yyleng));
+ }
+ return token::T_INTEGER;
+}
+
+ /* Sign is handled explicitly in the parser to avoid lexing ambiguities for expressions such as "1 -2" */
+{DECIMAL} {
+ if (!util::parse_i64(yytext, yyleng, yylval->i64_val)) {
+ throw_parser_syntax_error(*yyloc, "Not a valid signed 64-bit integer: " + std::string(yytext, yyleng));
+ }
+ return token::T_INTEGER;
+}
+
+ /*
+ * We use a strict definition of floats when lexing, i.e. we require a dot
+ * in order to remove ambiguities with the base 10 integer token.
+ */
+[0-9]+(\.[0-9]*){EXPONENT}?[fFdD]? {
+ if (!util::parse_double(yytext, yyleng, yylval->double_val)) {
+ throw_parser_syntax_error(*yyloc, "Not a valid floating point number: " + std::string(yytext, yyleng));
+ }
+ return token::T_FLOAT;
+}
+
+({DQ_STRING}|{SQ_STRING}) {
+ // Always slice off start and end quote chars
+ yylval->string_val = new string(yytext + 1, yyleng - 2);
+ return token::T_STRING;
+}
+
+ /* FIXME this is a syntactic hack to "flatten" fieldpath map and array lookups into a single token
+ rather than match these structurally in the parser itself. This is due to the way fieldpaths
+ are handled in the legacy AST (i.e. as strings, not structures), and this must be changed first
+ before we can fix this. */
+ /* Field path expressions do not support any other escapes than double quote char */
+ /* TODO {WS} does not include newline, do we need to support that here? */
+\{{WS}*($?{IDCHARS}|{DECIMAL}|\"([^\\\"]|\\\")*\"){WS}*\} STRING_TOKEN(FP_MAP_LOOKUP)
+\[{WS}*(${IDCHARS}|{DECIMAL}){WS}*\] STRING_TOKEN(FP_ARRAY_LOOKUP)
+
+ /* Primary tokens are case insensitive */
+(?i:"id") NAMED_TOKEN(ID)
+(?i:"null") NAMED_TOKEN(NULL)
+(?i:"true") NAMED_TOKEN(TRUE)
+(?i:"false") NAMED_TOKEN(FALSE)
+(?i:"and") NAMED_TOKEN(AND)
+(?i:"or") NAMED_TOKEN(OR)
+(?i:"not") NAMED_TOKEN(NOT)
+
+ /* We expose the verbatim input as the token value, as these may also be used for identifiers... */
+(?i:"user") STRING_TOKEN(USER)
+(?i:"group") STRING_TOKEN(GROUP)
+(?i:"scheme") STRING_TOKEN(SCHEME)
+(?i:"namespace") STRING_TOKEN(NAMESPACE)
+(?i:"specific") STRING_TOKEN(SPECIFIC)
+(?i:"bucket") STRING_TOKEN(BUCKET)
+(?i:"gid") STRING_TOKEN(GID)
+(?i:"type") STRING_TOKEN(TYPE)
+(?i:"order") STRING_TOKEN(ORDER)
+
+"now\(\)" NAMED_TOKEN(NOW_FUNC) /* This _is_ case-sensitive in the legacy parser */
+
+ /* Binary operators */
+ /* TODO INT_TOKEN with code directly from selection operator node? Or direct operator object ptr? */
+"=" NAMED_TOKEN(GLOB)
+"=~" NAMED_TOKEN(REGEX)
+"==" NAMED_TOKEN(EQ)
+"!=" NAMED_TOKEN(NE)
+">=" NAMED_TOKEN(GE)
+"<=" NAMED_TOKEN(LE)
+">" NAMED_TOKEN(GT)
+"<" NAMED_TOKEN(LT)
+
+"$" NAMED_TOKEN(DOLLAR)
+"." NAMED_TOKEN(DOT)
+"(" NAMED_TOKEN(LPAREN)
+")" NAMED_TOKEN(RPAREN)
+"," NAMED_TOKEN(COMMA)
+"+" NAMED_TOKEN(PLUS)
+"-" NAMED_TOKEN(MINUS)
+"*" NAMED_TOKEN(MULTIPLY)
+"/" NAMED_TOKEN(DIVIDE)
+"%" NAMED_TOKEN(MODULO)
+
+{IDCHARS} STRING_TOKEN(IDENTIFIER)
+
+\n {
+ yyloc->lines(yyleng);
+ yyloc->step();
+ return yytext[0];
+}
+
+{WS} {
+ yyloc->step();
+}
+
+ /*
+ * Everything that hasn't already matched is an error. Throw exception immediately with the exact
+ * char to avoid getting auto-generated error messages with "unexpected $undefined" due to the
+ * resulting token not matching any existing, explicitly named tokens.
+ */
+. { throw_parser_syntax_error(*yyloc, "Unexpected character: '" + StringUtil::escape(vespalib::string(yytext, 1)) + "'"); }
+
+%%
+
diff --git a/document/src/vespa/document/select/grammar/parser.yy b/document/src/vespa/document/select/grammar/parser.yy
new file mode 100644
index 00000000000..baf987355c9
--- /dev/null
+++ b/document/src/vespa/document/select/grammar/parser.yy
@@ -0,0 +1,374 @@
+ /* Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. */
+
+%output "parser.cxx"
+%defines "parser.hxx"
+
+ /* Skeleton implementation included as part of the generated source. Note: _not_ covered by the GPL. */
+%skeleton "lalr1.cc"
+
+%require "3.0"
+
+ /* Uncomment to enable debugging of lexer invocations */
+ /*%debug*/
+
+%locations
+
+%define parse.error verbose
+%define parse.assert
+
+%define api.token.prefix {T_}
+%define api.namespace {document::select}
+%define parser_class_name {DocSelParser}
+
+ /*
+ * Due to current Bison variant support not being move-enabled (and our AST ptrs being move-only),
+ * we have to use good old POD unions for our rule results. Note that we have to use %destructor
+ * for all ptrs to ensure cleanup.
+ */
+%union {
+ int64_t i64_val;
+ double double_val;
+ const char* const_str_val;
+ vespalib::string* string_val;
+ Constant* constant_node;
+ ValueNode* value_node;
+ FieldExprNode* field_expr_node;
+ Node* abstract_node;
+}
+
+%token END 0 "end of input"
+%token NULL
+%token TRUE
+%token FALSE
+%token AND
+%token OR
+%token NOT
+
+ /* Specify aliases for several tokens for ease of use and better error reporting */
+%token GLOB "="
+%token REGEX "=~"
+%token EQ "=="
+%token NE "!="
+%token GE ">="
+%token LE "<="
+%token GT ">"
+%token LT "<"
+%token ID
+%token NOW_FUNC
+
+ /*
+ * Tokens that we only mention by alias in the grammar rules, but which we define
+ * explicitly to improve error reporting
+ */
+%token DOLLAR "$"
+%token DOT "."
+%token LPAREN "("
+%token RPAREN ")"
+%token COMMA ","
+%token PLUS "+"
+%token MINUS "-"
+%token MULTIPLY "*"
+%token DIVIDE "/"
+%token MODULO "%"
+
+%token <string_val> IDENTIFIER
+%token <string_val> STRING
+%token <string_val> FP_MAP_LOOKUP FP_ARRAY_LOOKUP
+%token <double_val> FLOAT
+%token <i64_val> INTEGER
+%token <string_val> USER GROUP SCHEME NAMESPACE SPECIFIC BUCKET GID TYPE ORDER
+
+%type <string_val> ident mangled_ident
+%type <abstract_node> bool_
+ /* TODO 'leaf' is a bad name for something that isn't a leaf... */
+%type <abstract_node> expression comparison logical_expr leaf doc_type
+%type <string_val> id_arg
+%type <value_node> number null_ value string arith_expr id_spec variable
+%type <field_expr_node> field_spec
+
+%destructor { delete $$; } IDENTIFIER STRING FP_MAP_LOOKUP FP_ARRAY_LOOKUP
+%destructor { delete $$; } USER GROUP SCHEME NAMESPACE SPECIFIC BUCKET GID TYPE ORDER
+%destructor { delete $$; } null_ bool_ number string doc_type ident id_arg id_spec
+%destructor { delete $$; } variable mangled_ident field_spec value arith_expr
+%destructor { delete $$; } comparison leaf logical_expr expression
+
+%start entry
+
+%parse-param {DocSelScanner& scanner}
+%parse-param {const BucketIdFactory& bucket_id_factory}
+%parse-param {const DocumentTypeRepo& doc_type_repo}
+%parse-param {std::unique_ptr<Node>& recv_expr}
+
+ /* Generated parser header file verbatim */
+%code requires {
+
+#include "location.hh"
+#include <vespa/document/select/constant.h>
+#include <vespa/document/select/branch.h>
+#include <vespa/document/select/compare.h>
+#include <vespa/document/select/valuenodes.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <memory>
+
+namespace document {
+class BucketIdFactory;
+class DocumentTypeRepo;
+}
+
+namespace document::select {
+class DocSelScanner;
+class Node;
+class Constant;
+class ValueNode;
+}
+
+}
+
+%code {
+
+// Bison has some chunky destructors that trigger inlining warnings. Disable warning
+// for this translation unit, since we can't really do much about the code it generates.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winline"
+
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/select/scanner.h>
+#include <vespa/document/select/constant.h>
+#include <vespa/document/select/branch.h>
+#include <vespa/document/select/compare.h>
+#include <vespa/document/select/doctype.h>
+#include <vespa/document/select/valuenodes.h>
+#include <vespa/document/util/stringutil.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <memory>
+
+using string = vespalib::string;
+
+// Wrap grabbing pointers from sub-rules in a way that nulls out the
+// stored attribute from the Bison stack. Otherwise, exception cleanup
+// in the parser code will attempt to double-delete the pointee.
+// Yes, it's not beautiful, but that's life when you're dealing with raw pointers.
+template <typename T>
+std::unique_ptr<T> steal(T*& ptr) noexcept {
+ std::unique_ptr<T> owned(ptr);
+ ptr = nullptr;
+ return owned;
+}
+
+// yylex tokenization must defer to scanner instance given to parser
+#undef yylex
+#define yylex scanner.yylex
+
+}
+
+%code provides {
+
+// This cute little indirection is to get around the syntax_error constructor
+// being defined as inline and therefore not being available outside the
+// auto-generated parser source file.
+[[noreturn]] void throw_parser_syntax_error(const document::select::DocSelParser::location_type& loc,
+ const std::string& msg);
+
+}
+
+
+%left OR
+%left AND
+%left EQ NE LT GT LE GE GLOB REGEX
+%left PLUS MINUS
+%left MULTIPLY DIVIDE
+%left MODULO /* Matches legacy parser recursive descent precedence */
+%precedence NEG
+%right UNOT
+%left NON_DOT
+%precedence DOT /* Used to give higher precedence to id.foo vs id expressions. Re: "dangling else" problem */
+
+%%
+
+null_
+ : NULL { $$ = new NullValueNode(); }
+ ;
+
+bool_
+ : TRUE { $$ = new Constant(true); }
+ | FALSE { $$ = new Constant(false); }
+ ;
+
+number
+ : INTEGER { $$ = new IntegerValueNode($1, false); }
+ | FLOAT { $$ = new FloatValueNode($1); }
+ ;
+
+string
+ : STRING { {
+ try {
+ $$ = new StringValueNode(StringUtil::unescape(*steal<string>($1)));
+ } catch (const vespalib::IllegalArgumentException& exc) {
+ throw syntax_error(@$, exc.getMessage());
+ }
+ } }
+ ;
+
+doc_type
+ : ident {
+ if (doc_type_repo.getDocumentType(*$1) == nullptr) {
+ throw syntax_error(@$, vespalib::make_string("Document type '%s' not found", $1->c_str()));
+ }
+ $$ = new DocType(*steal<string>($1));
+ }
+ ;
+
+ident
+ : IDENTIFIER { $$ = $1; }
+ | SCHEME { $$ = $1; }
+ | TYPE { $$ = $1; }
+ | NAMESPACE { $$ = $1; }
+ | SPECIFIC { $$ = $1; }
+ | BUCKET { $$ = $1; }
+ | GID { $$ = $1; }
+ | ORDER { $$ = $1; }
+ ;
+
+id_arg
+ : USER { $$ = $1; }
+ | GROUP { $$ = $1; }
+ | SCHEME { $$ = $1; }
+ | NAMESPACE { $$ = $1; }
+ | SPECIFIC { $$ = $1; }
+ | BUCKET { $$ = $1; }
+ | GID { $$ = $1; }
+ | TYPE { $$ = $1; }
+ ;
+
+id_spec
+ : ID %prec NON_DOT { $$ = new IdValueNode(bucket_id_factory, "id", ""); } /* Prefer shifting instead of reducing */
+ | ID "." id_arg { $$ = new IdValueNode(bucket_id_factory, "id", *steal<string>($3)); }
+ | ID "." IDENTIFIER "(" ")" { $$ = new FunctionValueNode(*steal<string>($3), std::make_unique<IdValueNode>(bucket_id_factory, "id", "")); }
+ | ID "." ORDER "(" INTEGER "," INTEGER ")" { $$ = new IdValueNode(bucket_id_factory, "id", *steal<string>($3), $5, $7); }
+ ;
+
+variable
+ : "$" ident { $$ = new VariableValueNode(*steal<string>($2)); }
+ ;
+
+ /* FIXME this is a horrible leftover of post-parsed fieldpath processing */
+ /* At least we verify structural integrity at initial parse-time now... */
+ /* Post-parsing should be replaced with an actual parse-time built AST! */
+mangled_ident
+ : ident { $$ = $1; }
+ | mangled_ident FP_MAP_LOOKUP { $1->append(*steal<string>($2)); $$ = $1; }
+ | mangled_ident FP_ARRAY_LOOKUP { $1->append(*steal<string>($2)); $$ = $1; }
+ ;
+
+field_spec
+ : ident "." mangled_ident {
+ if (doc_type_repo.getDocumentType(*$1) == nullptr) {
+ throw syntax_error(@$, vespalib::make_string("Document type '%s' not found", $1->c_str()));
+ }
+ $$ = new FieldExprNode(std::make_unique<FieldExprNode>(*steal<string>($1)), *steal<string>($3));
+ }
+ | field_spec "." mangled_ident { $$ = new FieldExprNode(steal<FieldExprNode>($1), *steal<string>($3)); }
+ ;
+
+value
+ : null_ { $$ = $1; }
+ | string { $$ = $1; }
+ | id_spec { $$ = $1; }
+ | variable { $$ = $1; }
+ | NOW_FUNC { $$ = new CurrentTimeValueNode(); }
+ ;
+
+arith_expr
+ : value { $$ = $1; }
+ | number { $$ = $1; }
+ /* JavaCC and legacy parsers don't support unary plus/minus for _expressions_, just for numbers. So we have to fudge this a bit. */
+ | "-" number %prec NEG {
+ if (dynamic_cast<IntegerValueNode*>($2) != nullptr) {
+ $$ = new IntegerValueNode(- static_cast<IntegerValueNode&>(*steal<ValueNode>($2)).getValue(), false);
+ } else {
+ $$ = new FloatValueNode(- dynamic_cast<FloatValueNode&>(*steal<ValueNode>($2)).getValue());
+ }
+ }
+ | "+" number %prec NEG { $$ = $2; }
+ | field_spec { $$ = steal<FieldExprNode>($1)->convert_to_field_value().release(); }
+ | field_spec "(" ")" { $$ = steal<FieldExprNode>($1)->convert_to_function_call().release(); }
+ | arith_expr "+" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "+", steal<ValueNode>($3)); }
+ | arith_expr "-" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "-", steal<ValueNode>($3)); }
+ | arith_expr "*" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "*", steal<ValueNode>($3)); }
+ | arith_expr "/" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "/", steal<ValueNode>($3)); }
+ | arith_expr "%" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "%", steal<ValueNode>($3)); }
+ | "(" arith_expr ")" { $$ = $2; $$->setParentheses(); }
+ | arith_expr "." IDENTIFIER "(" ")" { $$ = new FunctionValueNode(*steal<string>($3), steal<ValueNode>($1)); } /* FIXME shift/reduce conflict */
+ ;
+
+comparison
+ : arith_expr EQ arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::EQ, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr NE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::NE, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr GE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::GEQ, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr LE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::LEQ, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr GT arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::GT, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr LT arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::LT, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr GLOB arith_expr { $$ = new Compare(steal<ValueNode>($1), GlobOperator::GLOB, steal<ValueNode>($3), bucket_id_factory); }
+ | arith_expr REGEX arith_expr { $$ = new Compare(steal<ValueNode>($1), RegexOperator::REGEX, steal<ValueNode>($3), bucket_id_factory); }
+ ;
+
+leaf
+ : bool_ { $$ = $1; }
+ | comparison { $$ = $1; }
+ | doc_type { $$ = $1; }
+ | arith_expr { /* Actually field_spec, see comment below..! */
+ // Grammar-wise, we _do not_ accept arbitrary arith_exprs at this level. But the
+ // selection grammar as it stands is otherwise ambiguous with LR(1) parsing.
+ // More specifically, if we used field_spec instead of arith_expr here, the parser
+ // state machine cannot decide what to do if it has processed the sequence '(' field_spec
+ // and sees the next token of ')'. Since both logical_expr and arith_expr allows for
+ // parenthesis expression recursion, the reduce step may produce either of these and
+ // is therefore technically undefined. By using arith_expr instead for this rule, all
+ // '(' field_spec ')' sequences result in an arith_expr rule match and the reduce/reduce
+ // conflict goes away. We can then do a sneaky "run-time" type check to ensure we only
+ // get the expected node from the rule.
+ // It's not pretty, but it avoids an undefined grammar (which is much less pretty!).
+ auto node = steal<ValueNode>($1);
+ if (dynamic_cast<FieldValueNode*>(node.get()) == nullptr) {
+ throw syntax_error(@$, "expected field spec, doctype, bool or comparison");
+ }
+ // Implicit rewrite to non-null comparison node
+ $$ = new Compare(std::move(node),
+ FunctionOperator::NE,
+ std::make_unique<NullValueNode>(),
+ bucket_id_factory);
+ }
+ ;
+
+logical_expr
+ : leaf { $$ = $1; }
+ | logical_expr AND logical_expr { $$ = new And(steal<Node>($1), steal<Node>($3)); }
+ | logical_expr OR logical_expr { $$ = new Or(steal<Node>($1), steal<Node>($3)); }
+ | NOT logical_expr %prec UNOT { $$ = new Not(steal<Node>($2)); }
+ | "(" logical_expr ")" { $$ = $2; $$->setParentheses(); }
+ ;
+
+expression
+ : logical_expr { $$ = $1; }
+ ;
+
+entry
+ : expression END { recv_expr = steal<Node>($1); }
+ | END { recv_expr = std::make_unique<Constant>(true); }
+ ;
+
+%%
+
+void document::select::DocSelParser::error(const location_type& l, const std::string& what) {
+ throw syntax_error(l, what);
+}
+
+void throw_parser_syntax_error(const document::select::DocSelParser::location_type& loc, const std::string& msg) {
+ throw document::select::DocSelParser::syntax_error(loc, msg);
+}
+
+#pragma GCC diagnostic pop
diff --git a/document/src/vespa/document/select/node.h b/document/src/vespa/document/select/node.h
index eab46e824c8..83e2ea3542d 100644
--- a/document/src/vespa/document/select/node.h
+++ b/document/src/vespa/document/select/node.h
@@ -5,9 +5,8 @@
*
* @brief Base class for all nodes in the document selection tree.
*
- * @author H�kon Humberset
+ * @author HÃ¥kon Humberset
* @date 2005-06-07
- * @version $Id$
*/
#pragma once
diff --git a/document/src/vespa/document/select/orderingselector.cpp b/document/src/vespa/document/select/orderingselector.cpp
index bf8e96c0533..836647aab26 100644
--- a/document/src/vespa/document/select/orderingselector.cpp
+++ b/document/src/vespa/document/select/orderingselector.cpp
@@ -137,7 +137,6 @@ namespace {
void visitArithmeticValueNode(const ArithmeticValueNode &) override {}
void visitFunctionValueNode(const FunctionValueNode &) override {}
void visitIdValueNode(const IdValueNode &) override {}
- void visitSearchColumnValueNode(const SearchColumnValueNode &) override {}
void visitFieldValueNode(const FieldValueNode &) override {}
void visitFloatValueNode(const FloatValueNode &) override {}
void visitVariableValueNode(const VariableValueNode &) override {}
diff --git a/document/src/vespa/document/select/parse_utils.cpp b/document/src/vespa/document/select/parse_utils.cpp
new file mode 100644
index 00000000000..ab4ce2f6d4a
--- /dev/null
+++ b/document/src/vespa/document/select/parse_utils.cpp
@@ -0,0 +1,37 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "parse_utils.h"
+#include <boost/spirit/include/qi.hpp>
+
+namespace document::select::util {
+
+namespace qi = boost::spirit::qi;
+
+// TODO replace use of Spirit.Qi with std::from_string when available.
+// Note: these parsers are all pure, reentrant and without locking.
+bool parse_hex_i64(const char* str, size_t len, int64_t& out) {
+ const char* iter = str;
+ const char* end = str + len;
+ // Legacy parser parses hex numbers as u64 rather than i64 (then implicitly
+ // converts), so we do the same thing here to avoid change of semantics.
+ using u64_hex_parser = qi::uint_parser<uint64_t, 16, 1, 16>;
+ u64_hex_parser u64_hex;
+ uint64_t tmp = 0;
+ const bool ok = qi::parse(iter, end, u64_hex, tmp);
+ out = static_cast<int64_t>(tmp);
+ return (ok && (iter == end));
+}
+bool parse_i64(const char* str, size_t len, int64_t& out) {
+ const char* iter = str;
+ const char* end = str + len;
+ const bool ok = qi::parse(iter, end, qi::long_long, out);
+ return (ok && (iter == end));
+}
+bool parse_double(const char* str, size_t len, double& out) {
+ const char* iter = str;
+ const char* end = str + len;
+ const bool ok = qi::parse(iter, end, qi::double_, out);
+ return (ok && (iter == end));
+}
+
+}
diff --git a/document/src/vespa/document/select/parse_utils.h b/document/src/vespa/document/select/parse_utils.h
new file mode 100644
index 00000000000..38c36dfe94a
--- /dev/null
+++ b/document/src/vespa/document/select/parse_utils.h
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+
+namespace document::select::util {
+
+// Fast, locale-independent numeric parse helpers for Flex lexing.
+
+// For all parse_* functions, returns true if parsing is successful. False otherwise.
+// Value of `out` is undefined if return value is false.
+bool parse_hex_i64(const char* str, size_t len, int64_t& out);
+bool parse_i64(const char* str, size_t len, int64_t& out);
+bool parse_double(const char* str, size_t len, double& out);
+
+} \ No newline at end of file
diff --git a/document/src/vespa/document/select/parser.cpp b/document/src/vespa/document/select/parser.cpp
index ceaf0b0c438..9f015409011 100644
--- a/document/src/vespa/document/select/parser.cpp
+++ b/document/src/vespa/document/select/parser.cpp
@@ -1,1493 +1,33 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
#include "parser.h"
-#include "branch.h"
-#include "compare.h"
-#include "constant.h"
-#include "operator.h"
-#include "doctype.h"
-#include "valuenode.h"
-#include "simpleparser.h"
-
-#include <vespa/document/repo/documenttyperepo.h>
+#include "scanner.h"
#include <vespa/document/base/exceptions.h>
#include <vespa/document/util/stringutil.h>
-#include <vespa/vespalib/stllike/asciistream.h>
-#include <vespa/vespalib/locale/c.h>
-#include <boost/spirit/include/classic_chset.hpp>
-#include <boost/spirit/include/classic_core.hpp>
-#include <boost/spirit/include/classic_escape_char.hpp>
-#include <boost/spirit/include/classic_grammar_def.hpp>
-#include <boost/spirit/include/classic_parse_tree.hpp>
-#include <boost/spirit/include/classic_tree_to_xml.hpp>
-#include <iostream>
-#include <map>
+#include <vespa/vespalib/util/stringfmt.h>
#include <sstream>
-using boost::spirit::classic::tree_node;
-using document::DocumentTypeRepo;
-using std::unique_ptr;
-using std::cerr;
-using std::endl;
-using std::istringstream;
-using std::ostringstream;
-using vespalib::IllegalStateException;
-
-/*
- * This cannot be part of a plugin. boost contains constructs causing
- * compiler to generate calls to atexit().
- */
-
-#define parse_assert(a)
-
-namespace document {
-namespace select {
-
-VESPA_IMPLEMENT_EXCEPTION(ParsingFailedException, vespalib::Exception);
-
-Parser::Parser(const DocumentTypeRepo& repo,
- const BucketIdFactory& bucketIdFactory)
- : _repo(repo),
- _bucketIdFactory(bucketIdFactory)
-{
-}
-
-namespace {
-
-/**
- * Defines the grammar for the document selection text format.
- */
-struct DocSelectionGrammar
- : public boost::spirit::classic::grammar<DocSelectionGrammar>
-{
- /** Node identifiers (value 0 should not be used) */
- enum ids { id_nil=1, id_bool, id_number, id_string,
- id_doctype, id_fieldname, id_function, id_idarg, id_searchcolumnarg,
- id_operator, id_idspec, id_searchcolumnspec, id_fieldspec, id_value,
- id_valuefuncadd, id_valuefuncmul, id_valuefuncmod,
- id_valuegroup, id_arithmvalue,
- id_comparison, id_leaf, id_not, id_and,
- id_or, id_group, id_order, id_expression, id_variable };
-
- const DocumentTypeRepo &_repo;
- const BucketIdFactory& _bucketIdFactory;
-
- DocSelectionGrammar(const DocumentTypeRepo& repo,
- const BucketIdFactory& bucketIdFactory)
- : _repo(repo),
- _bucketIdFactory(bucketIdFactory) {}
-
- const BucketIdFactory& getBucketIdFactory() const
- { return _bucketIdFactory; }
-
- /** Grammar base types. To be able to retrieve different grammars. */
- template <typename Scanner>
- struct gram_base {
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_nil> > rule_nil;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_bool> > rule_bool;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_number> > rule_number;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_string> > rule_string;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_doctype> > rule_doctype;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_fieldname> > rule_fieldname;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_function> > rule_function;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_idarg> > rule_idarg;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_searchcolumnarg> > rule_searchcolumnarg;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_operator> > rule_operator;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_idspec> > rule_idspec;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_searchcolumnspec> > rule_searchcolumnspec;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_fieldspec> > rule_fieldspec;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_value> > rule_value;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_valuefuncadd> > rule_valuefuncadd;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_valuefuncmul> > rule_valuefuncmul;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_valuefuncmod> > rule_valuefuncmod;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_valuegroup> > rule_valuegroup;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_arithmvalue> > rule_arithmvalue;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_comparison> > rule_comparison;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_leaf> > rule_leaf;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_not> > rule_not;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_and> > rule_and;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_or> > rule_or;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_group> > rule_group;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_order> > rule_order;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_expression> > rule_expression;
- typedef typename boost::spirit::classic::rule<Scanner,
- boost::spirit::classic::parser_tag<id_variable> > rule_variable;
- typedef boost::spirit::classic::grammar_def<rule_expression,
- rule_leaf,
- rule_arithmvalue> type;
- };
-
- template <typename Scanner>
- struct definition : gram_base<Scanner>::type
- {
- typename gram_base<Scanner>::rule_nil _nil;
- typename gram_base<Scanner>::rule_bool _bool;
- typename gram_base<Scanner>::rule_number _number;
- typename gram_base<Scanner>::rule_string _string;
- typename gram_base<Scanner>::rule_doctype _doctype;
- typename gram_base<Scanner>::rule_fieldname _fieldname;
- typename gram_base<Scanner>::rule_function _function;
- typename gram_base<Scanner>::rule_idarg _idarg;
- typename gram_base<Scanner>::rule_searchcolumnarg _searchcolumnarg;
- typename gram_base<Scanner>::rule_operator _operator;
- typename gram_base<Scanner>::rule_idspec _idspec;
- typename gram_base<Scanner>::rule_searchcolumnspec _searchcolumnspec;
- typename gram_base<Scanner>::rule_fieldspec _fieldspec;
- typename gram_base<Scanner>::rule_value _value;
- typename gram_base<Scanner>::rule_valuefuncadd _valuefuncadd;
- typename gram_base<Scanner>::rule_valuefuncmul _valuefuncmul;
- typename gram_base<Scanner>::rule_valuefuncmod _valuefuncmod;
- typename gram_base<Scanner>::rule_valuegroup _valuegroup;
- typename gram_base<Scanner>::rule_arithmvalue _arithmvalue;
- typename gram_base<Scanner>::rule_comparison _comparison;
- typename gram_base<Scanner>::rule_leaf _leaf;
- typename gram_base<Scanner>::rule_not _not;
- typename gram_base<Scanner>::rule_and _and;
- typename gram_base<Scanner>::rule_or _or;
- typename gram_base<Scanner>::rule_group _group;
- typename gram_base<Scanner>::rule_order _order;
- typename gram_base<Scanner>::rule_expression _expression;
- typename gram_base<Scanner>::rule_variable _variable;
+namespace document::select {
- definition(const DocSelectionGrammar&)
- : _nil(),
- _bool(),
- _number(),
- _string(),
- _doctype(),
- _fieldname(),
- _function(),
- _idarg(),
- _operator(),
- _idspec(),
- _searchcolumnspec(),
- _fieldspec(),
- _value(),
- _valuefuncadd(),
- _valuefuncmul(),
- _valuefuncmod(),
- _valuegroup(),
- _arithmvalue(),
- _comparison(),
- _leaf(),
- _not(),
- _and(),
- _or(),
- _group(),
- _order(),
- _expression(),
- _variable()
- {
- using namespace boost::spirit::classic;
+std::unique_ptr<Node> Parser::parse(const std::string& str) const {
+ try {
+ std::istringstream ss(str);
+ DocSelScanner scanner(&ss);
- boost::spirit::classic::uint_parser<uint64_t, 16, 1, -1> hexvalue;
-
- // Initialize primitives
- _nil = lexeme_d[ as_lower_d["null"] ];
- _bool = lexeme_d[ as_lower_d["true"] | as_lower_d["false"] ];
- _number = lexeme_d[ str_p("0x") >> hexvalue ] | lexeme_d[ real_p ];
- _string = ( lexeme_d[
- ( no_node_d[ ch_p('"') ] >>
- token_node_d[ *( ~chset<>("\\\"\x00-\x1f\x7f-\xff") |
- ( '\\' >> ( ch_p('\\') | 't' | 'n' | 'f' | 'r' | '"' |
- (ch_p('x') >> xdigit_p >> xdigit_p) ) ) ) ] >>
- no_node_d[ ch_p('"') ] ) |
- ( no_node_d[ ch_p('\'') ] >>
- token_node_d[ *( ~chset<>("\\'\x00-\x1f\x7f-\xff") |
- ( '\\' >> ( ch_p('\\') | 't' | 'n' | 'f' | 'r' | '\'' |
- (ch_p('x') >> xdigit_p >> xdigit_p) ) ) ) ] >>
- no_node_d[ ch_p('\'') ] )
- ] );
- _doctype = lexeme_d[ token_node_d[ chset<>("_A-Za-z")
- >> *(chset<>("_A-Za-z0-9")) ]];
- _fieldname = lexeme_d[ token_node_d[chset<>("_A-Za-z")
- >> *(chset<>("_A-Za-z0-9{}[]$"))
- ]];
- _function = lexeme_d[ token_node_d[ chset<>("A-Za-z")
- >> *(chset<>("A-Za-z0-9")) ]
- >> no_node_d[ str_p("()") ] ];
-
- _order = as_lower_d["order"]
- >> no_node_d[ ch_p('(') ]
- >> _number
- >> no_node_d[ ch_p(',') ]
- >> _number
- >> no_node_d[ ch_p(')') ];
-
- _idarg = (as_lower_d[ "scheme"] | as_lower_d[ "namespace"] |
- as_lower_d[ "specific" ] | as_lower_d[ "user" ] |
- as_lower_d[ "group" ] | as_lower_d[ "bucket" ] |
- as_lower_d[ "gid" ] | as_lower_d["type"] | _order);
-
- _searchcolumnarg = lexeme_d[ token_node_d[ *(chset<>("_A-Za-z0-9")) ]];
- _operator = (str_p(">=") | ">" | "==" | "=~" | "="
- | "<=" | "<" | "!=");
- // Derived
- _idspec = as_lower_d["id"]
- >> !(no_node_d[ ch_p('.') ] >> _idarg);
- _searchcolumnspec = as_lower_d["searchcolumn"]
- >> !(no_node_d[ ch_p('.') ] >> _searchcolumnarg);
- _fieldspec = _doctype
- >> +( no_node_d[ ch_p('.') ] >> (_function | _fieldname));
- _variable = lexeme_d[ token_node_d[chset<>("$")
- >> *(chset<>("A-Za-z0-9"))
- ]];
- _value = (_valuegroup | _function | _nil | _number | _string
- | _idspec | _searchcolumnspec | _fieldspec | _variable)
- >> *(no_node_d[ ch_p('.') ] >> _function);
- _valuefuncmod = (_valuegroup | _value)
- >> +( ch_p('%')
- >> (_valuegroup | _value) );
- _valuefuncmul = (_valuefuncmod | _valuegroup | _value)
- >> +( (ch_p('*') | ch_p('/'))
- >> (_valuefuncmod | _valuegroup | _value));
- _valuefuncadd
- = (_valuefuncmul | _valuefuncmod | _valuegroup | _value)
- >> +((ch_p('+') | ch_p('-'))
- >> (_valuefuncmul | _valuefuncmod | _valuegroup |
- _value));
- _valuegroup = no_node_d[ ch_p('(') ] >> _arithmvalue
- >> no_node_d[ ch_p(')') ]
- >> *(no_node_d[ ch_p('.') ] >> _function);
- _arithmvalue = (_valuefuncadd | _valuefuncmul | _valuefuncmod
- | _valuegroup | _value);
- _comparison = _arithmvalue >> _operator >> _arithmvalue;
- _leaf = _bool | _comparison | _fieldspec | _doctype;
-
- _not = (as_lower_d["not"] >> _group)
- | (lexeme_d[ as_lower_d["not"] >> no_node_d[ space_p ] ] >> _leaf);
- _and = (_not | _group | _leaf)
- >> as_lower_d["and"] >> (_and | _not | _group | _leaf);
- _or = (_and | _not | _group | _leaf)
- >> as_lower_d["or"] >> (_or | _and | _not | _group | _leaf);
- _group = no_node_d[ ch_p('(') ]
- >> (_or | _and | _not | _group | _leaf)
- >> no_node_d[ ch_p(')') ];
-
- _expression = !(_or | _and | _not | _group | _leaf) >> end_p;
-
- this->start_parsers(_expression, _leaf, _arithmvalue);
+ std::unique_ptr<Node> root;
+ DocSelParser parser(scanner, _bucket_id_factory, _doc_type_repo, root);
+ if (parser.parse() != 0) {
+ throw ParsingFailedException(
+ vespalib::make_string("Unknown parse failure while parsing selection '%s'", str.c_str()),
+ VESPA_STRLOC);
}
- };
-
-};
-
-template<typename T>
-std::unique_ptr<Node>
-parseTree(DocSelectionGrammar& grammar, tree_node<T>& root) {
- return parseNode(grammar, root);
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseNode(DocSelectionGrammar& grammar, tree_node<T>& node) {
- switch (node.value.id().to_long()) {
- case DocSelectionGrammar::id_or:
- return parseOr(grammar, node);
- case DocSelectionGrammar::id_and:
- return parseAnd(grammar, node);
- case DocSelectionGrammar::id_not:
- return parseNot(grammar, node);
- case DocSelectionGrammar::id_group:
- {
- std::unique_ptr<Node> n(parseNode(grammar, node.children[0]));
- n->setParentheses();
- return n;
- }
- case DocSelectionGrammar::id_leaf:
- case DocSelectionGrammar::id_value:
- parse_assert(node.children.size() == 1);
- return parseNode(grammar, node.children[0]);
- case DocSelectionGrammar::id_expression:
- if (node.children.size() == 1) {
- return parseNode(grammar, node.children[0]);
- }
- parse_assert(node.children.size() == 0);
- return std::unique_ptr<Node>(new Constant("true"));
- case DocSelectionGrammar::id_bool:
- return parseBool(grammar, node);
- case DocSelectionGrammar::id_comparison:
- return parseComparison(grammar, node);
- case DocSelectionGrammar::id_fieldspec:
- return parseFieldSpec(grammar, node);
- case DocSelectionGrammar::id_doctype:
- return parseDocType(grammar, node);
- }
- vespalib::asciistream ost;
- ost << "Received unhandled nodetype "
- << node.value.id().to_long() << " in parseNode()\n";
- throw IllegalStateException(ost.str(), VESPA_STRLOC);
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseOr(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_or);
- parse_assert(node.children.size() == 3);
- vespalib::string op(node.children[1].value.begin(),
- node.children[1].value.end());
- return std::unique_ptr<Node>(new Or(
- parseNode(grammar, node.children[0]),
- parseNode(grammar, node.children[2]),
- op.c_str()));
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseAnd(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_and);
- parse_assert(node.children.size() == 3);
- vespalib::string op(node.children[1].value.begin(),
- node.children[1].value.end());
- return std::unique_ptr<Node>(new And(
- parseNode(grammar, node.children[0]),
- parseNode(grammar, node.children[2]),
- op.c_str()));
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseNot(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_not);
- parse_assert(node.children.size() == 2);
- vespalib::string op(node.children[0].value.begin(),
- node.children[0].value.end());
- return std::unique_ptr<Node>(new Not(
- parseNode(grammar, node.children[1]), op.c_str()));
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseBool(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_bool);
- parse_assert(node.children.size() == 1);
- parse_assert(node.children[0].value.id().to_long() == grammar.id_bool);
- parse_assert(node.children[0].children.size() == 0);
- vespalib::string s(node.children[0].value.begin(), node.children[0].value.end());
- return std::unique_ptr<Node>(new Constant(s));
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseComparison(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_comparison);
- parse_assert(node.children.size() == 3);
- parse_assert(node.children[1].children.size() == 1);
- vespalib::string op(node.children[1].children[0].value.begin(),
- node.children[1].children[0].value.end());
- return std::unique_ptr<Node>(new Compare(
- parseArithmValue(grammar, node.children[0]),
- Operator::get(op),
- parseArithmValue(grammar, node.children[2]),
- grammar.getBucketIdFactory()));
-}
-
-template<typename T>
-std::unique_ptr<Node>
-parseFieldSpec(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_fieldspec);
- return std::unique_ptr<Node>(new Compare(
- parseFieldSpecValue(grammar, node),
- Operator::get("!="),
- std::unique_ptr<ValueNode>(new NullValueNode("null")),
- grammar.getBucketIdFactory()));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseVariable(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_variable);
- vespalib::string varName(node.children[0].value.begin(),
- node.children[0].value.end());
- return std::unique_ptr<ValueNode>(new VariableValueNode(varName.substr(1)));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseGlobValueFunction(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_function);
- vespalib::string varName(node.children[0].value.begin(),
- node.children[0].value.end());
- if (varName == "now") {
- return std::unique_ptr<ValueNode>(new CurrentTimeValueNode);
+ return root;
+ } catch (const DocSelParser::syntax_error& err) {
+ throw ParsingFailedException(
+ vespalib::make_string("%s at column %u when parsing selection '%s'",
+ err.what(), err.location.begin.column, str.c_str()),
+ VESPA_STRLOC);
}
- throw ParsingFailedException("Unexpected function name '" + varName
- + "' found.", VESPA_STRLOC);
}
-template<typename T>
-std::unique_ptr<Node>
-parseDocType(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_doctype);
- parse_assert(node.children.size() == 1);
- parse_assert(node.children[0].value.id().to_long() == grammar.id_doctype);
- parse_assert(node.children[0].children.size() == 0);
- vespalib::string doctype(node.children[0].value.begin(),
- node.children[0].value.end());
- // Verify existance of any version of document
- if (!grammar._repo.getDocumentType(doctype)) {
- throw ParsingFailedException("Document type " + doctype + " not found",
- VESPA_STRLOC);
- }
- return std::unique_ptr<Node>(new DocType(doctype));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-addFunctions(DocSelectionGrammar& grammar, tree_node<T>& node,
- std::unique_ptr<ValueNode> src, uint32_t index)
-{
- (void) grammar;
- while (index < node.children.size()) {
- parse_assert(node.children[index].value.id().to_long()
- == grammar.id_function);
- vespalib::string func(node.children[index].children[0].value.begin(),
- node.children[index].children[0].value.end());
- std::unique_ptr<ValueNode> fnode(new FunctionValueNode(func, std::move(src)));
- src = std::move(fnode);
- ++index;
- }
- return std::move(src);
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseArithmValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- switch (node.value.id().to_long()) {
- case DocSelectionGrammar::id_arithmvalue:
- parse_assert(node.children.size() == 1);
- return parseArithmValue(grammar, node.children[0]);
- case DocSelectionGrammar::id_value:
- return parseValue(grammar, node);
- case DocSelectionGrammar::id_valuegroup:
- return parseValueGroup(grammar, node);
- case DocSelectionGrammar::id_valuefuncadd:
- case DocSelectionGrammar::id_valuefuncmul:
- case DocSelectionGrammar::id_valuefuncmod:
- return parseValueArithmetics(grammar, node);
- }
- vespalib::asciistream ost;
- ost << "Received unhandled nodetype "
- << node.value.id().to_long()
- << " in parseArithmValue()\n";
- throw IllegalStateException(ost.str(), VESPA_STRLOC);
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseValueArithmetics(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.children.size() >= 3 && node.children.size() % 2 == 1);
- std::unique_ptr<ValueNode> lhs(parseArithmValue(grammar, node.children[0]));
- for (unsigned int i = 1; i < node.children.size(); i += 2) {
- vespalib::string op(node.children[i].value.begin(),
- node.children[i].value.end());
- std::unique_ptr<ValueNode> rhs(parseArithmValue(grammar,
- node.children[i + 1]));
- std::unique_ptr<ValueNode> res(
- new ArithmeticValueNode(std::move(lhs), op, std::move(rhs)));
- lhs = std::move(res);
- }
- return lhs;
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseValueGroup(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_valuegroup);
- parse_assert(node.children.size() >= 1);
- std::unique_ptr<ValueNode> result(
- parseArithmValue(grammar, node.children[0]));
- result->setParentheses();
- return addFunctions(grammar, node, std::move(result), 1);
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_value);
- parse_assert(node.children.size() >= 1);
- std::unique_ptr<ValueNode> result;
- switch (node.children[0].value.id().to_long()) {
- case DocSelectionGrammar::id_nil:
- result = parseNilValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_idspec:
- result = parseIdSpecValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_searchcolumnspec:
- result = parseSearchColumnSpecValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_fieldspec:
- result = parseFieldSpecValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_number:
- result = parseNumberValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_string:
- result = parseStringValue(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_valuegroup:
- result = parseValueGroup(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_variable:
- result = parseVariable(grammar, node.children[0]);
- break;
- case DocSelectionGrammar::id_function:
- result = parseGlobValueFunction(grammar, node.children[0]);
- break;
- default:
- vespalib::asciistream ost;
- ost << "Received unhandled nodetype "
- << node.children[0].value.id().to_long()
- << " in parseValue(), from node of type "
- << node.value.id().to_long() << "\n";
- throw IllegalStateException(ost.str(), VESPA_STRLOC);
- }
- return addFunctions(grammar, node, std::move(result), 1);
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseNilValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_nil);
- parse_assert(node.children.size() == 1);
- parse_assert(node.children[0].children.size() == 0);
- vespalib::string op(node.children[0].value.begin(),
- node.children[0].value.end());
- return std::unique_ptr<ValueNode>(new NullValueNode(op));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseIdSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_idspec);
- parse_assert(node.children.size() >= 1);
- parse_assert(node.children[0].children.size() == 0);
- vespalib::string id(node.children[0].value.begin(),
- node.children[0].value.end());
- if (node.children.size() == 1) {
- return std::unique_ptr<ValueNode>(
- new IdValueNode(grammar.getBucketIdFactory(), id, ""));
- }
-
- vespalib::string type;
-
- int widthBits = -1;
- int divisionBits = -1;
-
- if (node.children[1].children[0].value.id().to_long() == grammar.id_order) {
- tree_node<T>& ordernode(node.children[1].children[0]);
- type = vespalib::string(ordernode.children[0].value.begin(),
- ordernode.children[0].value.end());
-
- vespalib::string val = vespalib::string(
- ordernode.children[1].children[0].value.begin(),
- ordernode.children[1].children[0].value.end());
- widthBits = atoi(val.c_str());
-
- val = vespalib::string(ordernode.children[2].children[0].value.begin(),
- ordernode.children[2].children[0].value.end());
- divisionBits = atoi(val.c_str());
- } else {
- type = vespalib::string(node.children[1].children[0].value.begin(),
- node.children[1].children[0].value.end());
- }
-
- return std::unique_ptr<ValueNode>(
- new IdValueNode(grammar.getBucketIdFactory(), id, type,
- widthBits, divisionBits));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseSearchColumnSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_searchcolumnspec);
- parse_assert(node.children.size() == 2);
- parse_assert(node.children[0].children.size() == 0);
- parse_assert(node.children[1].value.id().to_long() == grammar.id_searchcolumnarg);
-
- vespalib::string id(node.children[0].value.begin(),
- node.children[0].value.end());
- parse_assert(node.children.size() == 2);
-
- vespalib::string val = vespalib::string(node.children[1].children[0].value.begin(),
- node.children[1].children[0].value.end());
- return std::unique_ptr<ValueNode>(new SearchColumnValueNode(
- grammar.getBucketIdFactory(), id, atoi(val.c_str())));
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseFieldSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- parse_assert(node.value.id().to_long() == grammar.id_fieldspec);
- parse_assert(node.children.size() >= 2);
- parse_assert(node.children[0].value.id().to_long() == grammar.id_doctype);
- vespalib::string doctype(node.children[0].children[0].value.begin(),
- node.children[0].children[0].value.end());
- // Verify that document type exist at any version
- if (!grammar._repo.getDocumentType(doctype)) {
- throw ParsingFailedException("Document type " + doctype + " not found",
- VESPA_STRLOC);
- }
- std::unique_ptr<ValueNode> value;
- uint32_t iterator = 2;
-
- parse_assert(node.children[1].value.id().to_long() == grammar.id_fieldname);
- vespalib::string field(node.children[1].children[0].value.begin(),
- node.children[1].children[0].value.end());
- while (iterator < node.children.size()
- && node.children[iterator].value.id().to_long() == grammar.id_fieldname)
- {
- field += "." + vespalib::string(
- node.children[iterator].children[0].value.begin(),
- node.children[iterator].children[0].value.end());
- ++iterator;
- }
- value.reset(new FieldValueNode(doctype, field));
-
- for (; iterator<node.children.size(); ++iterator) {
- std::unique_ptr<ValueNode> child(std::move(value));
- vespalib::string function(node.children[iterator].children[0].value.begin(),
- node.children[iterator].children[0].value.end());
- parse_assert(node.children[iterator].value.id().to_long() == grammar.id_function);
- value.reset(new FunctionValueNode(function, std::move(child)));
- }
- return value;
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseNumberValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_number);
- vespalib::string sval;
- int base = 10;
- if (node.children.size() == 2) {
- base = 16;
- sval = vespalib::string(node.children[1].value.begin(),
- node.children[1].value.end());
- parse_assert(node.children[0].value.id().to_long() == grammar.id_number);
- parse_assert(node.children[1].value.id().to_long() == grammar.id_number);
- } else {
- parse_assert(node.children.size() == 1);
- sval = vespalib::string(node.children[0].value.begin(),
- node.children[0].value.end());
- parse_assert(node.children[0].value.id().to_long() == grammar.id_number);
- }
- if (sval.find('.') != vespalib::string::npos) {
- char* endptr;
- double val = vespalib::locale::c::strtod(sval.c_str(), &endptr);
- if (*endptr == '\0') {
- return std::unique_ptr<ValueNode>(new FloatValueNode(val));
- }
- } else {
- char* endptr;
- int64_t val;
- if (base == 16) {
- val = strtoull(sval.c_str(), &endptr, base);
- } else {
- val = strtoll(sval.c_str(), &endptr, base);
- }
- if (*endptr == '\0') {
- return std::unique_ptr<ValueNode>(new IntegerValueNode(val, false));
- }
- }
- vespalib::string error = "'" + sval + "' is not a valid number.";
- throw ParsingFailedException(error, VESPA_STRLOC);
-}
-
-template<typename T>
-std::unique_ptr<ValueNode>
-parseStringValue(DocSelectionGrammar& grammar, tree_node<T>& node) {
- (void) grammar;
- parse_assert(node.value.id().to_long() == grammar.id_string);
- if (node.children.size() == 0) {
- return std::unique_ptr<ValueNode>(new StringValueNode(""));
- }
- parse_assert(node.children.size() == 1);
- parse_assert(node.children[0].value.id().to_long() == grammar.id_string);
- vespalib::string val(node.children[0].value.begin(),
- node.children[0].value.end());
- return std::unique_ptr<ValueNode>(new StringValueNode(StringUtil::unescape(val)));
-}
-
-template<typename Tree>
-void printSpiritTree(std::ostream& out, Tree tree, const vespalib::string& query,
- const DocSelectionGrammar& grammar) {
- using boost::spirit::classic::parser_id;
-
- std::map<parser_id, vespalib::string> names;
- names[parser_id(grammar.id_bool)] = "bool";
- names[parser_id(grammar.id_number)] = "number";
- names[parser_id(grammar.id_string)] = "string";
- names[parser_id(grammar.id_doctype)] = "doctype";
- names[parser_id(grammar.id_fieldname)] = "fieldname";
- names[parser_id(grammar.id_function)] = "function";
- names[parser_id(grammar.id_idarg)] = "idarg";
- names[parser_id(grammar.id_searchcolumnarg)] = "searchcolumnarg";
- names[parser_id(grammar.id_operator)] = "operator";
- names[parser_id(grammar.id_idspec)] = "idspec";
- names[parser_id(grammar.id_searchcolumnspec)] = "searchcolumnspec";
- names[parser_id(grammar.id_fieldspec)] = "fieldspec";
- names[parser_id(grammar.id_value)] = "value";
- names[parser_id(grammar.id_valuefuncadd)] = "valuefuncadd";
- names[parser_id(grammar.id_valuefuncmul)] = "valuefuncmul";
- names[parser_id(grammar.id_valuefuncmod)] = "valuefuncmod";
- names[parser_id(grammar.id_valuegroup)] = "valuegroup";
- names[parser_id(grammar.id_arithmvalue)] = "arithmvalue";
- names[parser_id(grammar.id_comparison)] = "comparison";
- names[parser_id(grammar.id_leaf)] = "leaf";
- names[parser_id(grammar.id_not)] = "not";
- names[parser_id(grammar.id_and)] = "and";
- names[parser_id(grammar.id_or)] = "or";
- names[parser_id(grammar.id_group)] = "group";
- names[parser_id(grammar.id_expression)] = "expression";
- tree_to_xml(out, tree, query.c_str(), names);
-}
-
-template<typename Parser>
-bool testExpr(const DocumentTypeRepo& repo,
- const BucketIdFactory& factory,
- const vespalib::string& expression, const Parser& parser,
- const vespalib::string& result)
-{
- //std::cerr << "Testing expression '" << expression << "'.\n";
- using boost::spirit::classic::space_p;
-
- DocSelectionGrammar grammar(repo, factory);
- boost::spirit::classic::tree_parse_info<> info;
- info = pt_parse(expression.c_str(), parser,
- space_p);
- std::ostringstream ost;
- printSpiritTree(ost, info.trees, expression, grammar);
- if (!info.full) {
- cerr << "Expression '" << expression
- << "' wasn't completely parsed\n"
- << ost.str() << "\n";
- return false;
- }
- vespalib::string httpexpr = expression;
- vespalib::string::size_type index;
- while ((index = httpexpr.find('>')) != vespalib::string::npos) {
- httpexpr = httpexpr.substr(0,index) + "&gt;"
- + httpexpr.substr(index+1);
- }
- vespalib::string fullresult = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
- "<!DOCTYPE parsetree SYSTEM \"parsetree.dtd\">\n"
- "<!-- " + httpexpr + " -->\n" + result;
- //if (ost.str() != fullresult) {
- if (fullresult != ost.str()) {
- cerr << "Parsing expression '" << expression << "', expected\n"
- << fullresult << "\nbut got\n" << ost.str() << "\n";
- return false;
- }
- return true;
-}
-
-bool test(const DocumentTypeRepo& repo,
- const BucketIdFactory& bucketIdFactory)
-{
- //std::cerr << "\n\nTESTING DOCUMENT SELECT PARSER\n\n";
- DocSelectionGrammar grammar(repo, bucketIdFactory);
-
- using boost::spirit::classic::space_p;
-
- // Parser two is the arithmvalue..
- // idspec, fieldspec, number & stringval, + - * / % ()
- testExpr(repo, bucketIdFactory, "3.14", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>3.14</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "-999", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>-999</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "15e4", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>15e4</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "3.4e-4", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>3.4e-4</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "\" Test \"", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"string\">\n"
- " <parsenode rule=\"string\">\n"
- " <value> Test </value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "id", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <value>id</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "id.namespace",
- grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <value>id</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"idarg\">\n"
- " <parsenode rule=\"idarg\">\n"
- " <value>namespace</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "id.hash()", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <value>id</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"function\">\n"
- " <parsenode rule=\"function\">\n"
- " <value>hash</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "id.namespace.hash()", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <parsenode rule=\"idspec\">\n"
- " <value>id</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"idarg\">\n"
- " <parsenode rule=\"idarg\">\n"
- " <value>namespace</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"function\">\n"
- " <parsenode rule=\"function\">\n"
- " <value>hash</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "music.artist", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"fieldspec\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"fieldname\">\n"
- " <parsenode rule=\"fieldname\">\n"
- " <value>artist</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "music.artist.lowercase()", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"fieldspec\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"fieldname\">\n"
- " <parsenode rule=\"fieldname\">\n"
- " <value>artist</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"function\">\n"
- " <parsenode rule=\"function\">\n"
- " <value>lowercase</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "(43)", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuegroup\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>43</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "1 + 2 * 3 - 10 % 2 / 3", grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>1</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <value>+</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>2</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <value>*</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>3</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <value>-</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <parsenode rule=\"valuefuncmod\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>10</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmod\">\n"
- " <value>%</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>2</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <value>/</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>3</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "(43 + 14) / 34",
- grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <parsenode rule=\"valuegroup\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>43</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <value>+</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>14</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <value>/</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>34</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "34 * (3 - 1) % 4",
- grammar.use_parser<2>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>34</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmul\">\n"
- " <value>*</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmod\">\n"
- " <parsenode rule=\"valuegroup\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>3</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncadd\">\n"
- " <value>-</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>1</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"valuefuncmod\">\n"
- " <value>%</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>4</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
-
- // Parser 1 is a leaf. bool, comparison, fieldspec, doctype
- testExpr(repo, bucketIdFactory, "true", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>true</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "false", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>false</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "music.test", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"fieldspec\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"fieldname\">\n"
- " <parsenode rule=\"fieldname\">\n"
- " <value>test</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory, "music", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "music.artist = \"*john*\"", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"comparison\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"fieldspec\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"fieldname\">\n"
- " <parsenode rule=\"fieldname\">\n"
- " <value>artist</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"operator\">\n"
- " <parsenode rule=\"operator\">\n"
- " <value>=</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"string\">\n"
- " <parsenode rule=\"string\">\n"
- " <value>*john*</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "music.length >= 180", grammar.use_parser<1>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"comparison\">\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"fieldspec\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>music</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"fieldname\">\n"
- " <parsenode rule=\"fieldname\">\n"
- " <value>length</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"operator\">\n"
- " <parsenode rule=\"operator\">\n"
- " <value>&gt;=</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"arithmvalue\">\n"
- " <parsenode rule=\"value\">\n"
- " <parsenode rule=\"number\">\n"
- " <parsenode rule=\"number\">\n"
- " <value>180</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
-
- // Parser 0 - The whole expression
- testExpr(repo, bucketIdFactory,
- "true oR nOt false And true", grammar.use_parser<0>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"expression\">\n"
- " <parsenode rule=\"or\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>true</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"or\">\n"
- " <value>oR</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"and\">\n"
- " <parsenode rule=\"not\">\n"
- " <parsenode rule=\"not\">\n"
- " <value>nOt</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>false</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"and\">\n"
- " <value>And</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>true</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "(true oR false) aNd true", grammar.use_parser<0>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"expression\">\n"
- " <parsenode rule=\"and\">\n"
- " <parsenode rule=\"group\">\n"
- " <parsenode rule=\"or\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>true</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"or\">\n"
- " <value>oR</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>false</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"and\">\n"
- " <value>aNd</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"bool\">\n"
- " <parsenode rule=\"bool\">\n"
- " <value>true</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- testExpr(repo, bucketIdFactory,
- "iddoc or not(notand and ornot)", grammar.use_parser<0>(),
- "<parsetree version=\"1.0\">\n"
- " <parsenode rule=\"expression\">\n"
- " <parsenode rule=\"or\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>iddoc</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"or\">\n"
- " <value>or</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"not\">\n"
- " <parsenode rule=\"not\">\n"
- " <value>not</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"group\">\n"
- " <parsenode rule=\"and\">\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>notand</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " <parsenode rule=\"and\">\n"
- " <value>and</value>\n"
- " </parsenode>\n"
- " <parsenode rule=\"leaf\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <parsenode rule=\"doctype\">\n"
- " <value>ornot</value>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- " </parsenode>\n"
- "</parsetree>\n");
- return true;
-}
-
-}
-
-vespalib::Lock Parser::_G_parseLock;
-
-unique_ptr<Node> Parser::parse(const vespalib::stringref & s)
-{
-
- simple::SelectionParser simple(_bucketIdFactory);
- if (simple.parse(s) && simple.getRemaining().empty()) {
- Node::UP tmp(simple.getNode());
- assert(tmp.get() != NULL);
- return tmp;
- } else {
- return fullParse(s);
- }
-}
-
-unique_ptr<Node> Parser::fullParse(const vespalib::stringref & s)
-{
- static bool haveTested = test(_repo, _bucketIdFactory); if (haveTested) {}
- try{
- vespalib::LockGuard guard(_G_parseLock);
- DocSelectionGrammar grammar(_repo, _bucketIdFactory);
- boost::spirit::classic::tree_parse_info<> info
- = pt_parse(&s[0], &s[0]+s.size(),
- grammar.use_parser<0>(), boost::spirit::classic::space_p);
- if (!info.full) {
- vespalib::string unexpected(info.stop);
- unsigned int position = s.size() - unexpected.size();
- if (unexpected.size() > 10) {
- unexpected = unexpected.substr(0,10);
- }
- vespalib::asciistream ost;
- ost << "Unexpected token at position " << position << " ('"
- << unexpected << "') in query '" << s << "',";
- throw ParsingFailedException(ost.str(), VESPA_STRLOC);
- }
- parse_assert(info.trees.size() == 1);
- //printSpiritTree(std::cerr, info.trees, s, grammar);
- return parseTree(grammar, info.trees[0]);
- } catch (ParsingFailedException& e) {
- throw;
- } catch (vespalib::Exception& e) {
- throw ParsingFailedException("Parsing failed. See cause exception.",
- e, VESPA_STRLOC);
- } catch (std::exception& e) {
- cerr << "Parser::parse() internal error: "
- << e.what() << endl;
- throw; // Program will abort when this tries to go out..
- }
- return unique_ptr<Node>();
}
-} // select
-} // document
diff --git a/document/src/vespa/document/select/parser.h b/document/src/vespa/document/select/parser.h
index 4df00d64bf3..35d710298c3 100644
--- a/document/src/vespa/document/select/parser.h
+++ b/document/src/vespa/document/select/parser.h
@@ -1,37 +1,40 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
#pragma once
#include "node.h"
+#include "parsing_failed_exception.h"
#include <vespa/document/bucket/bucketidfactory.h>
-#include <vespa/vespalib/util/exception.h>
-#include <vespa/vespalib/util/sync.h>
-
-namespace document {
-class DocumentTypeRepo;
-
-namespace select {
-
-VESPA_DEFINE_EXCEPTION(ParsingFailedException, vespalib::Exception);
-
+#include <vespa/document/repo/documenttyperepo.h>
+#include <memory>
+#include <string>
+
+namespace document::select {
+
+/**
+ * Document selection parser built around Flex/Bison. O(n) on input size
+ * and non-locking.
+ *
+ * Thread safety: same as a std::vector
+ */
class Parser {
+ const DocumentTypeRepo&_doc_type_repo;
+ const BucketIdFactory& _bucket_id_factory;
public:
- Parser(const DocumentTypeRepo&, const BucketIdFactory& bucketIdFactory);
+ Parser(const DocumentTypeRepo& repo, const BucketIdFactory& bucket_id_factory)
+ : _doc_type_repo(repo),
+ _bucket_id_factory(bucket_id_factory)
+ {}
/**
* Returns a newly allocated AST root node representing the selection
* if parsing is successful. Otherwise, ParsingFailedException will be
* thrown.
+ *
+ * Thread safe, assuming referenced DocumentTypeRepo and BucketIdFactory
+ * instances are immutable.
*/
- std::unique_ptr<Node> parse(const vespalib::stringref& s);
-
-private:
- std::unique_ptr<Node> fullParse(const vespalib::stringref& s);
- static vespalib::Lock _G_parseLock;
- const DocumentTypeRepo& _repo;
- const BucketIdFactory& _bucketIdFactory;
+ std::unique_ptr<Node> parse(const std::string& str) const;
};
-} // select
-} // parser
+}
diff --git a/document/src/vespa/document/select/parsing_failed_exception.cpp b/document/src/vespa/document/select/parsing_failed_exception.cpp
new file mode 100644
index 00000000000..ce02389ed46
--- /dev/null
+++ b/document/src/vespa/document/select/parsing_failed_exception.cpp
@@ -0,0 +1,9 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "parsing_failed_exception.h"
+#include <vespa/document/base/exceptions.h>
+
+namespace document::select {
+
+VESPA_IMPLEMENT_EXCEPTION(ParsingFailedException, vespalib::Exception);
+
+} \ No newline at end of file
diff --git a/document/src/vespa/document/select/parsing_failed_exception.h b/document/src/vespa/document/select/parsing_failed_exception.h
new file mode 100644
index 00000000000..54138a492e8
--- /dev/null
+++ b/document/src/vespa/document/select/parsing_failed_exception.h
@@ -0,0 +1,10 @@
+// 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/util/exception.h>
+
+namespace document::select {
+
+VESPA_DEFINE_EXCEPTION(ParsingFailedException, vespalib::Exception);
+
+}
diff --git a/document/src/vespa/document/select/scanner.h b/document/src/vespa/document/select/scanner.h
new file mode 100644
index 00000000000..5aa9ea1c8d3
--- /dev/null
+++ b/document/src/vespa/document/select/scanner.h
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#if !defined(yyFlexLexerOnce)
+# include <FlexLexer.h>
+#endif
+
+#include "parser.hxx"
+#include "location.hh"
+#include <iosfwd>
+
+namespace document::select {
+
+class DocSelScanner final : yyFlexLexer {
+public:
+ explicit DocSelScanner(std::istream* in) : yyFlexLexer(in) {}
+ ~DocSelScanner() override = default;
+ int yylex(DocSelParser::semantic_type* yylval, DocSelParser::location_type* yyloc);
+};
+
+}
diff --git a/document/src/vespa/document/select/traversingvisitor.cpp b/document/src/vespa/document/select/traversingvisitor.cpp
index b8f34540b29..26de6093ddf 100644
--- a/document/src/vespa/document/select/traversingvisitor.cpp
+++ b/document/src/vespa/document/select/traversingvisitor.cpp
@@ -73,12 +73,6 @@ TraversingVisitor::visitIdValueNode(const IdValueNode &)
void
-TraversingVisitor::visitSearchColumnValueNode(const SearchColumnValueNode &)
-{
-}
-
-
-void
TraversingVisitor::visitFieldValueNode(const FieldValueNode &)
{
}
diff --git a/document/src/vespa/document/select/traversingvisitor.h b/document/src/vespa/document/select/traversingvisitor.h
index 43d10cfcaa2..f8b0377b102 100644
--- a/document/src/vespa/document/select/traversingvisitor.h
+++ b/document/src/vespa/document/select/traversingvisitor.h
@@ -21,7 +21,6 @@ public:
void visitInvalidConstant(const InvalidConstant &) override;
void visitDocumentType(const DocType &) override;
void visitIdValueNode(const IdValueNode &) override;
- void visitSearchColumnValueNode(const SearchColumnValueNode &) override;
void visitFieldValueNode(const FieldValueNode &) override;
void visitFloatValueNode(const FloatValueNode &) override;
void visitVariableValueNode(const VariableValueNode &) override;
diff --git a/document/src/vespa/document/select/valuenodes.cpp b/document/src/vespa/document/select/valuenodes.cpp
index 479896f9124..837ebd873e3 100644
--- a/document/src/vespa/document/select/valuenodes.cpp
+++ b/document/src/vespa/document/select/valuenodes.cpp
@@ -15,7 +15,6 @@
#include <iomanip>
#include <sys/time.h>
-
#include <vespa/log/log.h>
LOG_SETUP(".document.select.valuenode");
@@ -61,10 +60,7 @@ InvalidValueNode::print(std::ostream& out, bool verbose,
if (hadParentheses()) out << ')';
}
-NullValueNode::NullValueNode(const vespalib::stringref & name)
- : _name(name)
-{ }
-
+NullValueNode::NullValueNode() {}
void
NullValueNode::visit(Visitor &visitor) const
@@ -79,7 +75,7 @@ NullValueNode::print(std::ostream& out, bool verbose,
{
(void) verbose; (void) indent;
if (hadParentheses()) out << '(';
- out << _name;
+ out << "null";
if (hadParentheses()) out << ')';
}
@@ -678,88 +674,6 @@ IdValueNode::print(std::ostream& out, bool verbose,
if (hadParentheses()) out << ')';
}
-SearchColumnValueNode::SearchColumnValueNode(
- const BucketIdFactory& bucketIdFactory,
- const vespalib::stringref & name, int numColumns)
- : _bucketIdFactory(bucketIdFactory),
- _id(name),
- _numColumns(numColumns),
- _distribution(std::make_unique<BucketDistribution>(_numColumns, 16))
-{
-}
-
-int64_t
-SearchColumnValueNode::getValue(const BucketId& id) const
-{
- return _distribution->getColumn(id);
-}
-
-
-std::unique_ptr<Value>
-SearchColumnValueNode::getValue(const Context& context) const
-{
- if (context._doc != NULL) {
- return getValue(context._doc->getId());
- } else if (context._docId != NULL) {
- return getValue(*context._docId);
- } else {
- return getValue(context._docUpdate->getId());
- }
-}
-
-
-std::unique_ptr<Value>
-SearchColumnValueNode::getValue(const DocumentId& id) const
-{
- return std::unique_ptr<Value>(new IntegerValue(
- getValue(_bucketIdFactory.getBucketId(id)), false));
-}
-
-
-std::unique_ptr<Value>
-SearchColumnValueNode::traceValue(const Context& context,
- std::ostream &out) const
-{
- if (context._doc != NULL) {
- return traceValue(context._doc->getId(), out);
- } else if (context._docId != NULL) {
- return traceValue(*context._docId, out);
- } else {
- return traceValue(context._docUpdate->getId(), out);
- }
-}
-
-
-std::unique_ptr<Value>
-SearchColumnValueNode::traceValue(const DocumentId& id,
- std::ostream& out) const
-{
- std::unique_ptr<Value> result(new IntegerValue(
- getValue(_bucketIdFactory.getBucketId(id)), false));
- out << "Resolved search column of doc \"" << id << "\" to " << *result
- << "\n";
- return result;
-}
-
-
-void
-SearchColumnValueNode::visit(Visitor &visitor) const
-{
- visitor.visitSearchColumnValueNode(*this);
-}
-
-
-void
-SearchColumnValueNode::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- (void) verbose; (void) indent;
- if (hadParentheses()) out << '(';
- out << _id;
- out << '.' << _numColumns;
- if (hadParentheses()) out << ')';
-}
-
namespace {
union HashUnion {
unsigned char _key[16];
@@ -1176,5 +1090,45 @@ ArithmeticValueNode::print(std::ostream& out, bool verbose,
if (hadParentheses()) out << ')';
}
+FieldExprNode::~FieldExprNode() = default;
+
+std::unique_ptr<FieldValueNode> FieldExprNode::convert_to_field_value() const {
+ const auto& doctype = resolve_doctype();
+ // FIXME deprecate manual post-parsing of field expressions in favor of
+ // actually using the structural parser in the way nature intended.
+ vespalib::string mangled_expression;
+ build_mangled_expression(mangled_expression);
+ return std::make_unique<FieldValueNode>(doctype, mangled_expression);
+}
+
+std::unique_ptr<FunctionValueNode> FieldExprNode::convert_to_function_call() const {
+ // Right hand expr string contains function call, lhs contains field spec on which
+ // the function is to be invoked.
+ if ((_left_expr == nullptr) || (_left_expr->_left_expr == nullptr)) {
+ throw vespalib::IllegalArgumentException(
+ vespalib::make_string("Cannot call function '%s' directly on document type", _right_expr.c_str()));
+ }
+ auto lhs = _left_expr->convert_to_field_value();
+ const auto& function_name = _right_expr;
+ return std::make_unique<FunctionValueNode>(function_name, std::move(lhs));
+}
+
+void FieldExprNode::build_mangled_expression(vespalib::string& dest) const {
+ // Leftmost node is doctype, which should not be emitted as part of mangled expression.
+ if (_left_expr && _left_expr->_left_expr) {
+ _left_expr->build_mangled_expression(dest);
+ dest.push_back('.');
+ }
+ dest.append(_right_expr);
+}
+
+const vespalib::string& FieldExprNode::resolve_doctype() const {
+ const auto* leftmost = this;
+ while (leftmost->_left_expr) {
+ leftmost = leftmost->_left_expr.get();
+ }
+ return leftmost->_right_expr;
+}
+
}
diff --git a/document/src/vespa/document/select/valuenodes.h b/document/src/vespa/document/select/valuenodes.h
index 0464159b85f..bc1ec0e01e8 100644
--- a/document/src/vespa/document/select/valuenodes.h
+++ b/document/src/vespa/document/select/valuenodes.h
@@ -35,9 +35,8 @@ public:
class NullValueNode : public ValueNode
{
- vespalib::string _name;
public:
- NullValueNode(const vespalib::stringref & name);
+ NullValueNode();
std::unique_ptr<Value> getValue(const Context&) const override {
return std::unique_ptr<Value>(new NullValue());
@@ -48,7 +47,7 @@ public:
void visit(Visitor& visitor) const override;
ValueNode::UP clone() const override {
- return wrapParens(new NullValueNode(_name));
+ return wrapParens(new NullValueNode());
}
};
@@ -56,7 +55,7 @@ class StringValueNode : public ValueNode
{
vespalib::string _value;
public:
- StringValueNode(const vespalib::stringref & val);
+ explicit StringValueNode(const vespalib::stringref & val);
const vespalib::string& getValue() const { return _value; }
@@ -115,6 +114,7 @@ class VariableValueNode : public ValueNode
{
vespalib::string _value;
public:
+ // TODO stringref
VariableValueNode(const vespalib::string & variableName) : _value(variableName) {}
const vespalib::string& getVariableName() const { return _value; }
@@ -183,6 +183,59 @@ private:
void initFieldPath(const DocumentType&) const;
};
+class FunctionValueNode;
+
+// Only used by the parser to build a partial field expression. Never part of
+// an AST tree returned to the caller.
+class FieldExprNode final : public ValueNode {
+ std::unique_ptr<FieldExprNode> _left_expr;
+ vespalib::string _right_expr;
+public:
+ explicit FieldExprNode(const vespalib::string& doctype) : _left_expr(), _right_expr(doctype) {}
+ FieldExprNode(std::unique_ptr<FieldExprNode> left_expr, vespalib::stringref right_expr)
+ : _left_expr(std::move(left_expr)), _right_expr(right_expr)
+ {}
+ FieldExprNode(const FieldExprNode &) = delete;
+ FieldExprNode & operator = (const FieldExprNode &) = delete;
+ FieldExprNode(FieldExprNode &&) = default;
+ FieldExprNode & operator = (FieldExprNode &&) = default;
+ ~FieldExprNode();
+
+ std::unique_ptr<FieldValueNode> convert_to_field_value() const;
+ std::unique_ptr<FunctionValueNode> convert_to_function_call() const;
+private:
+ void build_mangled_expression(vespalib::string& dest) const;
+ const vespalib::string& resolve_doctype() const;
+
+ // These are not used, can just return dummy values.
+ std::unique_ptr<Value> getValue(const Context& context) const override {
+ (void) context;
+ return std::unique_ptr<Value>();
+ }
+ std::unique_ptr<Value> traceValue(const Context &context, std::ostream& out) const override {
+ (void) context;
+ (void) out;
+ return std::unique_ptr<Value>();
+ }
+ void print(std::ostream& out, bool verbose, const std::string& indent) const override {
+ (void) out;
+ (void) verbose;
+ (void) indent;
+ }
+ void visit(Visitor& visitor) const override {
+ (void) visitor;
+ }
+
+ ValueNode::UP clone() const override {
+ if (_left_expr) {
+ return wrapParens(new FieldExprNode(std::unique_ptr<FieldExprNode>(
+ static_cast<FieldExprNode*>(_left_expr->clone().release())), _right_expr));
+ } else {
+ return wrapParens(new FieldExprNode(_right_expr));
+ }
+ }
+};
+
class IdValueNode : public ValueNode
{
public:
@@ -222,35 +275,6 @@ private:
int _divisionBits;
};
-class SearchColumnValueNode : public ValueNode
-{
-public:
- SearchColumnValueNode(const BucketIdFactory& bucketIdFactory,
- const vespalib::stringref & name,
- int numColumns);
-
- int getColumns() { return _numColumns; }
-
- std::unique_ptr<Value> getValue(const Context& context) const override;
- std::unique_ptr<Value> getValue(const DocumentId& id) const;
- std::unique_ptr<Value> traceValue(const Context& context, std::ostream &out) const override;
- std::unique_ptr<Value> traceValue(const DocumentId& val, std::ostream& out) const;
-
- int64_t getValue(const BucketId& bucketId) const;
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- void visit(Visitor& visitor) const override;
-
- ValueNode::UP clone() const override {
- return wrapParens(new SearchColumnValueNode(_bucketIdFactory, _id, _numColumns));
-}
-
-private:
- const BucketIdFactory& _bucketIdFactory;
- vespalib::string _id;
- int _numColumns;
- std::unique_ptr<BucketDistribution> _distribution;
-};
-
class FunctionValueNode : public ValueNode
{
public:
diff --git a/document/src/vespa/document/select/visitor.h b/document/src/vespa/document/select/visitor.h
index c89f0f24a6f..762d47c7c35 100644
--- a/document/src/vespa/document/select/visitor.h
+++ b/document/src/vespa/document/select/visitor.h
@@ -70,9 +70,6 @@ public:
visitIdValueNode(const IdValueNode &) = 0;
virtual void
- visitSearchColumnValueNode(const SearchColumnValueNode &) = 0;
-
- virtual void
visitFieldValueNode(const FieldValueNode &) = 0;
virtual void
diff --git a/documentapi/pom.xml b/documentapi/pom.xml
index c94337e8873..3781dfd93ac 100644
--- a/documentapi/pom.xml
+++ b/documentapi/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>documentapi</artifactId>
<packaging>container-plugin</packaging>
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp
index 96408e9a204..62747309c13 100644
--- a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp
+++ b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp
@@ -13,6 +13,8 @@ GetBucketListMessage::GetBucketListMessage(const document::BucketId &bucketId) :
{
}
+GetBucketListMessage::~GetBucketListMessage() = default;
+
DocumentReply::UP
GetBucketListMessage::doCreateReply() const
{
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h
index 8b49afd6672..e89020bcf37 100644
--- a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h
+++ b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h
@@ -24,6 +24,8 @@ public:
*/
GetBucketListMessage(const document::BucketId &bucketId);
+ ~GetBucketListMessage();
+
/**
* Returns the bucket whose list to retrieve.
*
diff --git a/documentgen-test/pom.xml b/documentgen-test/pom.xml
index 53b49cf1473..d009ef3d592 100644
--- a/documentgen-test/pom.xml
+++ b/documentgen-test/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>documentgen-test</artifactId>
<packaging>jar</packaging>
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index 89e8a72e330..d107ebfed40 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -24,6 +24,7 @@ vespa_define_module(
src/tests/eval/value_cache
src/tests/eval/value_type
src/tests/tensor/dense_dot_product_function
+ src/tests/tensor/dense_xw_product_function
src/tests/tensor/dense_tensor_address_combiner
src/tests/tensor/dense_tensor_builder
src/tests/tensor/dense_tensor_function_compiler
diff --git a/eval/src/apps/tensor_conformance/generate.cpp b/eval/src/apps/tensor_conformance/generate.cpp
index 0aba5276ace..f70c472cbcd 100644
--- a/eval/src/apps/tensor_conformance/generate.cpp
+++ b/eval/src/apps/tensor_conformance/generate.cpp
@@ -169,6 +169,16 @@ void generate_dot_product(TestBuilder &dst) {
//-----------------------------------------------------------------------------
+void generate_xw_product(TestBuilder &dst) {
+ auto matrix = spec({x(2),y(3)}, Seq({ 3, 5, 7, 11, 13, 17 }));
+ dst.add("reduce(a*b,sum,x)", {{"a", spec(x(2), Seq({ 1, 2 }))}, {"b", matrix}},
+ spec(y(3), Seq({(1*3+2*11),(1*5+2*13),(1*7+2*17)})));
+ dst.add("reduce(a*b,sum,y)", {{"a", spec(y(3), Seq({ 1, 2, 3 }))}, {"b", matrix}},
+ spec(x(2), Seq({(1*3+2*5+3*7),(1*11+2*13+3*17)})));
+}
+
+//-----------------------------------------------------------------------------
+
void generate_tensor_concat(TestBuilder &dst) {
dst.add("concat(a,b,x)", {{"a", spec(10.0)}, {"b", spec(20.0)}}, spec(x(2), Seq({10.0, 20.0})));
dst.add("concat(a,b,x)", {{"a", spec(x(1), Seq({10.0}))}, {"b", spec(20.0)}}, spec(x(2), Seq({10.0, 20.0})));
@@ -218,6 +228,7 @@ Generator::generate(TestBuilder &dst)
generate_tensor_map(dst);
generate_tensor_join(dst);
generate_dot_product(dst);
+ generate_xw_product(dst);
generate_tensor_concat(dst);
generate_tensor_rename(dst);
generate_tensor_lambda(dst);
diff --git a/eval/src/apps/tensor_conformance/test_spec.json b/eval/src/apps/tensor_conformance/test_spec.json
index 24edc9a7ac7..513d5e8e902 100644
--- a/eval/src/apps/tensor_conformance/test_spec.json
+++ b/eval/src/apps/tensor_conformance/test_spec.json
@@ -1225,6 +1225,8 @@
{"expression":"reduce(a*b,sum)","inputs":{"a":"0x0201017803400000000000000040080000000000004014000000000000","b":"0x0201017803401C0000000000004026000000000000402A000000000000"},"result":{"expect":"0x0200405C000000000000"}}
{"expression":"reduce(a*b,sum)","inputs":{"a":"0x020101780240000000000000004008000000000000","b":"0x0201017803401C0000000000004026000000000000402A000000000000"},"result":{"expect":"0x02004047800000000000"}}
{"expression":"reduce(a*b,sum)","inputs":{"a":"0x0201017803400000000000000040080000000000004014000000000000","b":"0x0201017802401C0000000000004026000000000000"},"result":{"expect":"0x02004047800000000000"}}
+{"expression":"reduce(a*b,sum,x)","inputs":{"a":"0x02010178023FF00000000000004000000000000000","b":"0x020201780201790340080000000000004014000000000000401C0000000000004026000000000000402A0000000000004031000000000000"},"result":{"expect":"0x02010179034039000000000000403F0000000000004044800000000000"}}
+{"expression":"reduce(a*b,sum,y)","inputs":{"a":"0x02010179033FF000000000000040000000000000004008000000000000","b":"0x020201780201790340080000000000004014000000000000401C0000000000004026000000000000402A0000000000004031000000000000"},"result":{"expect":"0x020101780240410000000000004056000000000000"}}
{"expression":"concat(a,b,x)","inputs":{"a":"0x02004024000000000000","b":"0x02004034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}}
{"expression":"concat(a,b,x)","inputs":{"a":"0x02010178014024000000000000","b":"0x02004034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}}
{"expression":"concat(a,b,x)","inputs":{"a":"0x02004024000000000000","b":"0x02010178014034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}}
@@ -1242,4 +1244,4 @@
{"expression":"tensor(x[10])(x+1)","inputs":{},"result":{"expect":"0x020101780A3FF000000000000040000000000000004008000000000000401000000000000040140000000000004018000000000000401C000000000000402000000000000040220000000000004024000000000000"}}
{"expression":"tensor(x[5],y[4])(x*4+(y+1))","inputs":{},"result":{"expect":"0x02020178050179043FF000000000000040000000000000004008000000000000401000000000000040140000000000004018000000000000401C00000000000040200000000000004022000000000000402400000000000040260000000000004028000000000000402A000000000000402C000000000000402E00000000000040300000000000004031000000000000403200000000000040330000000000004034000000000000"}}
{"expression":"tensor(x[5],y[4])(x==y)","inputs":{},"result":{"expect":"0x02020178050179043FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF00000000000000000000000000000000000000000000000000000000000000000000000000000"}}
-{"num_tests":1244}
+{"num_tests":1246}
diff --git a/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp b/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp
index 76f776df552..29bff7fbd69 100644
--- a/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp
+++ b/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp
@@ -151,73 +151,152 @@ TEST("require that basic addition works") {
//-----------------------------------------------------------------------------
-TEST("require that dot product like expression is not optimized for unknown types") {
- const TensorEngine &engine = SimpleTensorEngine::ref();
- Function function = Function::parse("reduce(a*b,sum)");
- DoubleValue a(2.0);
- DoubleValue b(3.0);
- double expect = (2.0 * 3.0);
- InterpretedFunction interpreted(engine, function, NodeTypes());
- EXPECT_EQUAL(4u, interpreted.program_size());
- InterpretedFunction::Context ctx(interpreted);
- InterpretedFunction::SimpleObjectParams params({a,b});
- const Value &result = interpreted.eval(ctx, params);
- EXPECT_TRUE(result.is_double());
- EXPECT_EQUAL(expect, result.as_double());
+struct InnerProduct {
+ const TensorEngine &engine;
+ Function function;
+ TensorSpec a;
+ TensorSpec b;
+ TensorSpec expect;
+ NodeTypes types;
+ InterpretedFunction interpreted;
+ ~InnerProduct() {}
+ InnerProduct(const vespalib::string &expr)
+ : engine(SimpleTensorEngine::ref()),
+ function(Function::parse({"a", "b"}, expr)),
+ a("null"), b("null"), expect("null"),
+ types(),
+ interpreted(engine, function, types) {}
+ InnerProduct(const vespalib::string &expr,
+ TensorSpec a_in,
+ TensorSpec b_in,
+ TensorSpec expect_in)
+ : engine(SimpleTensorEngine::ref()),
+ function(Function::parse(expr)),
+ a(a_in), b(b_in), expect(expect_in),
+ types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())}),
+ interpreted(engine, function, types) {}
+ void verify_optimized() const {
+ EXPECT_EQUAL(1u, interpreted.program_size());
+ InterpretedFunction::Context ctx(interpreted);
+ Value::UP va = engine.from_spec(a);
+ Value::UP vb = engine.from_spec(b);
+ InterpretedFunction::SimpleObjectParams params({*va,*vb});
+ const Value &result = interpreted.eval(ctx, params);
+ EXPECT_EQUAL(engine.to_spec(result), expect);
+ }
+ void verify_not_optimized() const {
+ EXPECT_EQUAL(4u, interpreted.program_size());
+ }
+};
+
+struct UntypedIP : InnerProduct {
+ UntypedIP(const vespalib::string &expr) : InnerProduct(expr) {
+ a = TensorSpec("double").add({}, 2.0);
+ b = TensorSpec("double").add({}, 3.0);
+ expect = TensorSpec("double").add({}, 6.0);
+ }
+};
+
+struct DotProduct : InnerProduct {
+ DotProduct(const vespalib::string &expr)
+ : InnerProduct(expr,
+ TensorSpec("tensor(x[3])")
+ .add({{"x", 0}}, 5.0)
+ .add({{"x", 1}}, 3.0)
+ .add({{"x", 2}}, 2.0),
+ TensorSpec("tensor(x[3])")
+ .add({{"x", 0}}, 7.0)
+ .add({{"x", 1}}, 11.0)
+ .add({{"x", 2}}, 13.0),
+ TensorSpec("double")
+ .add({}, (5.0 * 7.0) + (3.0 * 11.0) + (2.0 * 13.0))) {}
+};
+
+struct XW : InnerProduct {
+ XW(const vespalib::string &expr)
+ : InnerProduct(expr,
+ TensorSpec("tensor(x[2])")
+ .add({{"x", 0}}, 1.0)
+ .add({{"x", 1}}, 2.0),
+ TensorSpec("tensor(x[2],y[3])")
+ .add({{"y", 0},{"x", 0}}, 3.0)
+ .add({{"y", 0},{"x", 1}}, 5.0)
+ .add({{"y", 1},{"x", 0}}, 7.0)
+ .add({{"y", 1},{"x", 1}}, 11.0)
+ .add({{"y", 2},{"x", 0}}, 13.0)
+ .add({{"y", 2},{"x", 1}}, 17.0),
+ TensorSpec("tensor(y[3])")
+ .add({{"y", 0}}, (1.0 * 3.0) + (2.0 * 5.0))
+ .add({{"y", 1}}, (1.0 * 7.0) + (2.0 * 11.0))
+ .add({{"y", 2}}, (1.0 * 13.0) + (2.0 * 17.0))) {}
+};
+
+struct MatMul : InnerProduct {
+ MatMul(const vespalib::string &expr)
+ : InnerProduct(expr,
+ TensorSpec("tensor(x[2],y[2])")
+ .add({{"x", 0},{"y", 0}}, 1.0)
+ .add({{"x", 0},{"y", 1}}, 2.0)
+ .add({{"x", 1},{"y", 0}}, 3.0)
+ .add({{"x", 1},{"y", 1}}, 5.0),
+ TensorSpec("tensor(y[2],z[2])")
+ .add({{"y", 0},{"z", 0}}, 7.0)
+ .add({{"y", 0},{"z", 1}}, 11.0)
+ .add({{"y", 1},{"z", 0}}, 13.0)
+ .add({{"y", 1},{"z", 1}}, 17.0),
+ TensorSpec("tensor(x[2],z[2])")
+ .add({{"x", 0},{"z", 0}}, (1.0 * 7.0) + (2.0 * 13.0))
+ .add({{"x", 0},{"z", 1}}, (1.0 * 11.0) + (2.0 * 17.0))
+ .add({{"x", 1},{"z", 0}}, (3.0 * 7.0) + (5.0 * 13.0))
+ .add({{"x", 1},{"z", 1}}, (3.0 * 11.0) + (5.0 * 17.0))) {}
+};
+
+TEST("require that inner product is not optimized for unknown types") {
+ TEST_DO(UntypedIP("reduce(a*b,sum)").verify_not_optimized());
+ TEST_DO(UntypedIP("reduce(join(a,b,f(x,y)(x*y)),sum)").verify_not_optimized());
}
TEST("require that dot product works with tensor function") {
- const TensorEngine &engine = SimpleTensorEngine::ref();
- Function function = Function::parse("reduce(a*b,sum)");
- auto a = TensorSpec("tensor(x[3])")
- .add({{"x", 0}}, 5.0)
- .add({{"x", 1}}, 3.0)
- .add({{"x", 2}}, 2.0);
- auto b = TensorSpec("tensor(x[3])")
- .add({{"x", 0}}, 7.0)
- .add({{"x", 1}}, 11.0)
- .add({{"x", 2}}, 13.0);
- double expect = ((5.0 * 7.0) + (3.0 * 11.0) + (2.0 * 13.0));
- NodeTypes types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())});
- InterpretedFunction interpreted(engine, function, types);
- EXPECT_EQUAL(1u, interpreted.program_size());
- InterpretedFunction::Context ctx(interpreted);
- Value::UP va = engine.from_spec(a);
- Value::UP vb = engine.from_spec(b);
- InterpretedFunction::SimpleObjectParams params({*va,*vb});
- const Value &result = interpreted.eval(ctx, params);
- EXPECT_TRUE(result.is_double());
- EXPECT_EQUAL(expect, result.as_double());
+ TEST_DO(DotProduct("reduce(a*b,sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y)),sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(b*a,sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(x*y)),sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*x)),sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(y*x)),sum)").verify_optimized());
+ TEST_DO(DotProduct("reduce(a*b,sum,x)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y)),sum,x)").verify_optimized());
+ TEST_DO(DotProduct("reduce(b*a,sum,x)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(x*y)),sum,x)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*x)),sum,x)").verify_optimized());
+ TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(y*x)),sum,x)").verify_optimized());
+}
+
+TEST("require that vector matrix multiplication works with tensor function") {
+ TEST_DO(XW("reduce(a*b,sum,x)").verify_optimized());
+ TEST_DO(XW("reduce(join(a,b,f(x,y)(x*y)),sum,x)").verify_optimized());
+ TEST_DO(XW("reduce(b*a,sum,x)").verify_optimized());
+ TEST_DO(XW("reduce(join(b,a,f(x,y)(x*y)),sum,x)").verify_optimized());
+ TEST_DO(XW("reduce(join(a,b,f(x,y)(y*x)),sum,x)").verify_optimized());
+ TEST_DO(XW("reduce(join(b,a,f(x,y)(y*x)),sum,x)").verify_optimized());
}
TEST("require that matrix multiplication works with tensor function") {
- const TensorEngine &engine = SimpleTensorEngine::ref();
- Function function = Function::parse("reduce(a*b,sum,y)");
- auto a = TensorSpec("tensor(x[2],y[2])")
- .add({{"x", 0},{"y", 0}}, 1.0)
- .add({{"x", 0},{"y", 1}}, 2.0)
- .add({{"x", 1},{"y", 0}}, 3.0)
- .add({{"x", 1},{"y", 1}}, 5.0);
- auto b = TensorSpec("tensor(y[2],z[2])")
- .add({{"y", 0},{"z", 0}}, 7.0)
- .add({{"y", 0},{"z", 1}}, 11.0)
- .add({{"y", 1},{"z", 0}}, 13.0)
- .add({{"y", 1},{"z", 1}}, 17.0);
- auto expect = TensorSpec("tensor(x[2],z[2])")
- .add({{"x", 0},{"z", 0}}, (1.0 * 7.0) + (2.0 * 13.0))
- .add({{"x", 0},{"z", 1}}, (1.0 * 11.0) + (2.0 * 17.0))
- .add({{"x", 1},{"z", 0}}, (3.0 * 7.0) + (5.0 * 13.0))
- .add({{"x", 1},{"z", 1}}, (3.0 * 11.0) + (5.0 * 17.0));
- NodeTypes types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())});
- InterpretedFunction interpreted(engine, function, types);
- EXPECT_EQUAL(1u, interpreted.program_size());
- InterpretedFunction::Context ctx(interpreted);
- Value::UP va = engine.from_spec(a);
- Value::UP vb = engine.from_spec(b);
- InterpretedFunction::SimpleObjectParams params({*va,*vb});
- const Value &result = interpreted.eval(ctx, params);
- ASSERT_TRUE(result.is_tensor());
- EXPECT_EQUAL(expect, engine.to_spec(result));
+ TEST_DO(MatMul("reduce(a*b,sum,y)").verify_optimized());
+ TEST_DO(MatMul("reduce(join(a,b,f(x,y)(x*y)),sum,y)").verify_optimized());
+ TEST_DO(MatMul("reduce(b*a,sum,y)").verify_optimized());
+ TEST_DO(MatMul("reduce(join(b,a,f(x,y)(x*y)),sum,y)").verify_optimized());
+ TEST_DO(MatMul("reduce(join(a,b,f(x,y)(y*x)),sum,y)").verify_optimized());
+ TEST_DO(MatMul("reduce(join(b,a,f(x,y)(y*x)),sum,y)").verify_optimized());
+}
+
+TEST("require that expressions similar to inner product are not optimized") {
+ TEST_DO(DotProduct("reduce(a*b,prod)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(a*b,max)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(a+b,sum)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x+y)),sum)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*x)),sum)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*y)),sum)").verify_not_optimized());
+ TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y*1)),sum)").verify_not_optimized());
}
//-----------------------------------------------------------------------------
diff --git a/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp b/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp
index de6fe4f175a..6f3cdd5f93f 100644
--- a/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp
+++ b/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp
@@ -240,7 +240,7 @@ TEST_F("require that dimensions are sorted", Fixture)
assertTensor({{"x", 5}, {"y", 3}},
{10, 11, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
denseTensor);
- EXPECT_EQUAL("tensor(x[5],y[3])", denseTensor.getType().to_spec());
+ EXPECT_EQUAL("tensor(x[5],y[3])", denseTensor.type().to_spec());
}
diff --git a/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp b/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp
index 63829650cc5..7df436d85a1 100644
--- a/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp
+++ b/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp
@@ -2,6 +2,7 @@
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/eval/tensor/dense/dense_dot_product_function.h>
+#include <vespa/eval/tensor/dense/dense_xw_product_function.h>
#include <vespa/eval/tensor/dense/dense_tensor_function_compiler.h>
#include <vespa/eval/eval/operation.h>
@@ -11,8 +12,7 @@ using namespace vespalib::eval::tensor_function;
using namespace vespalib::tensor;
using vespalib::Stash;
-template <typename T>
-const T *as(const TensorFunction &function) { return dynamic_cast<const T *>(&function); }
+//-----------------------------------------------------------------------------
const TensorFunction &
compileDotProduct(const vespalib::string &lhsType,
@@ -48,6 +48,65 @@ assertNotCompiledDotProduct(const vespalib::string &lhsType,
EXPECT_TRUE(reduce);
}
+//-----------------------------------------------------------------------------
+
+const TensorFunction &
+compileXWProduct(const vespalib::string &lhsType,
+ const vespalib::string &rhsType,
+ const vespalib::string &dim,
+ Stash &stash)
+{
+ const Node &reduceNode = reduce(join(inject(ValueType::from_spec(lhsType), 1, stash),
+ inject(ValueType::from_spec(rhsType), 3, stash),
+ Mul::f, stash),
+ Aggr::SUM, {dim}, stash);
+ return DenseTensorFunctionCompiler::compile(reduceNode, stash);
+}
+
+void
+assertCompiledXWProduct(const vespalib::string &vecTypeStr,
+ const vespalib::string &matTypeStr,
+ const vespalib::string &dim)
+{
+ Stash stash;
+ const TensorFunction &func = compileXWProduct(vecTypeStr, matTypeStr, dim, stash);
+ const TensorFunction &inv_func = compileXWProduct(matTypeStr, vecTypeStr, dim, stash);
+ const DenseXWProductFunction *xwProduct = as<DenseXWProductFunction>(func);
+ const DenseXWProductFunction *inv_xwProduct = as<DenseXWProductFunction>(inv_func);
+ ValueType vecType = ValueType::from_spec(vecTypeStr);
+ ValueType matType = ValueType::from_spec(matTypeStr);
+ size_t common_idx = matType.dimension_index(vecType.dimensions()[0].name);
+ ASSERT_TRUE(xwProduct);
+ ASSERT_TRUE(inv_xwProduct);
+ ASSERT_TRUE(common_idx != ValueType::Dimension::npos);
+ EXPECT_EQUAL(xwProduct->vectorId(), 1u);
+ EXPECT_EQUAL(inv_xwProduct->vectorId(), 3u);
+ EXPECT_EQUAL(xwProduct->matrixId(), 3u);
+ EXPECT_EQUAL(inv_xwProduct->matrixId(), 1u);
+ EXPECT_EQUAL(xwProduct->vectorSize(), vecType.dimensions()[0].size);
+ EXPECT_EQUAL(inv_xwProduct->vectorSize(), vecType.dimensions()[0].size);
+ EXPECT_EQUAL(xwProduct->resultSize(), matType.dimensions()[1 - common_idx].size);
+ EXPECT_EQUAL(inv_xwProduct->resultSize(), matType.dimensions()[1 - common_idx].size);
+ EXPECT_EQUAL(xwProduct->matrixHasCommonDimensionInnermost(), (common_idx == 1));
+ EXPECT_EQUAL(inv_xwProduct->matrixHasCommonDimensionInnermost(), (common_idx == 1));
+}
+
+void
+assertNotCompiledXWProduct(const vespalib::string &vecType,
+ const vespalib::string &matType,
+ const vespalib::string &dim)
+{
+ Stash stash;
+ const TensorFunction &func = compileXWProduct(vecType, matType, dim, stash);
+ const TensorFunction &inv_func = compileXWProduct(matType, vecType, dim, stash);
+ const Reduce *reduce = as<Reduce>(func);
+ const Reduce *inv_reduce = as<Reduce>(inv_func);
+ EXPECT_TRUE(reduce);
+ EXPECT_TRUE(inv_reduce);
+}
+
+//-----------------------------------------------------------------------------
+
TEST("require that dot product with compatible dimensions is compiled")
{
TEST_DO(assertCompiledDotProduct("tensor(x[5])", "tensor(x[5])"));
@@ -67,4 +126,28 @@ TEST("require that dot product with incompatible dimensions is NOT compiled")
TEST_DO(assertNotCompiledDotProduct("tensor(x[5],y[7])", "tensor(x[5],y[7])"));
}
+//-----------------------------------------------------------------------------
+
+TEST("require that xw products with compatible dimensions are compiled") {
+ TEST_DO(assertCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[4])", "x"));
+ TEST_DO(assertCompiledXWProduct("tensor(y[4])", "tensor(x[3],y[4])", "y"));
+}
+
+TEST("require that xw products with incompatible dimensions are not compiled") {
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[4])", "y"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[])", "tensor(x[3],y[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[],y[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[2])", "tensor(x[3],y[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[4])", "tensor(x[3],y[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "y"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "z"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(y[4])", "tensor(x[3],y[4])", "x"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(y[3])", "tensor(x[3],y[4])", "y"));
+ TEST_DO(assertNotCompiledXWProduct("tensor(y[5])", "tensor(x[3],y[4])", "y"));
+}
+
+//-----------------------------------------------------------------------------
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt b/eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt
new file mode 100644
index 00000000000..648c10e64bd
--- /dev/null
+++ b/eval/src/tests/tensor/dense_xw_product_function/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(eval_dense_xw_product_function_test_app TEST
+ SOURCES
+ dense_xw_product_function_test.cpp
+ DEPENDS
+ vespaeval
+)
+vespa_add_test(NAME eval_dense_xw_product_function_test_app COMMAND eval_dense_xw_product_function_test_app)
diff --git a/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp
new file mode 100644
index 00000000000..5c62d319dc3
--- /dev/null
+++ b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp
@@ -0,0 +1,102 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+LOG_SETUP("dense_dot_product_function_test");
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/eval/eval/tensor_function.h>
+#include <vespa/eval/eval/operation.h>
+#include <vespa/eval/eval/simple_tensor.h>
+#include <vespa/eval/eval/simple_tensor_engine.h>
+#include <vespa/eval/tensor/default_tensor_engine.h>
+#include <vespa/eval/tensor/dense/dense_xw_product_function.h>
+#include <vespa/eval/tensor/dense/dense_tensor.h>
+#include <vespa/eval/tensor/dense/dense_tensor_builder.h>
+#include <vespa/eval/tensor/dense/dense_tensor_view.h>
+
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/stash.h>
+
+using namespace vespalib;
+using namespace vespalib::eval;
+using namespace vespalib::tensor;
+
+const TensorEngine &ref_engine = SimpleTensorEngine::ref();
+const TensorEngine &prod_engine = DefaultTensorEngine::ref();
+
+void verify_equal(const Value &expect, const Value &value) {
+ const eval::Tensor *tensor = value.as_tensor();
+ ASSERT_TRUE(tensor != nullptr);
+ const eval::Tensor *expect_tensor = expect.as_tensor();
+ ASSERT_TRUE(expect_tensor != nullptr);
+ auto expect_spec = expect_tensor->engine().to_spec(expect);
+ auto value_spec = tensor->engine().to_spec(value);
+ EXPECT_EQUAL(expect_spec, value_spec);
+}
+
+std::vector<eval::Value::CREF> wrap(std::vector<eval::Value::CREF> params) {
+ return std::move(params);
+}
+
+void verify_result(const TensorSpec &v, const TensorSpec &m, bool happy) {
+ Stash stash;
+ Value::UP ref_vec = ref_engine.from_spec(v);
+ Value::UP ref_mat = ref_engine.from_spec(m);
+ const Value &joined = ref_engine.join(*ref_vec, *ref_mat, operation::Mul::f, stash);
+ const Value &expect = ref_engine.reduce(joined, Aggr::SUM, {"x"}, stash);
+
+ Value::UP prod_vec = prod_engine.from_spec(v);
+ Value::UP prod_mat = prod_engine.from_spec(m);
+
+ DenseXWProductFunction fun1(expect.type(), 0, 1,
+ prod_vec->type().dimensions()[0].size,
+ expect.type().dimensions()[0].size,
+ happy);
+ const Value &actual1 = fun1.eval(wrap({*prod_vec, *prod_mat}), stash);
+ TEST_DO(verify_equal(expect, actual1));
+
+ DenseXWProductFunction fun2(expect.type(), 1, 0,
+ prod_vec->type().dimensions()[0].size,
+ expect.type().dimensions()[0].size,
+ happy);
+ const Value &actual2 = fun2.eval(wrap({*prod_mat, *prod_vec}), stash);
+ TEST_DO(verify_equal(expect, actual2));
+}
+
+TensorSpec make_vector(const vespalib::string &name, size_t sz) {
+ TensorSpec ret(make_string("tensor(%s[%zu])", name.c_str(), sz));
+ for (size_t i = 0; i < sz; ++i) {
+ ret.add({{name, i}}, (1.0 + i) * 16.0);
+ }
+ return ret;
+}
+
+TensorSpec make_matrix(const vespalib::string &d1name, size_t d1sz,
+ const vespalib::string &d2name, size_t d2sz)
+{
+ TensorSpec ret(make_string("tensor(%s[%zu],%s[%zu])",
+ d1name.c_str(), d1sz,
+ d2name.c_str(), d2sz));
+ for (size_t i = 0; i < d1sz; ++i) {
+ for (size_t j = 0; j < d2sz; ++j) {
+ ret.add({{d1name,i},{d2name,j}}, 1.0 + i*7.0 + j*43.0);
+ }
+ }
+ return ret;
+}
+
+TEST("require that xw product gives same results as reference join/reduce") {
+ verify_result(make_vector("x", 1), make_matrix("o", 1, "x", 1), true);
+ verify_result(make_vector("x", 1), make_matrix("x", 1, "y", 1), false);
+
+ verify_result(make_vector("x", 3), make_matrix("o", 2, "x", 3), true);
+ verify_result(make_vector("x", 3), make_matrix("x", 3, "y", 2), false);
+
+ verify_result(make_vector("x", 5), make_matrix("o", 8, "x", 5), true);
+ verify_result(make_vector("x", 5), make_matrix("x", 5, "y", 8), false);
+
+ verify_result(make_vector("x", 16), make_matrix("o", 5, "x", 16), true);
+ verify_result(make_vector("x", 16), make_matrix("x", 16, "y", 5), false);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp b/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp
index b6d58236c9c..bd3d1ada017 100644
--- a/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp
+++ b/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp
@@ -91,7 +91,7 @@ TEST("require that dimensions are extracted")
EXPECT_EQUAL("a", dims[0].name);
EXPECT_EQUAL("b", dims[1].name);
EXPECT_EQUAL("c", dims[2].name);
- EXPECT_EQUAL("tensor(a{},b{},c{})", sparseTensor.getType().to_spec());
+ EXPECT_EQUAL("tensor(a{},b{},c{})", sparseTensor.type().to_spec());
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/vespa/eval/eval/interpreted_function.cpp b/eval/src/vespa/eval/eval/interpreted_function.cpp
index cfe989e95f8..ada942767fa 100644
--- a/eval/src/vespa/eval/eval/interpreted_function.cpp
+++ b/eval/src/vespa/eval/eval/interpreted_function.cpp
@@ -161,25 +161,36 @@ struct ProgramBuilder : public NodeVisitor, public NodeTraverser {
//-------------------------------------------------------------------------
+ bool is_mul_join(const Node &node) const {
+ if (auto join = as<TensorJoin>(node)) {
+ if (auto mul = as<Mul>(join->lambda().root())) {
+ auto sym1 = as<Symbol>(mul->lhs());
+ auto sym2 = as<Symbol>(mul->rhs());
+ return (sym1 && sym2 && (sym1->id() != sym2->id()));
+ }
+ }
+ return false;
+ }
+
+ bool is_mul(const Node &node) const {
+ auto mul = as<Mul>(node);
+ return (mul || is_mul_join(node));
+ }
+
bool is_typed_tensor(const Node &node) const {
const ValueType &type = types.get_type(node);
return (type.is_tensor() && !type.dimensions().empty());
}
- bool is_typed(const Node &node) const {
- return (types.get_type(node).is_double() || is_typed_tensor(node));
- }
-
bool is_typed_tensor_param(const Node &node) const {
auto sym = as<Symbol>(node);
return (sym && is_typed_tensor(node));
}
bool is_typed_tensor_product_of_params(const Node &node) const {
- auto mul = as<Mul>(node);
- return (mul && is_typed_tensor(*mul) &&
- is_typed_tensor_param(mul->lhs()) &&
- is_typed_tensor_param(mul->rhs()));
+ return (is_typed_tensor(node) && is_mul(node) &&
+ is_typed_tensor_param(node.get_child(0)) &&
+ is_typed_tensor_param(node.get_child(1)));
}
//-------------------------------------------------------------------------
@@ -260,7 +271,7 @@ struct ProgramBuilder : public NodeVisitor, public NodeTraverser {
make_join_op(node, token.get()->get().get_function<2>());
}
void visit(const TensorReduce &node) override {
- if ((node.aggr() == Aggr::SUM) && is_typed(node) && is_typed_tensor_product_of_params(node.get_child(0))) {
+ if ((node.aggr() == Aggr::SUM) && is_typed_tensor_product_of_params(node.get_child(0))) {
assert(program.size() >= 3); // load,load,mul
program.pop_back(); // mul
program.pop_back(); // load
diff --git a/eval/src/vespa/eval/eval/tensor_function.h b/eval/src/vespa/eval/eval/tensor_function.h
index 359cabc18a0..e23dc8c6fc0 100644
--- a/eval/src/vespa/eval/eval/tensor_function.h
+++ b/eval/src/vespa/eval/eval/tensor_function.h
@@ -45,9 +45,13 @@ struct TensorFunction
virtual ~TensorFunction() {}
};
-//-----------------------------------------------------------------------------
+/**
+ * Simple typecasting utility.
+ */
+template <typename T>
+const T *as(const TensorFunction &node) { return dynamic_cast<const T *>(&node); }
-struct TensorFunctionVisitor;
+//-----------------------------------------------------------------------------
namespace tensor_function {
@@ -77,12 +81,6 @@ struct Node : public TensorFunction
Node &operator=(Node &&) = delete;
};
-/**
- * Simple typecasting utility.
- */
-template <typename T>
-const T *as(const Node &node) { return dynamic_cast<const T *>(&node); }
-
struct Inject : Node {
const size_t tensor_id;
Inject(const ValueType &result_type_in,
diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h
index 370804b9c5e..2988cc5204e 100644
--- a/eval/src/vespa/eval/eval/value_type.h
+++ b/eval/src/vespa/eval/eval/value_type.h
@@ -45,6 +45,10 @@ private:
: _type(type_in), _dimensions(std::move(dimensions_in)) {}
public:
+ ValueType(ValueType &&) = default;
+ ValueType(const ValueType &) = default;
+ ValueType &operator=(ValueType &&) = default;
+ ValueType &operator=(const ValueType &) = default;
~ValueType();
Type type() const { return _type; }
bool is_any() const { return (_type == Type::ANY); }
diff --git a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
index 2506e6fcf0e..773d2364b7d 100644
--- a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
+++ b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
@@ -12,6 +12,7 @@
#include <vespa/eval/eval/tensor_spec.h>
#include <vespa/eval/eval/simple_tensor_engine.h>
#include <vespa/eval/eval/operation.h>
+#include <vespa/vespalib/objects/nbostream.h>
#include <cassert>
@@ -42,8 +43,9 @@ const Value &to_simple(const Value &value, Stash &stash) {
if (auto wrapped = dynamic_cast<const WrappedSimpleTensor *>(tensor)) {
return wrapped->get();
}
- TensorSpec spec = tensor->engine().to_spec(*tensor);
- return *stash.create<Value::UP>(eval::SimpleTensor::create(spec));
+ nbostream data;
+ tensor->engine().encode(*tensor, data);
+ return *stash.create<Value::UP>(eval::SimpleTensor::decode(data));
}
return value;
}
@@ -57,8 +59,9 @@ const Value &to_default(const Value &value, Stash &stash) {
return stash.create<WrappedSimpleTensor>(*simple);
}
}
- TensorSpec spec = tensor->engine().to_spec(*tensor);
- return *stash.create<Value::UP>(default_engine().from_spec(spec));
+ nbostream data;
+ tensor->engine().encode(*tensor, data);
+ return *stash.create<Value::UP>(default_engine().decode(data));
}
return value;
}
@@ -67,7 +70,7 @@ const Value &to_value(std::unique_ptr<Tensor> tensor, Stash &stash) {
if (!tensor) {
return ErrorValue::instance;
}
- if (tensor->getType().is_tensor()) {
+ if (tensor->type().is_tensor()) {
return *stash.create<Value::UP>(std::move(tensor));
}
return stash.create<DoubleValue>(tensor->as_double());
@@ -218,7 +221,7 @@ DefaultTensorEngine::map(const Value &a, map_fun_t function, Stash &stash) const
} else if (auto tensor = a.as_tensor()) {
assert(&tensor->engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor);
- if (!tensor::Tensor::supported({my_a.getType()})) {
+ if (!tensor::Tensor::supported({my_a.type()})) {
return to_default(simple_engine().map(to_simple(a, stash), function, stash), stash);
}
CellFunctionFunAdapter cell_function(function);
@@ -237,7 +240,7 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S
} else if (auto tensor_b = b.as_tensor()) {
assert(&tensor_b->engine() == this);
const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(*tensor_b);
- if (!tensor::Tensor::supported({my_b.getType()})) {
+ if (!tensor::Tensor::supported({my_b.type()})) {
return fallback_join(a, b, function, stash);
}
CellFunctionBindLeftAdapter cell_function(function, a.as_double());
@@ -249,7 +252,7 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S
assert(&tensor_a->engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor_a);
if (b.is_double()) {
- if (!tensor::Tensor::supported({my_a.getType()})) {
+ if (!tensor::Tensor::supported({my_a.type()})) {
return fallback_join(a, b, function, stash);
}
CellFunctionBindRightAdapter cell_function(function, b.as_double());
@@ -257,18 +260,10 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S
} else if (auto tensor_b = b.as_tensor()) {
assert(&tensor_b->engine() == this);
const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(*tensor_b);
- if (!tensor::Tensor::supported({my_a.getType(), my_b.getType()})) {
+ if (!tensor::Tensor::supported({my_a.type(), my_b.type()})) {
return fallback_join(a, b, function, stash);
}
- if (function == eval::operation::Mul::f) {
- if (my_a.getType() == my_b.getType()) {
- return to_value(my_a.match(my_b), stash);
- } else {
- return to_value(my_a.multiply(my_b), stash);
- }
- } else {
- return to_value(my_a.join(function, my_b), stash);
- }
+ return to_value(my_a.join(function, my_b), stash);
} else {
return ErrorValue::instance;
}
@@ -291,16 +286,14 @@ DefaultTensorEngine::reduce(const Value &a, Aggr aggr, const std::vector<vespali
} else if (auto tensor = a.as_tensor()) {
assert(&tensor->engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor);
- if (!tensor::Tensor::supported({my_a.getType()})) {
+ if (!tensor::Tensor::supported({my_a.type()})) {
return fallback_reduce(a, aggr, dimensions, stash);
}
switch (aggr) {
case Aggr::PROD: return to_value(my_a.reduce(eval::operation::Mul::f, dimensions), stash);
case Aggr::SUM:
if (dimensions.empty()) {
- return stash.create<eval::DoubleValue>(my_a.sum());
- } else if (dimensions.size() == 1) {
- return to_value(my_a.sum(dimensions[0]), stash);
+ return stash.create<eval::DoubleValue>(my_a.as_double());
} else {
return to_value(my_a.reduce(eval::operation::Add::f, dimensions), stash);
}
@@ -314,9 +307,48 @@ DefaultTensorEngine::reduce(const Value &a, Aggr aggr, const std::vector<vespali
}
}
+size_t vector_size(const ValueType &type, const vespalib::string &dimension) {
+ if (type.is_double()) {
+ return 1;
+ } else if ((type.dimensions().size() == 1) &&
+ (type.dimensions()[0].is_indexed()) &&
+ (type.dimensions()[0].name == dimension))
+ {
+ return type.dimensions()[0].size;
+ } else {
+ return 0;
+ }
+}
+
+void append_vector(double *&pos, const Value &value) {
+ if (auto tensor = value.as_tensor()) {
+ const DenseTensorView *view = static_cast<const DenseTensorView *>(tensor);
+ for (double cell: view->cellsRef()) {
+ *pos++ = cell;
+ }
+ } else {
+ *pos++ = value.as_double();
+ }
+}
+
+const Value &concat_vectors(const Value &a, const Value &b, const vespalib::string &dimension, size_t vector_size, Stash &stash) {
+ ArrayRef<double> cells = stash.create_array<double>(vector_size);
+ double *pos = cells.begin();
+ append_vector(pos, a);
+ append_vector(pos, b);
+ assert(pos == cells.end());
+ const ValueType &type = stash.create<ValueType>(ValueType::tensor_type({ValueType::Dimension(dimension, vector_size)}));
+ return stash.create<DenseTensorView>(type, cells);
+}
+
const Value &
DefaultTensorEngine::concat(const Value &a, const Value &b, const vespalib::string &dimension, Stash &stash) const
{
+ size_t a_size = vector_size(a.type(), dimension);
+ size_t b_size = vector_size(b.type(), dimension);
+ if ((a_size > 0) && (b_size > 0)) {
+ return concat_vectors(a, b, dimension, a_size + b_size, stash);
+ }
return to_default(simple_engine().concat(to_simple(a, stash), to_simple(b, stash), dimension, stash), stash);
}
diff --git a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt
index ec715bce3d9..54698371f4e 100644
--- a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt
+++ b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt
@@ -3,6 +3,7 @@ vespa_add_library(eval_tensor_dense OBJECT
SOURCES
direct_dense_tensor_builder.cpp
dense_dot_product_function.cpp
+ dense_xw_product_function.cpp
dense_tensor.cpp
dense_tensor_address_combiner.cpp
dense_tensor_builder.cpp
diff --git a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp
index 705496714fa..992f2eae750 100644
--- a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp
+++ b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp
@@ -23,9 +23,8 @@ namespace {
CellsRef
getCellsRef(const eval::Value &value)
{
- const Tensor *tensor = static_cast<const Tensor *>(value.as_tensor());
- const DenseTensorView *denseTensor = static_cast<const DenseTensorView *>(tensor);
- return denseTensor->cellsRef();
+ const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value);
+ return denseTensor.cellsRef();
}
}
diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp
index e9ee7d30692..1268a46b8e5 100644
--- a/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp
+++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "dense_dot_product_function.h"
+#include "dense_xw_product_function.h"
#include "dense_tensor_function_compiler.h"
#include <vespa/eval/eval/operation.h>
#include <vespa/vespalib/test/insertion_operators.h>
@@ -15,39 +16,64 @@ namespace tensor {
namespace {
-bool
-willReduceAllDimensions(const std::vector<vespalib::string> &dimensions)
-{
- return (dimensions.empty() || (dimensions.size() == 1));
+bool is1dDenseTensor(const ValueType &type) {
+ return (type.is_dense() && (type.dimensions().size() == 1));
}
-bool
-is1dDenseTensor(const ValueType &type)
-{
- return (type.is_dense() && (type.dimensions().size() == 1));
+bool isConcreteDenseTensor(const ValueType &type, size_t d) {
+ return (type.is_dense() && (type.dimensions().size() == d) && !type.is_abstract());
}
-bool
-isCompatibleTensorsForDotProduct(const ValueType &lhsType, const ValueType &rhsType)
-{
- return (is1dDenseTensor(lhsType) &&
+bool isDenseDotProduct(const ValueType &res, const ValueType &lhsType, const ValueType &rhsType) {
+ return (res.is_double() &&
+ is1dDenseTensor(lhsType) &&
is1dDenseTensor(rhsType) &&
(lhsType.dimensions()[0].name == rhsType.dimensions()[0].name));
}
-struct DotProductFunctionCompiler
+bool isDenseXWProduct(const ValueType &res, const ValueType &vec, const ValueType &mat) {
+ if (isConcreteDenseTensor(res, 1) &&
+ isConcreteDenseTensor(vec, 1) &&
+ isConcreteDenseTensor(mat, 2))
+ {
+ size_t res_idx = mat.dimension_index(res.dimensions()[0].name);
+ size_t vec_idx = mat.dimension_index(vec.dimensions()[0].name);
+ size_t npos = ValueType::Dimension::npos;
+ if ((res_idx != npos) && (vec_idx != npos) && (res_idx != vec_idx)) {
+ return ((mat.dimensions()[res_idx].size == res.dimensions()[0].size) &&
+ (mat.dimensions()[vec_idx].size == vec.dimensions()[0].size));
+ }
+ }
+ return false;
+}
+
+const TensorFunction &createDenseXWProduct(const ValueType &res, const Inject &vec, const Inject &mat, Stash &stash) {
+ bool common_is_inner = (mat.result_type.dimension_index(vec.result_type.dimensions()[0].name) == 1);
+ return stash.create<DenseXWProductFunction>(res, vec.tensor_id, mat.tensor_id,
+ vec.result_type.dimensions()[0].size,
+ res.dimensions()[0].size,
+ common_is_inner);
+}
+
+struct InnerProductFunctionCompiler
{
static const TensorFunction &compile(const Node &expr, Stash &stash) {
const Reduce *reduce = as<Reduce>(expr);
- if (reduce && (reduce->aggr == Aggr::SUM) && willReduceAllDimensions(reduce->dimensions)) {
+ if (reduce && (reduce->aggr == Aggr::SUM)) {
const Join *join = as<Join>(reduce->tensor);
if (join && (join->function == Mul::f)) {
- const Inject *lhsTensor = as<Inject>(join->lhs_tensor);
- const Inject *rhsTensor = as<Inject>(join->rhs_tensor);
- if (lhsTensor && rhsTensor &&
- isCompatibleTensorsForDotProduct(lhsTensor->result_type, rhsTensor->result_type))
- {
- return stash.create<DenseDotProductFunction>(lhsTensor->tensor_id, rhsTensor->tensor_id);
+ const Inject *lhs = as<Inject>(join->lhs_tensor);
+ const Inject *rhs = as<Inject>(join->rhs_tensor);
+ if (lhs && rhs) {
+ if (isDenseDotProduct(expr.result_type, lhs->result_type, rhs->result_type)) {
+ return stash.create<DenseDotProductFunction>(lhs->tensor_id, rhs->tensor_id);
+ }
+ if (isDenseXWProduct(expr.result_type, lhs->result_type, rhs->result_type)) {
+ return createDenseXWProduct(expr.result_type, *lhs, *rhs, stash);
+ }
+ if (isDenseXWProduct(expr.result_type, rhs->result_type, lhs->result_type)) {
+ return createDenseXWProduct(expr.result_type, *rhs, *lhs, stash);
+ }
}
}
}
@@ -60,7 +86,7 @@ struct DotProductFunctionCompiler
const TensorFunction &
DenseTensorFunctionCompiler::compile(const eval::tensor_function::Node &expr, Stash &stash)
{
- return DotProductFunctionCompiler::compile(expr, stash);
+ return InnerProductFunctionCompiler::compile(expr, stash);
}
} // namespace tensor
diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp
index 4402b5b0ae0..30c9f17348e 100644
--- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp
+++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp
@@ -145,13 +145,13 @@ DenseTensorView::operator==(const DenseTensorView &rhs) const
}
const eval::ValueType &
-DenseTensorView::getType() const
+DenseTensorView::type() const
{
return _typeRef;
}
double
-DenseTensorView::sum() const
+DenseTensorView::as_double() const
{
double result = 0.0;
for (const auto &cell : _cellsRef) {
@@ -161,54 +161,6 @@ DenseTensorView::sum() const
}
Tensor::UP
-DenseTensorView::add(const Tensor &arg) const
-{
- return dense::apply(*this, arg,
- [](double lhsValue, double rhsValue)
- { return lhsValue + rhsValue; });
-}
-
-Tensor::UP
-DenseTensorView::subtract(const Tensor &arg) const
-{
- return dense::apply(*this, arg,
- [](double lhsValue, double rhsValue)
- { return lhsValue - rhsValue; });
-}
-
-Tensor::UP
-DenseTensorView::multiply(const Tensor &arg) const
-{
- return dense::apply(*this, arg,
- [](double lhsValue, double rhsValue)
- { return lhsValue * rhsValue; });
-}
-
-Tensor::UP
-DenseTensorView::min(const Tensor &arg) const
-{
- return dense::apply(*this, arg,
- [](double lhsValue, double rhsValue)
- { return std::min(lhsValue, rhsValue); });
-}
-
-Tensor::UP
-DenseTensorView::max(const Tensor &arg) const
-{
- return dense::apply(*this, arg,
- [](double lhsValue, double rhsValue)
- { return std::max(lhsValue, rhsValue); });
-}
-
-Tensor::UP
-DenseTensorView::match(const Tensor &arg) const
-{
- return joinDenseTensors(*this, arg, "match",
- [](double lhsValue, double rhsValue)
- { return (lhsValue * rhsValue); });
-}
-
-Tensor::UP
DenseTensorView::apply(const CellFunction &func) const
{
Cells newCells(_cellsRef.size());
@@ -221,14 +173,6 @@ DenseTensorView::apply(const CellFunction &func) const
return std::make_unique<DenseTensor>(_typeRef, std::move(newCells));
}
-Tensor::UP
-DenseTensorView::sum(const vespalib::string &dimension) const
-{
- return dense::reduce(*this, { dimension },
- [](double lhsValue, double rhsValue)
- { return lhsValue + rhsValue; });
-}
-
bool
DenseTensorView::equals(const Tensor &arg) const
{
@@ -239,14 +183,6 @@ DenseTensorView::equals(const Tensor &arg) const
return false;
}
-vespalib::string
-DenseTensorView::toString() const
-{
- std::ostringstream stream;
- stream << *this;
- return stream.str();
-}
-
Tensor::UP
DenseTensorView::clone() const
{
@@ -271,7 +207,7 @@ buildAddress(const DenseTensorCellsIterator &itr, TensorSpec::Address &address)
TensorSpec
DenseTensorView::toSpec() const
{
- TensorSpec result(getType().to_spec());
+ TensorSpec result(type().to_spec());
TensorSpec::Address address;
for (CellsIterator itr(_typeRef, _cellsRef); itr.valid(); itr.next()) {
buildAddress(itr, address);
@@ -282,31 +218,6 @@ DenseTensorView::toSpec() const
}
void
-DenseTensorView::print(std::ostream &out) const
-{
- // TODO (geirst): print on common format.
- out << "[ ";
- bool first = true;
- for (const auto &dim : _typeRef.dimensions()) {
- if (!first) {
- out << ", ";
- }
- out << dim.name << ":" << dim.size;
- first = false;
- }
- out << " ] { ";
- first = true;
- for (const auto &cell : cellsRef()) {
- if (!first) {
- out << ", ";
- }
- out << cell;
- first = false;
- }
- out << " }";
-}
-
-void
DenseTensorView::accept(TensorVisitor &visitor) const
{
CellsIterator iterator(_typeRef, _cellsRef);
@@ -330,6 +241,17 @@ DenseTensorView::accept(TensorVisitor &visitor) const
Tensor::UP
DenseTensorView::join(join_fun_t function, const Tensor &arg) const
{
+ if (fast_type() == arg.type()) {
+ if (function == eval::operation::Mul::f) {
+ return joinDenseTensors(*this, arg, "mul",
+ [](double a, double b) { return (a * b); });
+ }
+ if (function == eval::operation::Add::f) {
+ return joinDenseTensors(*this, arg, "add",
+ [](double a, double b) { return (a + b); });
+ }
+ return joinDenseTensors(*this, arg, "join", function);
+ }
return dense::apply(*this, arg, function);
}
diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h
index 472cc58ad6b..5a59594667d 100644
--- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h
+++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h
@@ -47,24 +47,15 @@ public:
bool operator==(const DenseTensorView &rhs) const;
CellsIterator cellsIterator() const { return CellsIterator(_typeRef, _cellsRef); }
- virtual const eval::ValueType &getType() const override;
- virtual double sum() const override;
- virtual Tensor::UP add(const Tensor &arg) const override;
- virtual Tensor::UP subtract(const Tensor &arg) const override;
- virtual Tensor::UP multiply(const Tensor &arg) const override;
- virtual Tensor::UP min(const Tensor &arg) const override;
- virtual Tensor::UP max(const Tensor &arg) const override;
- virtual Tensor::UP match(const Tensor &arg) const override;
+ virtual const eval::ValueType &type() const override;
+ virtual double as_double() const override;
virtual Tensor::UP apply(const CellFunction &func) const override;
- virtual Tensor::UP sum(const vespalib::string &dimension) const override;
virtual Tensor::UP join(join_fun_t function,
const Tensor &arg) const override;
virtual Tensor::UP reduce(join_fun_t op,
const std::vector<vespalib::string> &dimensions)
const override;
virtual bool equals(const Tensor &arg) const override;
- virtual void print(std::ostream &out) const override;
- virtual vespalib::string toString() const override;
virtual Tensor::UP clone() const override;
virtual eval::TensorSpec toSpec() const override;
virtual void accept(TensorVisitor &visitor) const override;
diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp
new file mode 100644
index 00000000000..45de00dc7fe
--- /dev/null
+++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp
@@ -0,0 +1,91 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "dense_xw_product_function.h"
+#include "dense_tensor.h"
+#include "dense_tensor_view.h"
+#include <vespa/eval/eval/value.h>
+#include <vespa/eval/tensor/tensor.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <assert.h>
+
+namespace vespalib {
+namespace tensor {
+
+DenseXWProductFunction::DenseXWProductFunction(const eval::ValueType &resultType,
+ size_t vectorId,
+ size_t matrixId,
+ size_t vectorSize,
+ size_t resultSize,
+ bool matrixHasCommonDimensionInnermost)
+ : _resultType(resultType),
+ _vectorId(vectorId),
+ _matrixId(matrixId),
+ _vectorSize(vectorSize),
+ _resultSize(resultSize),
+ _commonDimensionInnermost(matrixHasCommonDimensionInnermost),
+ _hwAccelerator(hwaccelrated::IAccelrated::getAccelrator())
+{}
+
+void
+DenseXWProductFunction::multiDotProduct(const XWInput &vectorCells,
+ const XWInput &matrixCells,
+ XWOutput &result) const
+{
+ double *out = result.begin();
+ const double *matrixP = matrixCells.cbegin();
+ const double * const vectorP = vectorCells.cbegin();
+ for (size_t row = 0; row < _resultSize; ++row) {
+ double cell = _hwAccelerator->dotProduct(vectorP, matrixP, _vectorSize);
+ *out++ = cell;
+ matrixP += _vectorSize;
+ }
+ assert(out == result.end());
+ assert(matrixP == matrixCells.cend());
+}
+
+void
+DenseXWProductFunction::transposedProduct(const XWInput &vectorCells,
+ const XWInput &matrixCells,
+ XWOutput &result) const
+{
+ double *out = result.begin();
+ const double * const matrixP = matrixCells.cbegin();
+ const double * const vectorP = vectorCells.cbegin();
+ for (size_t row = 0; row < _resultSize; ++row) {
+ double cell = 0;
+ for (size_t col = 0; col < _vectorSize; ++col) {
+ cell += matrixP[col*_resultSize + row] * vectorP[col];
+ }
+ *out++ = cell;
+ }
+ assert(out == result.end());
+}
+
+namespace {
+
+DenseTensorView::CellsRef
+getCellsRef(const eval::Value &value)
+{
+ const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value);
+ return denseTensor.cellsRef();
+}
+
+} // namespace <unnamed>
+
+const eval::Value &
+DenseXWProductFunction::eval(ConstArrayRef<eval::Value::CREF> params, Stash &stash) const
+{
+ DenseTensorView::CellsRef vectorCells = getCellsRef(params[_vectorId]);
+ DenseTensorView::CellsRef matrixCells = getCellsRef(params[_matrixId]);
+
+ ArrayRef<double> outputCells = stash.create_array<double>(_resultSize);
+ if (_commonDimensionInnermost) {
+ multiDotProduct(vectorCells, matrixCells, outputCells);
+ } else {
+ transposedProduct(vectorCells, matrixCells, outputCells);
+ }
+ return stash.create<DenseTensorView>(_resultType, outputCells);
+}
+
+} // namespace tensor
+} // namespace vespalib
diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h
new file mode 100644
index 00000000000..db006100e5a
--- /dev/null
+++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h
@@ -0,0 +1,53 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/eval/eval/tensor_function.h>
+#include "dense_tensor_view.h"
+#include <vespa/vespalib/hwaccelrated/iaccelrated.h>
+
+namespace vespalib {
+namespace tensor {
+
+using XWInput = DenseTensorView::CellsRef;
+using XWOutput = ArrayRef<double>;
+
+/**
+ * Tensor function for product of one 1-dimensional and one 2-dimensional dense tensor.
+ */
+class DenseXWProductFunction : public eval::TensorFunction
+{
+private:
+ const eval::ValueType _resultType;
+ const size_t _vectorId;
+ const size_t _matrixId;
+ const size_t _vectorSize;
+ const size_t _resultSize;
+ bool _commonDimensionInnermost;
+ hwaccelrated::IAccelrated::UP _hwAccelerator;
+
+ void multiDotProduct(const XWInput &v, const XWInput &m, XWOutput &r) const;
+ void transposedProduct(const XWInput &v, const XWInput &m, XWOutput &r) const;
+public:
+ DenseXWProductFunction(const eval::ValueType &resultType,
+ size_t vectorId,
+ size_t matrixId,
+ size_t vectorSize,
+ size_t resultSize,
+ bool matrixHasCommonDimensionInnermost);
+
+ ~DenseXWProductFunction() {}
+
+ size_t vectorId() const { return _vectorId; }
+ size_t matrixId() const { return _matrixId; }
+
+ size_t vectorSize() const { return _vectorSize; }
+ size_t resultSize() const { return _resultSize; }
+
+ bool matrixHasCommonDimensionInnermost() const { return _commonDimensionInnermost; }
+
+ const eval::Value &eval(ConstArrayRef<eval::Value::CREF> params, Stash &stash) const override;
+};
+
+} // namespace tensor
+} // namespace vespalib
diff --git a/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp b/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp
index c7be1580f19..94ca309ecbc 100644
--- a/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp
+++ b/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp
@@ -75,7 +75,7 @@ SlimeBinaryFormatSerializer::visit(const TensorAddress &address,
void
SlimeBinaryFormatSerializer::serialize(const Tensor &tensor)
{
- eval::ValueType type(tensor.getType());
+ eval::ValueType type(tensor.type());
for (const auto & dimension : type.dimensions()) {
_dimensions.addString(Memory(dimension.name));
}
diff --git a/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp b/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp
index bcb95eeed29..bd0c5b25f93 100644
--- a/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp
+++ b/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp
@@ -77,7 +77,7 @@ void
SparseBinaryFormatSerializer::serialize(nbostream &stream,
const Tensor &tensor)
{
- _type = tensor.getType();
+ _type = tensor.type();
tensor.accept(*this);
stream.putInt1_4Bytes(_type.dimensions().size());
for (const auto &dimension : _type.dimensions()) {
diff --git a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp
index 84aab8826c3..4762f1eceb4 100644
--- a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp
+++ b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp
@@ -33,27 +33,6 @@ copyCells(Cells &cells, const Cells &cells_in, Stash &stash)
}
}
-void
-printAddress(std::ostream &out, const SparseTensorAddressRef &ref,
- const eval::ValueType &type)
-{
- out << "{";
- bool first = true;
- SparseTensorAddressDecoder addr(ref);
- for (auto &dim : type.dimensions()) {
- auto label = addr.decodeLabel();
- if (label.size() != 0u) {
- if (!first) {
- out << ",";
- }
- out << dim.name << ":" << label;
- first = false;
- }
- }
- assert(!addr.valid());
- out << "}";
-}
-
}
SparseTensor::SparseTensor(const eval::ValueType &type_in,
@@ -98,13 +77,13 @@ SparseTensor::combineDimensionsWith(const SparseTensor &rhs) const
}
const eval::ValueType &
-SparseTensor::getType() const
+SparseTensor::type() const
{
return _type;
}
double
-SparseTensor::sum() const
+SparseTensor::as_double() const
{
double result = 0.0;
for (const auto &cell : _cells) {
@@ -114,84 +93,11 @@ SparseTensor::sum() const
}
Tensor::UP
-SparseTensor::add(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
- { return lhsValue + rhsValue; });
-}
-
-Tensor::UP
-SparseTensor::subtract(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
- { return lhsValue - rhsValue; });
-}
-
-Tensor::UP
-SparseTensor::multiply(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
- { return lhsValue * rhsValue; });
-}
-
-Tensor::UP
-SparseTensor::min(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
- { return std::min(lhsValue, rhsValue); });
-}
-
-Tensor::UP
-SparseTensor::max(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
- { return std::max(lhsValue, rhsValue); });
-}
-
-Tensor::UP
-SparseTensor::match(const Tensor &arg) const
-{
- const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg);
- if (!rhs) {
- return Tensor::UP();
- }
- return SparseTensorMatch(*this, *rhs).result();
-}
-
-Tensor::UP
SparseTensor::apply(const CellFunction &func) const
{
return TensorApply<SparseTensor>(*this, func).result();
}
-Tensor::UP
-SparseTensor::sum(const vespalib::string &dimension) const
-{
- return sparse::reduce(*this, { dimension },
- [](double lhsValue, double rhsValue)
- { return lhsValue + rhsValue; });
-}
-
bool
SparseTensor::equals(const Tensor &arg) const
{
@@ -202,14 +108,6 @@ SparseTensor::equals(const Tensor &arg) const
return *this == *rhs;
}
-vespalib::string
-SparseTensor::toString() const
-{
- std::ostringstream stream;
- stream << *this;
- return stream.str();
-}
-
Tensor::UP
SparseTensor::clone() const
{
@@ -235,7 +133,7 @@ buildAddress(const eval::ValueType &type,
TensorSpec
SparseTensor::toSpec() const
{
- TensorSpec result(getType().to_spec());
+ TensorSpec result(type().to_spec());
TensorSpec::Address address;
for (const auto &cell : _cells) {
SparseTensorAddressDecoder decoder(cell.first);
@@ -250,22 +148,6 @@ SparseTensor::toSpec() const
}
void
-SparseTensor::print(std::ostream &out) const
-{
- out << "{ ";
- bool first = true;
- for (const auto &cell : cells()) {
- if (!first) {
- out << ", ";
- }
- printAddress(out, cell.first, _type);
- out << ":" << cell.second;
- first = false;
- }
- out << " }";
-}
-
-void
SparseTensor::accept(TensorVisitor &visitor) const
{
TensorAddressBuilder addrBuilder;
@@ -292,6 +174,14 @@ SparseTensor::join(join_fun_t function, const Tensor &arg) const
if (!rhs) {
return Tensor::UP();
}
+ if (function == eval::operation::Mul::f) {
+ if (fast_type() == rhs->fast_type()) {
+ return SparseTensorMatch(*this, *rhs).result();
+ } else {
+ return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue)
+ { return lhsValue * rhsValue; });
+ }
+ }
return sparse::apply(*this, *rhs, function);
}
diff --git a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h
index 8f5f8066352..c7c38f0a182 100644
--- a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h
+++ b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h
@@ -41,24 +41,15 @@ public:
bool operator==(const SparseTensor &rhs) const;
eval::ValueType combineDimensionsWith(const SparseTensor &rhs) const;
- virtual const eval::ValueType &getType() const override;
- virtual double sum() const override;
- virtual Tensor::UP add(const Tensor &arg) const override;
- virtual Tensor::UP subtract(const Tensor &arg) const override;
- virtual Tensor::UP multiply(const Tensor &arg) const override;
- virtual Tensor::UP min(const Tensor &arg) const override;
- virtual Tensor::UP max(const Tensor &arg) const override;
- virtual Tensor::UP match(const Tensor &arg) const override;
+ virtual const eval::ValueType &type() const override;
+ virtual double as_double() const override;
virtual Tensor::UP apply(const CellFunction &func) const override;
- virtual Tensor::UP sum(const vespalib::string &dimension) const override;
virtual Tensor::UP join(join_fun_t function,
const Tensor &arg) const override;
virtual Tensor::UP reduce(join_fun_t op,
const std::vector<vespalib::string> &dimensions)
const override;
virtual bool equals(const Tensor &arg) const override;
- virtual void print(std::ostream &out) const override;
- virtual vespalib::string toString() const override;
virtual Tensor::UP clone() const override;
virtual eval::TensorSpec toSpec() const override;
virtual void accept(TensorVisitor &visitor) const override;
diff --git a/eval/src/vespa/eval/tensor/tensor.cpp b/eval/src/vespa/eval/tensor/tensor.cpp
index bbf8dd5017c..8715a864f68 100644
--- a/eval/src/vespa/eval/tensor/tensor.cpp
+++ b/eval/src/vespa/eval/tensor/tensor.cpp
@@ -30,7 +30,7 @@ Tensor::supported(TypeList types)
std::ostream &
operator<<(std::ostream &out, const Tensor &value)
{
- value.print(out);
+ out << value.toSpec().to_string();
return out;
}
diff --git a/eval/src/vespa/eval/tensor/tensor.h b/eval/src/vespa/eval/tensor/tensor.h
index 80afbbf52ff..8e31448e026 100644
--- a/eval/src/vespa/eval/tensor/tensor.h
+++ b/eval/src/vespa/eval/tensor/tensor.h
@@ -30,27 +30,11 @@ struct Tensor : public eval::Tensor
Tensor();
virtual ~Tensor() {}
- virtual const eval::ValueType &getType() const = 0;
- virtual const eval::ValueType &type() const override { return getType(); }
- virtual double sum() const = 0;
- virtual double as_double() const final override { return sum(); }
- virtual Tensor::UP add(const Tensor &arg) const = 0;
- virtual Tensor::UP subtract(const Tensor &arg) const = 0;
- virtual Tensor::UP multiply(const Tensor &arg) const = 0;
- virtual Tensor::UP min(const Tensor &arg) const = 0;
- virtual Tensor::UP max(const Tensor &arg) const = 0;
- virtual Tensor::UP match(const Tensor &arg) const = 0;
virtual Tensor::UP apply(const CellFunction &func) const = 0;
- virtual Tensor::UP sum(const vespalib::string &dimension) const = 0;
- virtual Tensor::UP join(join_fun_t function,
- const Tensor &arg) const = 0;
- virtual Tensor::UP reduce(join_fun_t op,
- const std::vector<vespalib::string> &dimensions)
- const = 0;
- virtual bool equals(const Tensor &arg) const = 0;
- virtual void print(std::ostream &out) const = 0;
- virtual vespalib::string toString() const = 0;
- virtual Tensor::UP clone() const = 0;
+ virtual Tensor::UP join(join_fun_t function, const Tensor &arg) const = 0;
+ virtual Tensor::UP reduce(join_fun_t op, const std::vector<vespalib::string> &dimensions) const = 0;
+ virtual bool equals(const Tensor &arg) const = 0; // want to remove, but needed by document
+ virtual Tensor::UP clone() const = 0; // want to remove, but needed by document
virtual eval::TensorSpec toSpec() const = 0;
virtual void accept(TensorVisitor &visitor) const = 0;
diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp
index 7ad97a6e84e..463105b7c1f 100644
--- a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp
+++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp
@@ -17,12 +17,6 @@ WrappedSimpleTensor::equals(const Tensor &arg) const
return (lhs_spec == rhs_spec);
}
-vespalib::string
-WrappedSimpleTensor::toString() const
-{
- return toSpec().to_string();
-}
-
eval::TensorSpec
WrappedSimpleTensor::toSpec() const
{
@@ -30,13 +24,9 @@ WrappedSimpleTensor::toSpec() const
}
double
-WrappedSimpleTensor::sum() const
+WrappedSimpleTensor::as_double() const
{
- double result = 0.0;
- for (const auto &cell: _tensor.cells()) {
- result += cell.value;
- }
- return result;
+ return _tensor.as_double();
}
void
@@ -57,12 +47,6 @@ WrappedSimpleTensor::accept(TensorVisitor &visitor) const
}
}
-void
-WrappedSimpleTensor::print(std::ostream &out) const
-{
- out << toString();
-}
-
Tensor::UP
WrappedSimpleTensor::clone() const
{
@@ -73,48 +57,6 @@ WrappedSimpleTensor::clone() const
//-----------------------------------------------------------------------------
Tensor::UP
-WrappedSimpleTensor::add(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
-WrappedSimpleTensor::subtract(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
-WrappedSimpleTensor::multiply(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
-WrappedSimpleTensor::min(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
-WrappedSimpleTensor::max(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
-WrappedSimpleTensor::match(const Tensor &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
WrappedSimpleTensor::apply(const CellFunction &) const
{
abort();
@@ -122,13 +64,6 @@ WrappedSimpleTensor::apply(const CellFunction &) const
}
Tensor::UP
-WrappedSimpleTensor::sum(const vespalib::string &) const
-{
- abort();
- return Tensor::UP();
-}
-
-Tensor::UP
WrappedSimpleTensor::join(join_fun_t, const Tensor &) const
{
abort();
diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h
index ef3cb6425c1..ae7907845e1 100644
--- a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h
+++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h
@@ -28,23 +28,14 @@ public:
: _space(std::move(tensor)), _tensor(*_space) {}
~WrappedSimpleTensor() {}
const eval::SimpleTensor &get() const { return _tensor; }
- const eval::ValueType &getType() const override { return _tensor.type(); }
+ const eval::ValueType &type() const override { return _tensor.type(); }
bool equals(const Tensor &arg) const override;
- vespalib::string toString() const override;
eval::TensorSpec toSpec() const override;
- double sum() const override;
+ double as_double() const override;
void accept(TensorVisitor &visitor) const override;
- void print(std::ostream &out) const override;
Tensor::UP clone() const override;
// functions below should not be used for this implementation
- Tensor::UP add(const Tensor &) const override;
- Tensor::UP subtract(const Tensor &) const override;
- Tensor::UP multiply(const Tensor &) const override;
- Tensor::UP min(const Tensor &) const override;
- Tensor::UP max(const Tensor &) const override;
- Tensor::UP match(const Tensor &) const override;
Tensor::UP apply(const CellFunction &) const override;
- Tensor::UP sum(const vespalib::string &) const override;
Tensor::UP join(join_fun_t, const Tensor &) const override;
Tensor::UP reduce(join_fun_t, const std::vector<vespalib::string> &) const override;
};
diff --git a/fileacquirer/pom.xml b/fileacquirer/pom.xml
index eb040eddffb..6449b8c620f 100644
--- a/fileacquirer/pom.xml
+++ b/fileacquirer/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>fileacquirer</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/filedistribution/OWNERS b/filedistribution/OWNERS
index 31af040f698..2faf7df0593 100644
--- a/filedistribution/OWNERS
+++ b/filedistribution/OWNERS
@@ -1 +1,2 @@
-bratseth
+baldersheim
+hmusum
diff --git a/filedistribution/pom.xml b/filedistribution/pom.xml
index 41622792e43..d9699b700d0 100644
--- a/filedistribution/pom.xml
+++ b/filedistribution/pom.xml
@@ -9,7 +9,8 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
- </parent>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
<artifactId>filedistribution</artifactId>
<version>6-SNAPSHOT</version>
@@ -19,11 +20,6 @@
<dependencies>
<dependency>
<groupId>com.yahoo.vespa</groupId>
- <artifactId>config-lib</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>com.yahoo.vespa</groupId>
<artifactId>vespajlib</artifactId>
<version>${project.version}</version>
</dependency>
@@ -58,6 +54,10 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java
new file mode 100644
index 00000000000..2c08f5a7605
--- /dev/null
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java
@@ -0,0 +1,118 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.filedistribution;
+
+import com.google.common.io.ByteStreams;
+import com.yahoo.log.LogLevel;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Utility class for compressing and decompressing files used in a file reference
+ *
+ * @author hmusum
+ */
+public class CompressedFileReference {
+
+ private static final Logger log = Logger.getLogger(CompressedFileReference.class.getName());
+ private static final int recurseDepth = 100;
+
+ public static File compress(File baseDir, List<File> inputFiles, File outputFile) throws IOException {
+ ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outputFile)));
+ createArchiveFile(archiveOutputStream, baseDir, inputFiles);
+ return outputFile;
+ }
+
+ public static File compress(File directory, File outputFile) throws IOException {
+ return compress(directory, Files.find(Paths.get(directory.getAbsolutePath()),
+ recurseDepth,
+ (p, basicFileAttributes) -> basicFileAttributes.isRegularFile())
+ .map(Path::toFile).collect(Collectors.toList()), outputFile);
+ }
+
+ public static byte[] compress(File directory) throws IOException {
+ return compress(directory, Files.find(Paths.get(directory.getAbsolutePath()),
+ recurseDepth,
+ (p, basicFileAttributes) -> basicFileAttributes.isRegularFile())
+ .map(Path::toFile).collect(Collectors.toList()));
+ }
+
+ public static byte[] compress(File baseDir, List<File> inputFiles) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(out));
+ createArchiveFile(archiveOutputStream, baseDir, inputFiles);
+ return out.toByteArray();
+ }
+
+ static void decompress(File inputFile, File outputDir) throws IOException {
+ log.log(LogLevel.DEBUG, "Decompressing '" + inputFile + "' into '" + outputDir + "'");
+ ArchiveInputStream ais = new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(inputFile)));
+ decompress(ais, outputDir);
+ ais.close();
+ }
+
+ private static void decompress(ArchiveInputStream archiveInputStream, File outputFile) throws IOException {
+ int entries = 0;
+ ArchiveEntry entry;
+ while ((entry = archiveInputStream.getNextEntry()) != null) {
+ log.log(LogLevel.DEBUG, "Unpacking " + entry.getName());
+ File outFile = new File(outputFile, entry.getName());
+ if (entry.isDirectory()) {
+ if (!(outFile.exists() && outFile.isDirectory())) {
+ log.log(LogLevel.DEBUG, "Creating dir: " + outFile.getAbsolutePath());
+ if (!outFile.mkdirs()) {
+ log.log(LogLevel.WARNING, "Could not create dir " + entry.getName());
+ }
+ }
+ } else {
+ // Create parent dir if necessary
+ File parent = new File(outFile.getParent());
+ if (!parent.exists() && !parent.mkdirs()) {
+ log.log(LogLevel.WARNING, "Could not create dir " + parent.getAbsolutePath());
+ }
+ FileOutputStream fos = new FileOutputStream(outFile);
+ ByteStreams.copy(archiveInputStream, fos);
+ fos.close();
+ }
+ entries++;
+ }
+ if (entries == 0) {
+ log.log(LogLevel.WARNING, "Not able to read any entries from " + outputFile.getName());
+ }
+ }
+
+ private static void createArchiveFile(ArchiveOutputStream archiveOutputStream, File baseDir, List<File> inputFiles) throws IOException {
+ inputFiles.forEach(file -> {
+ try {
+ writeFileToTar(archiveOutputStream, baseDir, file);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ archiveOutputStream.close();
+ }
+
+ private static void writeFileToTar(ArchiveOutputStream taos, File baseDir, File file) throws IOException {
+ log.log(LogLevel.DEBUG, "Adding file to tar: " + baseDir.toPath().relativize(file.toPath()).toString());
+ taos.putArchiveEntry(taos.createArchiveEntry(file, baseDir.toPath().relativize(file.toPath()).toString()));
+ ByteStreams.copy(new FileInputStream(file), taos);
+ taos.closeArchiveEntry();
+ }
+}
+
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java
index d09cf17b9e3..2e58455bc39 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java
@@ -118,7 +118,7 @@ public class FileDistributionRpcServer {
List<FileReference> fileReferences = Stream.of(fileReferenceStrings)
.map(FileReference::new)
.collect(Collectors.toList());
- downloader.queueForDownload(fileReferences);
+ downloader.queueForAsyncDownload(fileReferences);
req.returnValues().add(new Int32Value(0));
}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java
index cd4b3afb9b5..727786cdc78 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java
@@ -1,13 +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.filedistribution;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.yahoo.config.FileReference;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.config.ConnectionPool;
import com.yahoo.vespa.defaults.Defaults;
-import com.yahoo.yolean.Exceptions;
import java.io.File;
import java.time.Duration;
@@ -16,8 +14,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
@@ -37,37 +35,55 @@ public class FileDownloader {
public FileDownloader(ConnectionPool connectionPool) {
this(connectionPool,
- new File(Defaults.getDefaults().underVespaHome("var/db/vespa/filedistribution")),
- Duration.ofMinutes(15));
+ new File(Defaults.getDefaults().underVespaHome("var/db/vespa/filedistribution")),
+ new File(Defaults.getDefaults().underVespaHome("tmp")),
+ Duration.ofMinutes(15));
}
- FileDownloader(ConnectionPool connectionPool, File downloadDirectory, Duration timeout) {
+ FileDownloader(ConnectionPool connectionPool, File downloadDirectory, File tmpDirectory, Duration timeout) {
this.downloadDirectory = downloadDirectory;
this.timeout = timeout;
- this.fileReferenceDownloader = new FileReferenceDownloader(downloadDirectory, connectionPool, timeout);
+ this.fileReferenceDownloader = new FileReferenceDownloader(downloadDirectory, tmpDirectory, connectionPool, timeout);
}
public Optional<File> getFile(FileReference fileReference) {
+ try {
+ return getFutureFile(fileReference).get(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ return Optional.empty();
+ }
+ }
+
+ private Future<Optional<File>> getFutureFile(FileReference fileReference) {
Objects.requireNonNull(fileReference, "file reference cannot be null");
File directory = new File(downloadDirectory, fileReference.value());
log.log(LogLevel.DEBUG, "Checking if there is a file in '" + directory.getAbsolutePath() + "' ");
Optional<File> file = getFileFromFileSystem(fileReference, directory);
if (file.isPresent()) {
- return file;
+ SettableFuture<Optional<File>> future = SettableFuture.create();
+ future.set(file);
+ return future;
} else {
log.log(LogLevel.INFO, "File reference '" + fileReference.value() + "' not found in " +
directory.getAbsolutePath() + ", starting download");
- return queueForDownload(fileReference, timeout);
+ return queueForAsyncDownload(fileReference, timeout);
}
}
- public void queueForDownload(List<FileReference> fileReferences) {
- fileReferences.forEach(this::queueForDownload);
+ // Start downloading, but there is no Future used get file being downloaded
+ public void queueForAsyncDownload(List<FileReference> fileReferences) {
+ fileReferences.forEach(fileReference -> {
+ if (fileReferenceDownloader.isDownloading(fileReference)) {
+ log.log(LogLevel.DEBUG, "Already downloading '" + fileReference.value() + "'");
+ } else {
+ queueForAsyncDownload(fileReference);
+ }
+ });
}
- public void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) {
- fileReferenceDownloader.receiveFile(fileReference, filename, content, xxHash);
+ void receiveFile(FileReferenceData fileReferenceData) {
+ fileReferenceDownloader.receiveFile(fileReferenceData);
}
double downloadStatus(FileReference fileReference) {
@@ -85,17 +101,11 @@ public class FileDownloader {
private Optional<File> getFileFromFileSystem(FileReference fileReference, File directory) {
File[] files = directory.listFiles();
if (directory.exists() && directory.isDirectory() && files != null && files.length > 0) {
- if (files.length != 1) {
- throw new RuntimeException("More than one file in '" + fileReference.value() +
- "', expected only one, unable to proceed");
- }
File file = files[0];
if (!file.exists()) {
- throw new RuntimeException("File with reference '" + fileReference.value() +
- "' does not exist");
+ throw new RuntimeException("File with reference '" + fileReference.value() + "' does not exist");
} else if (!file.canRead()) {
- throw new RuntimeException("File with reference '" + fileReference.value() +
- "'exists, but unable to read it");
+ throw new RuntimeException("File with reference '" + fileReference.value() + "'exists, but unable to read it");
} else {
fileReferenceDownloader.setDownloadStatus(fileReference.value(), 100.0);
return Optional.of(file);
@@ -104,50 +114,25 @@ public class FileDownloader {
return Optional.empty();
}
- private synchronized Optional<File> queueForDownload(FileReference fileReference, Duration timeout) {
- if (fileReferenceDownloader.isDownloading(fileReference)) {
- log.log(LogLevel.INFO, "Already downloading '" + fileReference.value() + "'");
- ListenableFuture<Optional<File>> future =
- fileReferenceDownloader.addDownloadListener(fileReference, () -> getFile(fileReference));
- try {
- return future.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException("Failed downloading file reference '" + fileReference.value() + "': " +
- Exceptions.toMessageString(e));
- }
+ private synchronized Future<Optional<File>> queueForAsyncDownload(FileReference fileReference, Duration timeout) {
+ Future<Optional<File>> inProgress = fileReferenceDownloader.addDownloadListener(fileReference, () -> getFile(fileReference));
+ if (inProgress != null) {
+ log.log(LogLevel.DEBUG, "Already downloading '" + fileReference.value() + "'");
+ return inProgress;
}
- SettableFuture<Optional<File>> future = SettableFuture.create();
- queueForDownload(new FileReferenceDownload(fileReference, future));
+ Future<Optional<File>> future = queueForAsyncDownload(fileReference);
log.log(LogLevel.INFO, "Queued '" + fileReference.value() + "' for download with timeout " + timeout);
-
- try {
- Optional<File> fileDownloaded;
- try {
- log.log(LogLevel.INFO, "Waiting for '" + fileReference.value() + "' to download");
- fileDownloaded = future.get(timeout.getSeconds() - 1, TimeUnit.SECONDS);
- log.log(LogLevel.INFO, "'" + fileReference.value() + "' downloaded");
- } catch (TimeoutException e) {
- log.log(LogLevel.WARNING, "Downloading '" + fileReference.value() + "' timed out");
- return Optional.empty();
- }
- return fileDownloaded;
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException("Could not download '" + fileReference.value() + "'");
- }
+ return future;
}
- // We don't care about the future in this call
- private synchronized void queueForDownload(FileReference fileReference) {
- queueForDownload(new FileReferenceDownload(fileReference, SettableFuture.create()));
- }
-
- private synchronized void queueForDownload(FileReferenceDownload fileReferenceDownload) {
+ private Future<Optional<File>> queueForAsyncDownload(FileReference fileReference) {
+ FileReferenceDownload fileReferenceDownload = new FileReferenceDownload(fileReference, SettableFuture.create());
fileReferenceDownloader.addToDownloadQueue(fileReferenceDownload);
+ return fileReferenceDownload.future();
}
- Set<FileReference> queuedDownloads() {
- return fileReferenceDownloader.queuedDownloads();
+ public FileReferenceDownloader fileReferenceDownloader() {
+ return fileReferenceDownloader;
}
-
}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
index 3999389d2c7..d57ce4ca5de 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
@@ -1,4 +1,4 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.filedistribution;
@@ -8,49 +8,157 @@ import com.yahoo.jrt.Method;
import com.yahoo.jrt.Request;
import com.yahoo.jrt.Supervisor;
import com.yahoo.log.LogLevel;
-import net.jpountz.xxhash.XXHash64;
+import net.jpountz.xxhash.StreamingXXHash64;
import net.jpountz.xxhash.XXHashFactory;
import java.io.File;
import java.io.IOException;
-import java.nio.ByteBuffer;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
+/**
+ * When asking for a file reference, this handles RPC callbacks from config server with file data and metadata.
+ * Uses the same Supervisor as the original caller that requests files, so communication uses the same
+ * connection in both directions.
+ *
+ * @author baldersheim
+ */
public class FileReceiver {
private final static Logger log = Logger.getLogger(FileReceiver.class.getName());
- private final static String RECEIVE_METHOD = "filedistribution.receiveFile";
- private final static String RECEIVE_META_METHOD = "filedistribution.receiveFileMeta";
- private final static String RECEIVE_PART_METHOD = "filedistribution.receiveFilePart";
- private final static String RECEIVE_EOF_METHOD = "filedistribution.receiveFileEof";
+ public final static String RECEIVE_METHOD = "filedistribution.receiveFile";
+ public final static String RECEIVE_META_METHOD = "filedistribution.receiveFileMeta";
+ public final static String RECEIVE_PART_METHOD = "filedistribution.receiveFilePart";
+ public final static String RECEIVE_EOF_METHOD = "filedistribution.receiveFileEof";
private final Supervisor supervisor;
private final FileReferenceDownloader downloader;
private final File downloadDirectory;
- private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64();
+ // Should be on same partition as downloadDirectory to make sure moving files from tmpDirectory
+ // to downloadDirectory is atomic
+ private final File tmpDirectory;
+ private final AtomicInteger nextSessionId = new AtomicInteger(1);
+ private final Map<Integer, Session> sessions = new HashMap<>();
- public FileReceiver(Supervisor supervisor, FileReferenceDownloader downloader, File downloadDirectory) {
+ final static class Session {
+ private final StreamingXXHash64 hasher;
+ private final int sessionId;
+ private final FileReference reference;
+ private final FileReferenceData.Type fileType;
+ private final String fileName;
+ private final long fileSize;
+ private long currentFileSize;
+ private long currentPartId;
+ private long currentHash;
+ private final File fileReferenceDir;
+ private final File tmpDir;
+ private final File inprogressFile;
+
+ Session(File downloadDirectory, File tmpDirectory, int sessionId, FileReference reference,
+ FileReferenceData.Type fileType, String fileName, long fileSize)
+ {
+ this.hasher = XXHashFactory.fastestInstance().newStreamingHash64(0);
+ this.sessionId = sessionId;
+ this.reference = reference;
+ this.fileType = fileType;
+ this.fileName = fileName;
+ this.fileSize = fileSize;
+ currentFileSize = 0;
+ currentPartId = 0;
+ currentHash = 0;
+ fileReferenceDir = new File(downloadDirectory, reference.value());
+ this.tmpDir = tmpDirectory;
+ try {
+ Files.createDirectories(fileReferenceDir.toPath());
+ } catch (IOException e) {
+ log.log(LogLevel.ERROR, "Failed creating directory(" + fileReferenceDir.toPath() + "): " + e.getMessage(), e);
+ throw new RuntimeException("Failed creating directory(" + fileReferenceDir.toPath() + "): ", e);
+ }
+
+ try {
+ inprogressFile = Files.createTempFile(tmpDirectory.toPath(), fileName, ".inprogress").toFile();
+ } catch (IOException e) {
+ String msg = "Failed creating tempfile for inprogress file for(" + fileName + ") in '" + fileReferenceDir.toPath() + "': ";
+ log.log(LogLevel.ERROR, msg + e.getMessage(), e);
+ throw new RuntimeException(msg, e);
+ }
+ }
+
+ void addPart(int partId, byte [] part) {
+ if (partId != currentPartId) {
+ throw new IllegalStateException("Received partid " + partId + " while expecting " + currentPartId);
+ }
+ if (fileSize < currentFileSize + part.length) {
+ throw new IllegalStateException("Received part would extend the file from " + currentFileSize + " to " +
+ (currentFileSize + part.length) + ", but " + fileSize + " is max.");
+ }
+ try {
+ Files.write(inprogressFile.toPath(), part, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
+ } catch (IOException e) {
+ log.log(LogLevel.ERROR, "Failed writing to file(" + inprogressFile.toPath() + "): " + e.getMessage(), e);
+ throw new RuntimeException("Failed writing to file(" + inprogressFile.toPath() + "): ", e);
+ }
+ currentFileSize += part.length;
+ currentPartId++;
+ hasher.update(part, 0, part.length);
+ }
+
+ File close(long hash) {
+ if (hasher.getValue() != hash) {
+ throw new RuntimeException("xxhash from content (" + currentHash + ") is not equal to xxhash in request (" + hash + ")");
+ }
+ File file = new File(fileReferenceDir, fileName);
+ try {
+ // Unpack if necessary
+ if (fileType == FileReferenceData.Type.compressed) {
+ File decompressedDir = Files.createTempDirectory(tmpDir.toPath(), "archive").toFile();
+ log.log(LogLevel.DEBUG, "Archived file, unpacking " + inprogressFile + " to " + decompressedDir);
+ CompressedFileReference.decompress(inprogressFile, decompressedDir);
+ moveFileToDestination(decompressedDir, fileReferenceDir);
+ } else {
+ log.log(LogLevel.DEBUG, "Uncompressed file, moving to " + file.getAbsolutePath());
+ moveFileToDestination(inprogressFile, file);
+ }
+ } catch (IOException e) {
+ log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage(), e);
+ throw new RuntimeException("Failed writing file: ", e);
+ }
+ return file;
+ }
+
+ double percentageReceived() {
+ return (double)currentFileSize/(double)fileSize;
+ }
+ }
+
+ FileReceiver(Supervisor supervisor, FileReferenceDownloader downloader, File downloadDirectory, File tmpDirectory) {
this.supervisor = supervisor;
this.downloader = downloader;
this.downloadDirectory = downloadDirectory;
+ this.tmpDirectory = tmpDirectory;
registerMethods();
}
private void registerMethods() {
- receiveFileMethod(this).forEach((method) -> supervisor.addMethod(method));
+ receiveFileMethod(this).forEach(supervisor::addMethod);
}
// Defined here so that it can be added to supervisor used by client (server will use same connection when calling
// receiveFile after getting a serveFile method call). handler needs to implement receiveFile method
private List<Method> receiveFileMethod(Object handler) {
List<Method> methods = new ArrayList<>();
- methods.add(new Method(RECEIVE_META_METHOD, "ssl", "ii", handler,"receiveFileMeta")
+ methods.add(new Method(RECEIVE_META_METHOD, "sssl", "ii", handler,"receiveFileMeta")
.paramDesc(0, "filereference", "file reference to download")
.paramDesc(1, "filename", "filename")
- .paramDesc(2, "filelength", "length in bytes of file")
+ .paramDesc(2, "type", "'file' or 'compressed'")
+ .paramDesc(3, "filelength", "length in bytes of file")
.returnDesc(0, "ret", "0 if success, 1 otherwise")
.returnDesc(1, "session-id", "Session id to be used for this transfer"));
methods.add(new Method(RECEIVE_PART_METHOD, "siix", "i", handler,"receiveFilePart")
@@ -67,14 +175,15 @@ public class FileReceiver {
.paramDesc(4, "error-description", "Error description.")
.returnDesc(0, "ret", "0 if success, 1 if crc mismatch, 2 otherwise"));
// Temporary method until we have chunking
- methods.add(new Method(RECEIVE_METHOD, "ssxlis", "i", handler, "receiveFile")
+ methods.add(new Method(RECEIVE_METHOD, "sssxlis", "i", handler, "receiveFile")
.methodDesc("receive file reference content")
.paramDesc(0, "file reference", "file reference to download")
.paramDesc(1, "filename", "filename")
- .paramDesc(2, "content", "array of bytes")
- .paramDesc(3, "hash", "xx64hash of the file content")
- .paramDesc(4, "errorcode", "Error code. 0 if none")
- .paramDesc(5, "error-description", "Error description.")
+ .paramDesc(2, "type", "'file' or 'compressed'")
+ .paramDesc(3, "content", "array of bytes")
+ .paramDesc(4, "hash", "xx64hash of the file content")
+ .paramDesc(5, "errorcode", "Error code. 0 if none")
+ .paramDesc(6, "error-description", "Error description.")
.returnDesc(0, "ret", "0 if success, 1 otherwise"));
return methods;
}
@@ -83,15 +192,15 @@ public class FileReceiver {
public final void receiveFile(Request req) {
FileReference fileReference = new FileReference(req.parameters().get(0).asString());
String filename = req.parameters().get(1).asString();
- byte[] content = req.parameters().get(2).asData();
- long xxhash = req.parameters().get(3).asInt64();
- int errorCode = req.parameters().get(4).asInt32();
- String errorDescription = req.parameters().get(5).asString();
+ String type = req.parameters().get(2).asString();
+ byte[] content = req.parameters().get(3).asData();
+ long xxhash = req.parameters().get(4).asInt64();
+ int errorCode = req.parameters().get(5).asInt32();
+ String errorDescription = req.parameters().get(6).asString();
if (errorCode == 0) {
- // TODO: Remove when system test works
- log.log(LogLevel.INFO, "Receiving file reference '" + fileReference.value() + "'");
- receiveFile(fileReference, filename, content, xxhash);
+ log.log(LogLevel.DEBUG, "Receiving file reference '" + fileReference.value() + "'");
+ receiveFile(new FileReferenceDataBlob(fileReference, filename, FileReferenceData.Type.valueOf(type), content, xxhash));
req.returnValues().add(new Int32Value(0));
} else {
log.log(LogLevel.WARNING, "Receiving file reference '" + fileReference.value() + "' failed: " + errorDescription);
@@ -100,34 +209,131 @@ public class FileReceiver {
}
}
- void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) {
- long xxHashFromContent = hasher.hash(ByteBuffer.wrap(content), 0);
- if (xxHashFromContent != xxHash)
- throw new RuntimeException("xxhash from content (" + xxHashFromContent + ") is not equal to xxhash in request (" + xxHash + ")");
+ void receiveFile(FileReferenceData fileReferenceData) {
+ long xxHashFromContent = fileReferenceData.xxhash();
+ if (xxHashFromContent != fileReferenceData.xxhash()) {
+ throw new RuntimeException("xxhash from content (" + xxHashFromContent + ") is not equal to xxhash in request (" + fileReferenceData.xxhash() + ")");
+ }
- File fileReferenceDir = new File(downloadDirectory, fileReference.value());
+ File fileReferenceDir = new File(downloadDirectory, fileReferenceData.fileReference().value());
+ // file might be a directory (and then type is compressed)
+ File file = new File(fileReferenceDir, fileReferenceData.filename());
try {
- Files.createDirectories(fileReferenceDir.toPath());
- File file = new File(fileReferenceDir, filename);
- log.log(LogLevel.INFO, "Writing data to " + file.getAbsolutePath());
- Files.write(file.toPath(), content);
- downloader.completedDownloading(fileReference, file);
+ File tempDownloadedDir = Files.createTempDirectory(tmpDirectory.toPath(), "downloaded").toFile();
+ File tempFile = new File(tempDownloadedDir, fileReferenceData.filename());
+ Files.write(tempFile.toPath(), fileReferenceData.content().array());
+
+ // Unpack if necessary
+ if (fileReferenceData.type() == FileReferenceData.Type.compressed) {
+ File decompressedDir = Files.createTempDirectory(tempDownloadedDir.toPath(), "decompressed").toFile();
+ log.log(LogLevel.DEBUG, "Compressed file, unpacking " + tempFile + " to " + decompressedDir);
+ CompressedFileReference.decompress(tempFile, decompressedDir);
+ moveFileToDestination(decompressedDir, fileReferenceDir);
+ } else {
+ log.log(LogLevel.DEBUG, "Uncompressed file, moving to " + file.getAbsolutePath());
+ Files.createDirectories(fileReferenceDir.toPath());
+ moveFileToDestination(tempFile, file);
+ }
+ downloader.completedDownloading(fileReferenceData.fileReference(), file);
} catch (IOException e) {
- log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage());
+ log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage(), e);
throw new RuntimeException("Failed writing file: ", e);
}
}
+ private static void moveFileToDestination(File tempFile, File destination) {
+ try {
+ Files.move(tempFile.toPath(), destination.toPath());
+ log.log(LogLevel.DEBUG, "File moved from " + tempFile.getAbsolutePath()+ " to " + destination.getAbsolutePath());
+ } catch (FileAlreadyExistsException e) {
+ // Don't fail if it already exists (we might get the file from several config servers when retrying, servers are down etc.
+ // so it might be written already)
+ log.log(LogLevel.DEBUG, "File '" + destination.getAbsolutePath() + "' already exists, continuing: " + e.getMessage());
+ } catch (IOException e) {
+ String message = "Failed moving file '" + tempFile.getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'";
+ log.log(LogLevel.ERROR, message, e);
+ throw new RuntimeException(message, e);
+ }
+ }
+
@SuppressWarnings({"UnusedDeclaration"})
public final void receiveFileMeta(Request req) {
- log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ FileReference reference = new FileReference(req.parameters().get(0).asString());
+ String fileName = req.parameters().get(1).asString();
+ String type = req.parameters().get(2).asString();
+ long fileSize = req.parameters().get(3).asInt64();
+ int sessionId = nextSessionId.getAndIncrement();
+ int retval = 0;
+ synchronized (sessions) {
+ if (sessions.containsKey(sessionId)) {
+ retval = 1;
+ log.severe("Session id " + sessionId + " already exist, impossible. Request from(" + req.target() + ")");
+ } else {
+ try {
+ sessions.put(sessionId, new Session(downloadDirectory, tmpDirectory, sessionId, reference,
+ FileReferenceData.Type.valueOf(type),fileName, fileSize));
+ } catch (Exception e) {
+ retval = 1;
+ }
+ }
+ }
+ req.returnValues().add(new Int32Value(retval));
+ req.returnValues().add(new Int32Value(sessionId));
}
+
@SuppressWarnings({"UnusedDeclaration"})
public final void receiveFilePart(Request req) {
- log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+
+ FileReference reference = new FileReference(req.parameters().get(0).asString());
+ int sessionId = req.parameters().get(1).asInt32();
+ int partId = req.parameters().get(2).asInt32();
+ byte [] part = req.parameters().get(3).asData();
+ Session session = getSession(sessionId);
+ int retval = verifySession(session, sessionId, reference);
+ try {
+ session.addPart(partId, part);
+ } catch (Exception e) {
+ log.severe("Got exception + " + e);
+ retval = 1;
+ }
+ double completeness = (double) session.currentFileSize / (double) session.fileSize;
+ log.log(LogLevel.DEBUG, String.format("%.1f percent of '%s' downloaded", completeness * 100, reference.value()));
+ downloader.setDownloadStatus(reference, completeness);
+ req.returnValues().add(new Int32Value(retval));
}
+
@SuppressWarnings({"UnusedDeclaration"})
public final void receiveFileEof(Request req) {
- log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ FileReference reference = new FileReference(req.parameters().get(0).asString());
+ int sessionId = req.parameters().get(1).asInt32();
+ long xxhash = req.parameters().get(2).asInt64();
+ Session session = getSession(sessionId);
+ int retval = verifySession(session, sessionId, reference);
+ File file = session.close(xxhash);
+ downloader.completedDownloading(reference, file);
+ synchronized (sessions) {
+ sessions.remove(sessionId);
+ }
+ req.returnValues().add(new Int32Value(retval));
+ }
+
+ private Session getSession(Integer sessionId) {
+ synchronized (sessions) {
+ return sessions.get(sessionId);
+ }
+ }
+ private static int verifySession(Session session, int sessionId, FileReference reference) {
+ if (session == null) {
+ log.severe("session-id " + sessionId + " does not exist.");
+ return 1;
+ }
+ if (! session.reference.equals(reference)) {
+ log.severe("Session " + session.sessionId + " expects reference " + reference.value() + ", but was " + session.reference.value());
+ return 1;
+ }
+ return 0;
}
}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java
new file mode 100644
index 00000000000..dabdba2bfc0
--- /dev/null
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java
@@ -0,0 +1,67 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.filedistribution;
+
+import com.yahoo.config.FileReference;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Utility class for a file reference with data and metadata
+ *
+ * @author hmusum
+ */
+public abstract class FileReferenceData {
+
+ public enum Type {file, compressed}
+
+ private final FileReference fileReference;
+ private final String filename;
+ private final Type type;
+
+ public FileReferenceData(FileReference fileReference, String filename, Type type) {
+ this.fileReference = fileReference;
+ this.filename = filename;
+ this.type = type;
+ }
+
+ public FileReference fileReference() {
+ return fileReference;
+ }
+
+ public String filename() {
+ return filename;
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ public ByteBuffer content() {
+ ByteBuffer bb = ByteBuffer.allocate((int)size());
+ while (bb.remaining() > 0) {
+ nextContent(bb);
+ }
+ return bb;
+ }
+ /**
+ * Will provide the next part of the content.
+ *
+ * @param bb with some available space
+ * @return Number of bytes transferred.
+ */
+ public abstract int nextContent(ByteBuffer bb);
+
+ /**
+ * Only guaranteed to be valid after all content has been consumed.
+ * @return xx64hash of content
+ */
+ public abstract long xxhash();
+
+ /**
+ * The size of the content in bytes
+ *
+ * @return number of bytes
+ */
+ public abstract long size();
+}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java
new file mode 100644
index 00000000000..3759cbe2ef7
--- /dev/null
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java
@@ -0,0 +1,44 @@
+package com.yahoo.vespa.filedistribution;
+
+import com.yahoo.config.FileReference;
+import net.jpountz.xxhash.XXHashFactory;
+
+import java.nio.ByteBuffer;
+
+public class FileReferenceDataBlob extends FileReferenceData {
+ private final byte[] content;
+ private final long xxhash;
+
+ public FileReferenceDataBlob(FileReference fileReference, String filename, Type type, byte[] content) {
+ this(fileReference, filename, type, content, XXHashFactory.fastestInstance().hash64().hash(ByteBuffer.wrap(content), 0));
+ }
+
+ public FileReferenceDataBlob(FileReference fileReference, String filename, Type type, byte[] content, long xxhash) {
+ super(fileReference, filename, type);
+ this.content = content;
+ this.xxhash = xxhash;
+ }
+
+ public static FileReferenceData empty(FileReference fileReference, String filename) {
+ return new FileReferenceDataBlob(fileReference, filename, FileReferenceData.Type.file, new byte[0], 0);
+ }
+
+ public ByteBuffer content() {
+ return ByteBuffer.wrap(content);
+ }
+ @Override
+ public int nextContent(ByteBuffer bb) {
+ bb.put(content);
+ return content.length;
+ }
+
+ @Override
+ public long xxhash() {
+ return xxhash;
+ }
+
+ @Override
+ public long size() {
+ return content.length;
+ }
+}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java
index fb511411128..048287f0892 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java
@@ -8,7 +8,7 @@ import com.yahoo.config.FileReference;
import java.io.File;
import java.util.Optional;
-public class FileReferenceDownload {
+class FileReferenceDownload {
private final FileReference fileReference;
private final SettableFuture<Optional<File>> future;
@@ -20,9 +20,7 @@ public class FileReferenceDownload {
FileReference fileReference() {
return fileReference;
}
-
SettableFuture<Optional<File>> future() {
return future;
}
-
}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
index 08595662f36..509231ba7ff 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
@@ -16,18 +16,11 @@ import java.io.File;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
/**
* Downloads file reference using rpc requests to config server and keeps track of files being downloaded
@@ -37,90 +30,75 @@ import java.util.stream.Collectors;
* @author hmusum
*/
// TODO: Handle shutdown of executors
-class FileReferenceDownloader {
+public class FileReferenceDownloader {
private final static Logger log = Logger.getLogger(FileReferenceDownloader.class.getName());
private final static Duration rpcTimeout = Duration.ofSeconds(10);
private final ExecutorService downloadExecutor =
Executors.newFixedThreadPool(10, new DaemonThreadFactory("filereference downloader"));
- private ExecutorService readFromQueueExecutor =
- Executors.newFixedThreadPool(1, new DaemonThreadFactory("filereference download queue"));
private final ConnectionPool connectionPool;
- private final ConcurrentLinkedQueue<FileReferenceDownload> downloadQueue = new ConcurrentLinkedQueue<>();
private final Map<FileReference, FileReferenceDownload> downloads = new LinkedHashMap<>();
private final Map<FileReference, Double> downloadStatus = new HashMap<>();
private final Duration downloadTimeout;
private final FileReceiver fileReceiver;
- FileReferenceDownloader(File downloadDirectory, ConnectionPool connectionPool, Duration timeout) {
+ FileReferenceDownloader(File downloadDirectory, File tmpDirectory, ConnectionPool connectionPool, Duration timeout) {
this.connectionPool = connectionPool;
this.downloadTimeout = timeout;
- readFromQueueExecutor.submit(this::readFromQueue);
- this.fileReceiver = new FileReceiver(connectionPool.getSupervisor(), this, downloadDirectory);
+ this.fileReceiver = new FileReceiver(connectionPool.getSupervisor(), this, downloadDirectory, tmpDirectory);
}
- private synchronized Optional<File> startDownload(FileReference fileReference,
- Duration timeout,
- FileReferenceDownload fileReferenceDownload)
- throws ExecutionException, InterruptedException, TimeoutException {
- downloads.put(fileReference, fileReferenceDownload);
- setDownloadStatus(fileReference.value(), 0.0);
-
- int numAttempts = 0;
+ private void startDownload(Duration timeout, FileReferenceDownload fileReferenceDownload) {
+ FileReference fileReference = fileReferenceDownload.fileReference();
+ synchronized (downloads) {
+ downloads.put(fileReference, fileReferenceDownload);
+ downloadStatus.put(fileReference, 0.0);
+ }
+ long end = System.currentTimeMillis() + timeout.toMillis();
boolean downloadStarted = false;
- do {
- if (startDownloadRpc(fileReference))
- downloadStarted = true;
- else
- Thread.sleep(100);
- } while (!downloadStarted && ++numAttempts <= 10); // TODO: How long/many times to retry?
-
- if (downloadStarted) {
- return fileReferenceDownload.future().get(timeout.toMillis(), TimeUnit.MILLISECONDS);
- } else {
- fileReferenceDownload.future().setException(new RuntimeException("Failed getting file reference '" + fileReference.value() + "'"));
- downloads.remove(fileReference);
- return Optional.empty();
+ while ((System.currentTimeMillis() < end) && !downloadStarted) {
+ try {
+ if (startDownloadRpc(fileReference)) {
+ downloadStarted = true;
+ } else {
+ Thread.sleep(10);
+ }
+ }
+ catch (InterruptedException e) {}
}
- }
- synchronized void addToDownloadQueue(FileReferenceDownload fileReferenceDownload) {
- downloadQueue.add(fileReferenceDownload);
+ if ( !downloadStarted) {
+ fileReferenceDownload.future().setException(new RuntimeException("Failed getting file reference '" + fileReference.value() + "'"));
+ synchronized (downloads) {
+ downloads.remove(fileReference);
+ }
+ }
}
- void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) {
- fileReceiver.receiveFile(fileReference, filename, content, xxHash);
+ void addToDownloadQueue(FileReferenceDownload fileReferenceDownload) {
+ log.log(LogLevel.DEBUG, "Will download file reference '" + fileReferenceDownload.fileReference().value() + "' with timeout " + downloadTimeout);
+ downloadExecutor.submit(() -> startDownload(downloadTimeout, fileReferenceDownload));
}
- synchronized Set<FileReference> queuedDownloads() {
- return downloadQueue.stream()
- .map(FileReferenceDownload::fileReference)
- .collect(Collectors.toCollection(LinkedHashSet::new));
+ void receiveFile(FileReferenceData fileReferenceData) {
+ fileReceiver.receiveFile(fileReferenceData);
}
- private void readFromQueue() {
- do {
- FileReferenceDownload fileReferenceDownload = downloadQueue.poll();
- if (fileReferenceDownload == null) {
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) { /* ignore for now */}
+ void completedDownloading(FileReference fileReference, File file) {
+ synchronized (downloads) {
+ FileReferenceDownload download = downloads.get(fileReference);
+ if (download != null) {
+ downloadStatus.put(fileReference, 1.0);
+ downloads.remove(fileReference);
+ download.future().set(Optional.of(file));
} else {
- log.log(LogLevel.INFO, "Polling queue, found file reference '" +
- fileReferenceDownload.fileReference().value() + "' to download");
- downloadExecutor.submit(() -> startDownload(fileReferenceDownload.fileReference(), downloadTimeout, fileReferenceDownload));
+ log.log(LogLevel.WARNING, "Received a file " + fileReference + " I did not ask for. Impossible");
}
- } while (true);
- }
-
- void completedDownloading(FileReference fileReference, File file) {
- if (downloads.containsKey(fileReference))
- downloads.get(fileReference).future().set(Optional.of(file));
- downloadStatus.put(fileReference, 100.0);
+ }
}
- private boolean startDownloadRpc(FileReference fileReference) throws ExecutionException, InterruptedException {
+ private boolean startDownloadRpc(FileReference fileReference) {
Connection connection = connectionPool.getCurrent();
Request request = new Request("filedistribution.serveFile");
request.parameters().add(new StringValue(fileReference.value()));
@@ -133,24 +111,35 @@ class FileReferenceDownloader {
return true;
} else {
log.log(LogLevel.INFO, "File reference '" + fileReference.value() + "' not found for " + connection.getAddress());
+ connectionPool.setNewCurrentConnection();
return false;
}
} else {
- log.log(LogLevel.WARNING, "Request failed. Req: " + request + "\nSpec: " + connection.getAddress());
- if (request.isError() && request.errorCode() == ErrorCode.CONNECTION)
- connection.setError(request.errorCode());
+ log.log(LogLevel.WARNING, "Request failed. Req: " + request + "\nSpec: " + connection.getAddress() +
+ ", error code: " + request.errorCode());
+ if (request.isError() && request.errorCode() == ErrorCode.CONNECTION || request.errorCode() == ErrorCode.TIMEOUT) {
+ log.log(LogLevel.WARNING, "Setting error for connection " + connection.getAddress());
+ connectionPool.setError(connection, request.errorCode());
+ }
return false;
}
}
- synchronized boolean isDownloading(FileReference fileReference) {
- return downloads.containsKey(fileReference);
+ boolean isDownloading(FileReference fileReference) {
+ synchronized (downloads) {
+ return downloads.containsKey(fileReference);
+ }
}
- synchronized ListenableFuture<Optional<File>> addDownloadListener(FileReference fileReference, Runnable runnable) {
- FileReferenceDownload fileReferenceDownload = downloads.get(fileReference);
- fileReferenceDownload.future().addListener(runnable, downloadExecutor);
- return fileReferenceDownload.future();
+ ListenableFuture<Optional<File>> addDownloadListener(FileReference fileReference, Runnable runnable) {
+ synchronized (downloads) {
+ FileReferenceDownload download = downloads.get(fileReference);
+ if (download != null) {
+ download.future().addListener(runnable, downloadExecutor);
+ return download.future();
+ }
+ }
+ return null;
}
private void execute(Request request, Connection connection) {
@@ -170,15 +159,33 @@ class FileReferenceDownloader {
}
double downloadStatus(String file) {
- return downloadStatus.getOrDefault(new FileReference(file), 0.0);
+ double status = 0.0;
+ synchronized (downloads) {
+ Double download = downloadStatus.get(new FileReference(file));
+ if (download != null) {
+ status = download;
+ }
+ }
+ return status;
+ }
+
+ void setDownloadStatus(String file, double completeness) {
+ setDownloadStatus(new FileReference(file), completeness);
}
- void setDownloadStatus(String file, double percentageDownloaded) {
- downloadStatus.put(new FileReference(file), percentageDownloaded);
+ void setDownloadStatus(FileReference fileReference, double completeness) {
+ synchronized (downloads) {
+ downloadStatus.put(fileReference, completeness);
+ }
}
Map<FileReference, Double> downloadStatus() {
- return ImmutableMap.copyOf(downloadStatus);
+ synchronized (downloads) {
+ return ImmutableMap.copyOf(downloadStatus);
+ }
}
+ public ConnectionPool connectionPool() {
+ return connectionPool;
+ }
}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java
new file mode 100644
index 00000000000..1681843a818
--- /dev/null
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java
@@ -0,0 +1,52 @@
+package com.yahoo.vespa.filedistribution;
+
+import com.yahoo.config.FileReference;
+import net.jpountz.xxhash.StreamingXXHash64;
+import net.jpountz.xxhash.XXHashFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+
+public class LazyFileReferenceData extends FileReferenceData {
+ private final File file;
+ private final ReadableByteChannel channel;
+ private final StreamingXXHash64 hasher;
+ public LazyFileReferenceData(FileReference fileReference, String filename, Type type, File file) throws IOException {
+ super(fileReference, filename, type);
+ this.file = file;
+ channel = Files.newByteChannel(file.toPath());
+ this.hasher = XXHashFactory.fastestInstance().newStreamingHash64(0);
+ }
+
+ @Override
+ public int nextContent(ByteBuffer bb) {
+ int read = 0;
+ int pos = bb.position();
+ try {
+ read = channel.read(bb);
+ } catch (IOException e) {
+ return -1;
+ }
+ if (read > 0) {
+ hasher.update(bb.array(), pos, read);
+ }
+ return read;
+ }
+
+ @Override
+ public long xxhash() {
+ return hasher.getValue();
+ }
+
+ @Override
+ public long size() {
+ try {
+ return Files.size(file.toPath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java
new file mode 100644
index 00000000000..28935c203fe
--- /dev/null
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java
@@ -0,0 +1,98 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.filedistribution;
+
+import com.yahoo.config.FileReference;
+import com.yahoo.io.IOUtils;
+import com.yahoo.jrt.DataValue;
+import com.yahoo.jrt.Int32Value;
+import com.yahoo.jrt.Int64Value;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Target;
+import com.yahoo.jrt.Transport;
+import com.yahoo.log.LogLevel;
+import net.jpountz.xxhash.XXHash64;
+import net.jpountz.xxhash.XXHashFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+public class RpcTester {
+
+ private static final Logger log = Logger.getLogger(RpcTester.class.getName());
+
+ private final Target target;
+
+ private RpcTester(Target target) {
+ this.target = target;
+ }
+
+ private void call(String fileReference, String filename, byte[] blob) {
+ new FileReceiver(target).receive(new FileReference(fileReference), filename, blob);
+ }
+
+ public static void main(String[] args) {
+ //String fileReference = args[0];
+ String fileReference = "59f93f445438c9db7ccbf1629f583c2aa004a68b";
+ String filename = "com.yahoo.vespatest.ExtraHitSearcher-1.0.0-deploy.jar";
+ File file = new File(String.format("/tmp/%s/%s", fileReference, filename));
+ byte[] blob = null;
+
+ try {
+ blob = IOUtils.readFileBytes(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ log.log(LogLevel.INFO, "Read blob from " + file.getAbsolutePath());
+
+
+ Supervisor supervisor = new Supervisor(new Transport());
+
+ Spec spec = new Spec("tcp/localhost:19090");
+ log.log(LogLevel.INFO, "Connecting to " + spec);
+ Target target = supervisor.connect(spec);
+ if (! target.isValid()) {
+ log.log(LogLevel.INFO, "Could not connect");
+ System.exit(1);
+ } else {
+ log.log(LogLevel.INFO, "Connected to " + spec);
+ }
+
+ new RpcTester(target).call(fileReference, filename, blob);
+ }
+
+ class FileReceiver {
+
+ Target target;
+
+ FileReceiver(Target target) {
+ this.target = target;
+ }
+
+ void receive(FileReference reference, String filename, byte[] content) {
+
+ log.log(LogLevel.INFO, "Preparing receive call for " + reference.value() + " and file " + filename);
+
+ XXHash64 hasher = XXHashFactory.fastestInstance().hash64();
+ Request fileBlob = new Request("filedistribution.receiveFile");
+
+ log.log(LogLevel.INFO, "Calling " + fileBlob.methodName() + " with target " + target);
+
+ fileBlob.parameters().add(new StringValue(reference.value()));
+ fileBlob.parameters().add(new StringValue(filename));
+ fileBlob.parameters().add(new DataValue(content));
+ fileBlob.parameters().add(new Int64Value(hasher.hash(ByteBuffer.wrap(content), 0)));
+ fileBlob.parameters().add(new Int32Value(0));
+ fileBlob.parameters().add(new StringValue("OK"));
+ log.log(LogLevel.INFO, "Doing invokeSync");
+ target.invokeSync(fileBlob, 5);
+ log.log(LogLevel.INFO, "Done with invokeSync");
+ }
+ }
+}
diff --git a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java
index 278c46dab8b..d2da020539a 100644
--- a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java
+++ b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java
@@ -13,15 +13,13 @@ import com.yahoo.jrt.Transport;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.config.Connection;
import com.yahoo.vespa.config.ConnectionPool;
-import net.jpountz.xxhash.XXHash64;
-import net.jpountz.xxhash.XXHashFactory;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
@@ -35,18 +33,18 @@ import static org.junit.Assert.fail;
public class FileDownloaderTest {
- private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64();
-
private MockConnection connection;
private FileDownloader fileDownloader;
private File downloadDir;
+ private File tempDir;
@Before
public void setup() {
try {
downloadDir = Files.createTempDirectory("filedistribution").toFile();
+ tempDir = Files.createTempDirectory("download").toFile();
connection = new MockConnection();
- fileDownloader = new FileDownloader(connection, downloadDir, Duration.ofMillis(2000));
+ fileDownloader = new FileDownloader(connection, downloadDir, tempDir, Duration.ofMillis(2000));
} catch (IOException e) {
e.printStackTrace();
fail(e.getMessage());
@@ -102,7 +100,7 @@ public class FileDownloaderTest {
// Receives fileReference, should return and make it available to caller
String filename = "abc.jar";
- receiveFile(fileReference, filename, "some other content");
+ receiveFile(fileReference, filename, FileReferenceData.Type.file, "some other content");
Optional<File> downloadedFile = fileDownloader.getFile(fileReference);
assertTrue(downloadedFile.isPresent());
@@ -113,11 +111,45 @@ public class FileDownloaderTest {
// Verify download status when downloaded
assertDownloadStatus(fileDownloader, fileReference, 100.0);
}
+
+ {
+ // fileReference does not exist on disk, needs to be downloaded, is compressed data
+
+ FileReference fileReference = new FileReference("fileReferenceToDirWithManyFiles");
+ File fileReferenceFullPath = fileReferenceFullPath(downloadDir, fileReference);
+ assertFalse(fileReferenceFullPath.getAbsolutePath(), fileDownloader.getFile(fileReference).isPresent());
+
+ // Verify download status
+ assertDownloadStatus(fileDownloader, fileReference, 0.0);
+
+ // Receives fileReference, should return and make it available to caller
+ String filename = "abc.tar.gz";
+ Path tempPath = Files.createTempDirectory("dir");
+ File subdir = new File(tempPath.toFile(), "subdir");
+ File fooFile = new File(subdir, "foo");
+ IOUtils.writeFile(fooFile, "foo", false);
+ File barFile = new File(subdir, "bar");
+ IOUtils.writeFile(barFile, "bar", false);
+
+ File tarFile = CompressedFileReference.compress(tempPath.toFile(), Arrays.asList(fooFile, barFile), new File(tempPath.toFile(), filename));
+ byte[] tarredContent = IOUtils.readFileBytes(tarFile);
+ receiveFile(fileReference, filename, FileReferenceData.Type.compressed, tarredContent);
+ Optional<File> downloadedFile = fileDownloader.getFile(fileReference);
+
+ assertTrue(downloadedFile.isPresent());
+ File downloadedFoo = new File(fileReferenceFullPath, tempPath.relativize(fooFile.toPath()).toString());
+ File downloadedBar = new File(fileReferenceFullPath, tempPath.relativize(barFile.toPath()).toString());
+ assertEquals("foo", IOUtils.readFile(downloadedFoo));
+ assertEquals("bar", IOUtils.readFile(downloadedBar));
+
+ // Verify download status when downloaded
+ assertDownloadStatus(fileDownloader, fileReference, 100.0);
+ }
}
@Test
public void getFileWhenConnectionError() throws IOException {
- fileDownloader = new FileDownloader(connection, downloadDir, Duration.ofMillis(3000));
+ fileDownloader = new FileDownloader(connection, downloadDir, tempDir, Duration.ofMillis(3000));
File downloadDir = fileDownloader.downloadDirectory();
int timesToFail = 2;
@@ -133,7 +165,7 @@ public class FileDownloaderTest {
// Receives fileReference, should return and make it available to caller
String filename = "abc.jar";
- receiveFile(fileReference, filename, "some other content");
+ receiveFile(fileReference, filename, FileReferenceData.Type.file, "some other content");
Optional<File> downloadedFile = fileDownloader.getFile(fileReference);
assertTrue(downloadedFile.isPresent());
@@ -153,11 +185,11 @@ public class FileDownloaderTest {
File downloadDir = Files.createTempDirectory("filedistribution").toFile();
MockConnection connectionPool = new MockConnection();
connectionPool.setResponseHandler(new MockConnection.WaitResponseHandler(timeout.plus(Duration.ofMillis(1000))));
- FileDownloader fileDownloader = new FileDownloader(connectionPool, downloadDir, timeout);
+ FileDownloader fileDownloader = new FileDownloader(connectionPool, downloadDir, tempDir, timeout);
FileReference foo = new FileReference("foo");
FileReference bar = new FileReference("bar");
List<FileReference> fileReferences = Arrays.asList(foo, bar);
- fileDownloader.queueForDownload(fileReferences);
+ fileDownloader.queueForAsyncDownload(fileReferences);
// Verify download status
assertDownloadStatus(fileDownloader, foo, 0.0);
@@ -168,7 +200,7 @@ public class FileDownloaderTest {
public void receiveFile() throws IOException {
FileReference foo = new FileReference("foo");
String filename = "foo.jar";
- receiveFile(foo, filename, "content");
+ receiveFile(foo, filename, FileReferenceData.Type.file, "content");
File downloadedFile = new File(fileReferenceFullPath(downloadDir, foo), filename);
assertEquals("content", IOUtils.readFile(downloadedFile));
}
@@ -187,10 +219,12 @@ public class FileDownloaderTest {
assertEquals(expectedDownloadStatus, downloadStatus, 0.0001);
}
- private void receiveFile(FileReference fileReference, String filename, String content) {
- byte[] contentBytes = Utf8.toBytes(content);
- long xxHashFromContent = hasher.hash(ByteBuffer.wrap(contentBytes), 0);
- fileDownloader.receiveFile(fileReference, filename, contentBytes, xxHashFromContent);
+ private void receiveFile(FileReference fileReference, String filename, FileReferenceData.Type type, String content) {
+ receiveFile(fileReference, filename, type, Utf8.toBytes(content));
+ }
+
+ private void receiveFile(FileReference fileReference, String filename, FileReferenceData.Type type, byte[] content) {
+ fileDownloader.receiveFile(new FileReferenceDataBlob(fileReference, filename, type, content));
}
private static class MockConnection implements ConnectionPool, com.yahoo.vespa.config.Connection {
diff --git a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java
new file mode 100644
index 00000000000..762817c27ef
--- /dev/null
+++ b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java
@@ -0,0 +1,71 @@
+package com.yahoo.vespa.filedistribution;
+
+import com.yahoo.config.FileReference;
+import com.yahoo.text.Utf8;
+import net.jpountz.xxhash.XXHash64;
+import net.jpountz.xxhash.XXHashFactory;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.assertEquals;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+
+public class FileReceiverTest {
+ private File root;
+ private File tempDir;
+ private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64();
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setup() throws IOException {
+ root = temporaryFolder.newFolder("root");
+ tempDir = temporaryFolder.newFolder("tmp");
+ }
+
+ @Test
+ public void receiveMultiPartFile() throws IOException{
+ String [] parts = new String[3];
+ parts[0] = "first part\n";
+ parts[1] = "second part\n";
+ parts[2] = "third part\n";
+ StringBuilder sb = new StringBuilder();
+ for (String s : parts) {
+ sb.append(s);
+ }
+ String all = sb.toString();
+ transferPartsAndAssert(new FileReference("ref-a"), "myfile-1", all, 1);
+ transferPartsAndAssert(new FileReference("ref-a"), "myfile-2", all, 2);
+ transferPartsAndAssert(new FileReference("ref-a"), "myfile-3", all, 3);
+ }
+
+ private void transferPartsAndAssert(FileReference ref, String fileName, String all, int numParts) throws IOException {
+ byte [] allContent = Utf8.toBytes(all);
+
+ FileReceiver.Session session = new FileReceiver.Session(root, tempDir, 1, ref,
+ FileReferenceData.Type.file, fileName, allContent.length);
+ int partSize = (allContent.length+(numParts-1))/numParts;
+ ByteBuffer bb = ByteBuffer.wrap(allContent);
+ for (int i = 0, pos = 0; i < numParts; i++) {
+ byte [] buf = new byte[Math.min(partSize, allContent.length - pos)];
+ bb.get(buf);
+ session.addPart(i, buf);
+ // Small numbers, so need a large delta
+ assertEquals((double)(i+1)/(double)numParts, session.percentageReceived(), 0.04);
+ pos += buf.length;
+ }
+ File file = session.close(hasher.hash(ByteBuffer.wrap(Utf8.toBytes(all)), 0));
+
+ byte [] allReadBytes = Files.readAllBytes(file.toPath());
+ file.delete();
+ assertEquals(all, Utf8.toString(allReadBytes));
+ }
+}
diff --git a/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp b/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp
index e07f0684584..de410289ec0 100644
--- a/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp
+++ b/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp
@@ -4,7 +4,7 @@
#include <vespa/vespalib/net/socket_address.h>
#include <vespa/filedistribution/common/logfwd.h>
#include <vespa/defaults.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/vespalib/text/stringtokenizer.h>
#include <zookeeper/zookeeper.h>
#include <sstream>
diff --git a/filedistribution_test/pom.xml b/filedistribution_test/pom.xml
index c270eabb6cf..5cc113c6453 100644
--- a/filedistribution_test/pom.xml
+++ b/filedistribution_test/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>filedistribution_test</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/filedistributionmanager/pom.xml b/filedistributionmanager/pom.xml
index 9e3ecf95ecb..9f037814a12 100644
--- a/filedistributionmanager/pom.xml
+++ b/filedistributionmanager/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>filedistributionmanager</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/fsa/pom.xml b/fsa/pom.xml
index a49845105cf..2a56069f82e 100644
--- a/fsa/pom.xml
+++ b/fsa/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>fsa</artifactId>
<packaging>container-plugin</packaging>
diff --git a/indexinglanguage/pom.xml b/indexinglanguage/pom.xml
index 70cb9f6d8d0..f0aba8d9476 100644
--- a/indexinglanguage/pom.xml
+++ b/indexinglanguage/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>indexinglanguage</artifactId>
<packaging>jar</packaging>
diff --git a/jaxrs_client_utils/pom.xml b/jaxrs_client_utils/pom.xml
index 1c4bdde21a8..e3de5b8163d 100644
--- a/jaxrs_client_utils/pom.xml
+++ b/jaxrs_client_utils/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jaxrs_client_utils</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java
index 5fd72432702..0b3c7ad8d3b 100644
--- a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java
+++ b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java
@@ -6,10 +6,13 @@ import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.client.proxy.WebResourceFactory;
-import javax.ws.rs.client.Client;
+import javax.net.ssl.SSLContext;
import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder;
+import java.util.Collections;
/**
* @author bakksjo
@@ -20,14 +23,26 @@ public class JerseyJaxRsClientFactory implements JaxRsClientFactory {
private final int connectTimeoutMs;
private final int readTimeoutMs;
+ private final SSLContext sslContext;
+ private final String userAgent;
public JerseyJaxRsClientFactory() {
this(DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
}
+ public JerseyJaxRsClientFactory(SSLContext sslContext, String userAgent) {
+ this(DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS, sslContext, userAgent);
+ }
+
public JerseyJaxRsClientFactory(final int connectTimeoutMs, final int readTimeoutMs) {
+ this(connectTimeoutMs, readTimeoutMs, null, null);
+ }
+
+ public JerseyJaxRsClientFactory(int connectTimeoutMs, int readTimeoutMs, SSLContext sslContext, String userAgent) {
this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs;
+ this.sslContext = sslContext;
+ this.userAgent = userAgent;
}
/**
@@ -38,13 +53,21 @@ public class JerseyJaxRsClientFactory implements JaxRsClientFactory {
@Override
public <T> T createClient(final Class<T> apiClass, final HostName hostName, final int port, final String pathPrefix, String scheme) {
final UriBuilder uriBuilder = UriBuilder.fromPath(pathPrefix).host(hostName.s()).port(port).scheme(scheme);
- final Client webClient = ClientBuilder.newClient()
+ ClientBuilder builder = ClientBuilder.newBuilder()
.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs)
.property(ClientProperties.READ_TIMEOUT, readTimeoutMs)
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true) // Allow empty PUT. TODO: Fix API.
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) // Allow e.g. PATCH method.
.property(ClientProperties.FOLLOW_REDIRECTS, true);
- final WebTarget target = webClient.target(uriBuilder);
+ if (sslContext != null) {
+ builder.sslContext(sslContext);
+ builder.hostnameVerifier((s, sslSession) -> true);
+ }
+ if (userAgent != null) {
+ builder.register((ClientRequestFilter) context ->
+ context.getHeaders().put(HttpHeaders.USER_AGENT, Collections.singletonList(userAgent)));
+ }
+ final WebTarget target = builder.build().target(uriBuilder);
// TODO: Check if this fills up non-heap memory with loaded classes.
return WebResourceFactory.newResource(apiClass, target);
}
diff --git a/jaxrs_utils/pom.xml b/jaxrs_utils/pom.xml
index 249cbda992b..1753d016ec3 100644
--- a/jaxrs_utils/pom.xml
+++ b/jaxrs_utils/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jaxrs_utils</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jdisc_core/pom.xml b/jdisc_core/pom.xml
index fb8200eb610..55da222994f 100644
--- a/jdisc_core/pom.xml
+++ b/jdisc_core/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jdisc_core</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java
index cd52a724275..a78e4f1af40 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java
@@ -47,7 +47,7 @@ public class StandaloneMain {
loader.destroy();
System.out.println("debug\tStopped ok.");
System.exit(0);
- } catch (Exception e) {
+ } catch (Throwable e) {
System.out.print("debug\tUnexpected: ");
e.printStackTrace();
log.log(Level.SEVERE, "Unexpected: ", e);
diff --git a/jdisc_core_test/pom.xml b/jdisc_core_test/pom.xml
index 92e7a39fee0..a1a96463ffc 100644
--- a/jdisc_core_test/pom.xml
+++ b/jdisc_core_test/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa.jdisc_core</groupId>
<artifactId>parent</artifactId>
diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml
index d8c0b0bc29c..a1482669b39 100644
--- a/jdisc_http_service/pom.xml
+++ b/jdisc_http_service/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jdisc_http_service</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
index c3c83474e56..063bb35b63e 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
+import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -115,6 +116,10 @@ public class AccessLogRequestLog extends AbstractLifeCycle implements RequestLog
if (principal != null) {
accessLogEntry.setUserPrincipal(principal);
}
+ X509Certificate[] clientCert = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+ if (clientCert != null && clientCert.length > 0) {
+ accessLogEntry.setSslPrincipal(clientCert[0].getSubjectX500Principal());
+ }
}
private static String getRemoteAddress(final HttpServletRequest request) {
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
index 8255e16e0ee..981d4219158 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
@@ -2,12 +2,18 @@
package com.yahoo.jdisc.http.server.jetty;
import com.google.inject.Inject;
+import com.yahoo.config.InnerNode;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.ConnectorConfig.Ssl;
-import com.yahoo.jdisc.http.SecretStore;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ExcludeCipherSuite;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ExcludeProtocol;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.IncludeCipherSuite;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.IncludeProtocol;
import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreContext;
+import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreContext;
import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -18,6 +24,9 @@ import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import java.nio.channels.ServerSocketChannel;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
/**
* @author Einar M R Rosenvinge
@@ -26,27 +35,16 @@ import java.nio.channels.ServerSocketChannel;
public class ConnectorFactory {
private final ConnectorConfig connectorConfig;
- private final SecretStore secretStore;
private final SslKeyStoreConfigurator sslKeyStoreConfigurator;
+ private final SslTrustStoreConfigurator sslTrustStoreConfigurator;
@Inject
public ConnectorFactory(ConnectorConfig connectorConfig,
- SecretStore secretStore,
- SslKeyStoreConfigurator sslKeyStoreConfigurator) {
+ SslKeyStoreConfigurator sslKeyStoreConfigurator,
+ SslTrustStoreConfigurator sslTrustStoreConfigurator) {
this.connectorConfig = connectorConfig;
- this.secretStore = secretStore;
this.sslKeyStoreConfigurator = sslKeyStoreConfigurator;
-
- if (connectorConfig.ssl().enabled())
- validateSslConfig(connectorConfig);
- }
-
- // TODO: can be removed when we have dedicated SSL config in services.xml
- private static void validateSslConfig(ConnectorConfig config) {
- ConnectorConfig.Ssl ssl = config.ssl();
- if (!ssl.trustStorePath().isEmpty() && ssl.useTrustStorePassword() && ssl.keyDbKey().isEmpty()) {
- throw new IllegalArgumentException("Missing password for JKS truststore");
- }
+ this.sslTrustStoreConfigurator = sslTrustStoreConfigurator;
}
public ConnectorConfig getConnectorConfig() {
@@ -93,13 +91,13 @@ public class ConnectorFactory {
return new HttpConnectionFactory(httpConfig);
}
- //TODO: does not support loading non-yahoo readable JKS key stores.
private SslConnectionFactory newSslConnectionFactory() {
Ssl sslConfig = connectorConfig.ssl();
SslContextFactory factory = new SslContextFactory();
sslKeyStoreConfigurator.configure(new DefaultSslKeyStoreContext(factory));
+ sslTrustStoreConfigurator.configure(new DefaultSslTrustStoreContext(factory));
switch (sslConfig.clientAuth()) {
case NEED_AUTH:
@@ -114,49 +112,28 @@ public class ConnectorFactory {
factory.setSecureRandomAlgorithm(sslConfig.prng());
}
- if (!sslConfig.excludeProtocol().isEmpty()) {
- String[] prots = new String[sslConfig.excludeProtocol().size()];
- for (int i = 0; i < prots.length; i++) {
- prots[i] = sslConfig.excludeProtocol(i).name();
- }
- factory.setExcludeProtocols(prots);
- }
- if (!sslConfig.includeProtocol().isEmpty()) {
- String[] prots = new String[sslConfig.includeProtocol().size()];
- for (int i = 0; i < prots.length; i++) {
- prots[i] = sslConfig.includeProtocol(i).name();
- }
- factory.setIncludeProtocols(prots);
- }
- if (!sslConfig.excludeCipherSuite().isEmpty()) {
- String[] ciphs = new String[sslConfig.excludeCipherSuite().size()];
- for (int i = 0; i < ciphs.length; i++) {
- ciphs[i] = sslConfig.excludeCipherSuite(i).name();
- }
- factory.setExcludeCipherSuites(ciphs);
-
- }
- if (!sslConfig.includeCipherSuite().isEmpty()) {
- String[] ciphs = new String[sslConfig.includeCipherSuite().size()];
- for (int i = 0; i < ciphs.length; i++) {
- ciphs[i] = sslConfig.includeCipherSuite(i).name();
- }
- factory.setIncludeCipherSuites(ciphs);
- }
-
- String keyDbPassword = sslConfig.keyDbKey();
-
- if (!sslConfig.trustStorePath().isEmpty()) {
- factory.setTrustStorePath(sslConfig.trustStorePath());
- factory.setTrustStoreType(sslConfig.trustStoreType().toString());
- if (sslConfig.useTrustStorePassword()) {
- factory.setTrustStorePassword(secretStore.getSecret(keyDbPassword));
- }
- }
+ setStringArrayParameter(
+ factory, sslConfig.excludeProtocol(), ExcludeProtocol::name, SslContextFactory::setExcludeProtocols);
+ setStringArrayParameter(
+ factory, sslConfig.includeProtocol(), IncludeProtocol::name, SslContextFactory::setIncludeProtocols);
+ setStringArrayParameter(
+ factory, sslConfig.excludeCipherSuite(), ExcludeCipherSuite::name, SslContextFactory::setExcludeCipherSuites);
+ setStringArrayParameter(
+ factory, sslConfig.includeCipherSuite(), IncludeCipherSuite::name, SslContextFactory::setIncludeCipherSuites);
factory.setKeyManagerFactoryAlgorithm(sslConfig.sslKeyManagerFactoryAlgorithm());
factory.setProtocol(sslConfig.protocol());
return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString());
}
+ private static <T extends InnerNode> void setStringArrayParameter(SslContextFactory sslContextFactory,
+ List<T> configValues,
+ Function<T, String> nameProperty,
+ BiConsumer<SslContextFactory, String[]> setter) {
+ if (!configValues.isEmpty()) {
+ String[] nameArray = configValues.stream().map(nameProperty).toArray(String[]::new);
+ setter.accept(sslContextFactory, nameArray);
+ }
+ }
+
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java
index 714d75f9d1e..a005ea7d96e 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java
@@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
import com.yahoo.jdisc.service.CurrentContainer;
import javax.servlet.http.HttpServletRequest;
@@ -27,7 +28,7 @@ class HttpRequestFactory {
HttpRequest.Version.fromString(servletRequest.getProtocol()),
new InetSocketAddress(servletRequest.getRemoteAddr(), servletRequest.getRemotePort()),
getConnection(servletRequest).getCreatedTimeStamp());
- httpRequest.context().put("jdisc.request.X509Certificate", getCertChain(servletRequest));
+ httpRequest.context().put(ServletRequest.JDISC_REQUEST_X509CERT, getCertChain(servletRequest));
return httpRequest;
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java
index 735bf3ed89a..bc14a063cd1 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java
@@ -184,8 +184,7 @@ public class ServletResponseController {
private static void setHeaders_holdingLock(Response jdiscResponse, HttpServletResponse servletResponse) {
for (final Map.Entry<String, String> entry : jdiscResponse.headers().entries()) {
- final String value = entry.getValue();
- servletResponse.addHeader(entry.getKey(), value != null ? value : "");
+ servletResponse.addHeader(entry.getKey(), entry.getValue());
}
if (servletResponse.getContentType() == null) {
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java
index db8780b087c..ea36237bc45 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java
@@ -38,6 +38,7 @@ import static com.yahoo.jdisc.http.core.HttpServletRequestUtils.getConnection;
*/
public class ServletRequest extends HttpServletRequestWrapper implements ServletOrJdiscHttpRequest {
public static final String JDISC_REQUEST_PRINCIPAL = "jdisc.request.principal";
+ public static final String JDISC_REQUEST_X509CERT = "jdisc.request.X509Certificate";
private final HttpServletRequest request;
private final HeaderFields headerFields;
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java
new file mode 100644
index 00000000000..8af21d48e9a
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java
@@ -0,0 +1,40 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.SecretStore;
+
+/**
+ * @author bjorncs
+ */
+public class DefaultSslTrustStoreConfigurator implements SslTrustStoreConfigurator {
+
+ private final SecretStore secretStore;
+ private final ConnectorConfig.Ssl config;
+
+ @Inject
+ public DefaultSslTrustStoreConfigurator(ConnectorConfig config, SecretStore secretStore) {
+ validateConfig(config.ssl());
+ this.secretStore = secretStore;
+ this.config = config.ssl();
+ }
+
+ @Override
+ public void configure(SslTrustStoreContext context) {
+ if (!config.enabled()) return;
+ String keyDbPassword = config.keyDbKey();
+ if (!config.trustStorePath().isEmpty()) {
+ String password = config.useTrustStorePassword() ? secretStore.getSecret(keyDbPassword) : null;
+ context.updateTrustStore(config.trustStorePath(), config.trustStoreType().toString(), password);
+ }
+ }
+
+ private static void validateConfig(ConnectorConfig.Ssl config) {
+ if (!config.enabled()) return;
+ if (!config.trustStorePath().isEmpty() && config.useTrustStorePassword() && config.keyDbKey().isEmpty()) {
+ throw new IllegalArgumentException("Missing password for JKS truststore");
+ }
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java
new file mode 100644
index 00000000000..c2d91cca3ea
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java
@@ -0,0 +1,54 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import java.security.KeyStore;
+import java.util.function.Consumer;
+
+/**
+ * @author bjorncs
+ */
+public class DefaultSslTrustStoreContext implements SslTrustStoreContext {
+
+ private final SslContextFactory sslContextFactory;
+
+ public DefaultSslTrustStoreContext(SslContextFactory sslContextFactory) {
+ this.sslContextFactory = sslContextFactory;
+ }
+
+ @Override
+ public void updateTrustStore(KeyStore trustStore) {
+ updateTrustStore(trustStore, null);
+ }
+
+ @Override
+ public void updateTrustStore(KeyStore trustStore, String password) {
+ updateTrustStore(sslContextFactory -> {
+ sslContextFactory.setTrustStore(trustStore);
+ if (password != null) {
+ sslContextFactory.setTrustStorePassword(password);
+ }
+ });
+ }
+
+ @Override
+ public void updateTrustStore(String trustStorePath, String trustStoreType, String trustStorePassword) {
+ updateTrustStore(sslContextFactory -> {
+ sslContextFactory.setTrustStorePath(trustStorePath);
+ sslContextFactory.setTrustStoreType(trustStoreType);
+ if (trustStorePassword != null) {
+ sslContextFactory.setTrustStorePassword(trustStorePassword);
+ }
+ });
+ }
+
+ private void updateTrustStore(Consumer<SslContextFactory> reloader) {
+ try {
+ sslContextFactory.reload(reloader);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not update truststore: " + e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java
new file mode 100644
index 00000000000..de1119a5275
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+/**
+ * An interface for an component that can configure an {@link SslTrustStoreContext}. The implementor can assume that
+ * the {@link SslTrustStoreContext} instance is thread-safe and be updated at any time
+ * during and after the call to{@link #configure(SslTrustStoreContext)}.
+ * Modifying the {@link SslKeyStoreContext} instance will trigger a hot reload of the truststore in JDisc.
+ *
+ * @author bjorncs
+ */
+public interface SslTrustStoreConfigurator {
+ void configure(SslTrustStoreContext context);
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java
new file mode 100644
index 00000000000..fc8cf397b24
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import java.security.KeyStore;
+
+/**
+ * An interface to update the truststore in JDisc. Any update will trigger a hot reload and new connections will
+ * authenticated using the update truststore.
+ *
+ * @author bjorncs
+ */
+public interface SslTrustStoreContext {
+ void updateTrustStore(KeyStore trustStore);
+ void updateTrustStore(KeyStore trustStore, String password);
+ void updateTrustStore(String trustStorePath, String trustStoreType, String trustStorePassword);
+}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
index 0d8f433cc39..6281907e083 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
@@ -12,6 +12,7 @@ import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.server.jetty.ConnectorFactory;
import com.yahoo.jdisc.http.server.jetty.TestDrivers;
import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator;
/**
* Guice module for test ConnectorFactories
@@ -48,8 +49,8 @@ public class ConnectorFactoryRegistryModule implements Module {
public StaticKeyDbConnectorFactory(ConnectorConfig connectorConfig) {
super(connectorConfig,
- new MockSecretStore(),
- new DefaultSslKeyStoreConfigurator(connectorConfig, new MockSecretStore()));
+ new DefaultSslKeyStoreConfigurator(connectorConfig, new MockSecretStore()),
+ new DefaultSslTrustStoreConfigurator(connectorConfig, new MockSecretStore()));
}
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
index 781bc6a7b5f..103a317094b 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
@@ -5,6 +5,7 @@ import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
+import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -107,7 +108,9 @@ public class ConnectorFactoryTest {
private static ConnectorFactory createConnectorFactory(ConnectorConfig config) {
ThrowingSecretStore secretStore = new ThrowingSecretStore();
- return new ConnectorFactory(config, secretStore, new DefaultSslKeyStoreConfigurator(config, secretStore));
+ return new ConnectorFactory(config,
+ new DefaultSslKeyStoreConfigurator(config, secretStore),
+ new DefaultSslTrustStoreConfigurator(config, secretStore));
}
private static class HelloWorldHandler extends AbstractHandler {
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 7ed13decbf6..55dec64e967 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -371,6 +371,7 @@ public class HttpServerTest {
}
// Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2
+ // Details in https://github.com/eclipse/jetty.project/issues/1116
@Test
public void requireThatHeaderWithNullValueIsOmitted() throws Exception {
final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", null));
@@ -380,13 +381,14 @@ public class HttpServerTest {
assertThat(driver.close(), is(true));
}
- // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2
+ // Header with empty value is allowed by https://tools.ietf.org/html/rfc7230#section-3.2
+ // Details in https://github.com/eclipse/jetty.project/issues/1116
@Test
- public void requireThatHeaderWithEmptyValueIsOmitted() throws Exception {
+ public void requireThatHeaderWithEmptyValueIsAllowed() throws Exception {
final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", ""));
driver.client().get("/status.html")
.expectStatusCode(is(OK))
- .expectNoHeader("X-Foo");
+ .expectHeader("X-Foo", is(""));
assertThat(driver.close(), is(true));
}
diff --git a/jdisc_jetty/pom.xml b/jdisc_jetty/pom.xml
index 4e739aae8cc..0f8a5ba19e2 100644
--- a/jdisc_jetty/pom.xml
+++ b/jdisc_jetty/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jdisc_jetty</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jdisc_messagebus_service/pom.xml b/jdisc_messagebus_service/pom.xml
index e9531a73861..6201f413b9a 100644
--- a/jdisc_messagebus_service/pom.xml
+++ b/jdisc_messagebus_service/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jdisc_messagebus_service</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/jrt/pom.xml b/jrt/pom.xml
index 0d6be56fdab..cf3da2ab7ce 100644
--- a/jrt/pom.xml
+++ b/jrt/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>jrt</artifactId>
<packaging>container-plugin</packaging>
diff --git a/linguistics/pom.xml b/linguistics/pom.xml
index 328742f483a..e4aa7c3049e 100644
--- a/linguistics/pom.xml
+++ b/linguistics/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>linguistics</artifactId>
<packaging>container-plugin</packaging>
@@ -59,6 +60,7 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>provided</scope>
+ <classifier>no_aop</classifier>
</dependency>
</dependencies>
<build>
diff --git a/logd/pom.xml b/logd/pom.xml
index e21931efc5a..a1a107bc20c 100644
--- a/logd/pom.xml
+++ b/logd/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>logd</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/logd/src/apps/logd/main.cpp b/logd/src/apps/logd/main.cpp
index 70df928dfd2..f9d760c4d18 100644
--- a/logd/src/apps/logd/main.cpp
+++ b/logd/src/apps/logd/main.cpp
@@ -6,6 +6,7 @@
#include <logd/conf.h>
#include <logd/watch.h>
#include <logd/state.h>
+#include <logd/metrics.h>
#include <vespa/config/common/exceptions.h>
#include <csignal>
#include <unistd.h>
@@ -21,7 +22,9 @@ using config::FileSpec;
int main(int, char**)
{
- Forwarder fwd;
+ StateReporter stateReporter;
+ Metrics metrics(stateReporter.metrics());
+ Forwarder fwd(metrics);
EV_STARTED("logdemon");
@@ -31,7 +34,6 @@ int main(int, char**)
try {
ConfSub subscriber(fwd, config::ConfigUri(cfid));
- StateReporter stateReporter;
int sleepcount = 0;
while (true) {
diff --git a/logd/src/logd/CMakeLists.txt b/logd/src/logd/CMakeLists.txt
index 3ef48f9255a..78892fdeca6 100644
--- a/logd/src/logd/CMakeLists.txt
+++ b/logd/src/logd/CMakeLists.txt
@@ -1,14 +1,16 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_library(logd STATIC
SOURCES
- watch.cpp
- conn.cpp
+ cmdbuf.cpp
conf.cpp
+ conn.cpp
forward.cpp
- service.cpp
- cmdbuf.cpp
+ metrics.cpp
perform.cpp
+ service.cpp
state.cpp
+ watch.cpp
+
DEPENDS
)
vespa_generate_config(logd ../main/resources/configdefinitions/logd.def)
diff --git a/logd/src/logd/conf.cpp b/logd/src/logd/conf.cpp
index 716ba97effd..ef1f6b73cbe 100644
--- a/logd/src/logd/conf.cpp
+++ b/logd/src/logd/conf.cpp
@@ -35,12 +35,12 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg)
return;
}
strcpy(_logServer, newconf.logserver.host.c_str());
- _newConf = true;
+ _needToConnect = true;
}
if (newconf.logserver.use != _use_logserver)
{
_use_logserver = newconf.logserver.use;
- _newConf = true;
+ _needToConnect = true;
}
_statePort = newconf.stateport;
@@ -57,7 +57,7 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg)
if (newconf.logserver.port != _logPort) {
_logPort = newconf.logserver.port;
- _newConf = true;
+ _needToConnect = true;
}
if (newconf.rotate.size > 0) {
_rotate_size = newconf.rotate.size;
@@ -85,12 +85,23 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg)
}
}
+bool
+ConfSub::checkAvailable()
+{
+ if (_subscriber.nextGeneration(0)) {
+ _hasAvailable = true;
+ }
+ return _hasAvailable;
+}
+
void
ConfSub::latch()
{
- if (_subscriber.nextConfig(0))
+ if (checkAvailable()) {
configure(_handle->getConfig());
- if (_newConf) {
+ _hasAvailable = false;
+ }
+ if (_needToConnect) {
if (_use_logserver) {
connectToLogserver();
} else {
@@ -133,7 +144,7 @@ ConfSub::resetFileDescriptor(int newfd)
}
_logserverfd = newfd;
_fw.setLogserverFD(newfd);
- _newConf = false;
+ _needToConnect = false;
}
void
@@ -141,7 +152,7 @@ ConfSub::closeConn()
{
close(_logserverfd);
_logserverfd = -1;
- _newConf = true;
+ _needToConnect = true;
}
ConfSub::ConfSub(Forwarder &fw, const config::ConfigUri & configUri)
@@ -156,7 +167,8 @@ ConfSub::ConfSub(Forwarder &fw, const config::ConfigUri & configUri)
_fw(fw),
_subscriber(configUri.getContext()),
_handle(),
- _newConf(false)
+ _hasAvailable(false),
+ _needToConnect(true)
{
_logServer[0] = '\0';
_handle = _subscriber.subscribe<LogdConfig>(configUri.getConfigId());
diff --git a/logd/src/logd/conf.h b/logd/src/logd/conf.h
index 7a91630ff93..a777470efc6 100644
--- a/logd/src/logd/conf.h
+++ b/logd/src/logd/conf.h
@@ -19,7 +19,8 @@ private:
Forwarder& _fw;
config::ConfigSubscriber _subscriber;
config::ConfigHandle<cloud::config::log::LogdConfig>::UP _handle;
- bool _newConf;
+ bool _hasAvailable;
+ bool _needToConnect;
void connectToLogserver();
void connectToDevNull();
@@ -27,6 +28,7 @@ private:
ConfSub(const ConfSub& other);
ConfSub& operator=(const ConfSub& other);
public:
+ bool checkAvailable();
void latch();
void closeConn();
ConfSub(Forwarder &fw, const config::ConfigUri & configUri);
diff --git a/logd/src/logd/forward.cpp b/logd/src/logd/forward.cpp
index 66031ba2539..eba38dbcbd3 100644
--- a/logd/src/logd/forward.cpp
+++ b/logd/src/logd/forward.cpp
@@ -11,8 +11,9 @@ LOG_SETUP("");
namespace logdemon {
-Forwarder::Forwarder()
+Forwarder::Forwarder(Metrics &metrics)
: _logserverfd(-1),
+ _metrics(metrics),
_forwardMap(),
_levelparser(),
knownServices(),
@@ -172,6 +173,8 @@ Forwarder::parseline(const char *linestart, const char *lineend)
return false;
}
+ _metrics.countLine(level, service);
+
// Check overrides
ForwardMap::iterator found = _forwardMap.find(l);
if (found != _forwardMap.end()) {
diff --git a/logd/src/logd/forward.h b/logd/src/logd/forward.h
index 794db3bb45f..e012db205fe 100644
--- a/logd/src/logd/forward.h
+++ b/logd/src/logd/forward.h
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
#include "service.h"
+#include "metrics.h"
#include <vespa/vespalib/util/hashmap.h>
#include <map>
@@ -23,6 +24,7 @@ class Forwarder
{
private:
int _logserverfd;
+ Metrics &_metrics;
ForwardMap _forwardMap;
LevelParser _levelparser;
const char *copystr(const char *b, const char *e) {
@@ -36,7 +38,7 @@ private:
public:
Services knownServices;
int _badLines;
- Forwarder();
+ Forwarder(Metrics &metrics);
~Forwarder();
void forwardText(const char *text, int len);
void forwardLine(const char *line, const char *eol);
diff --git a/logd/src/logd/metrics.cpp b/logd/src/logd/metrics.cpp
new file mode 100644
index 00000000000..1a70a2226e2
--- /dev/null
+++ b/logd/src/logd/metrics.cpp
@@ -0,0 +1,3 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "metrics.h"
diff --git a/logd/src/logd/metrics.h b/logd/src/logd/metrics.h
new file mode 100644
index 00000000000..b5bc5a7dcd7
--- /dev/null
+++ b/logd/src/logd/metrics.h
@@ -0,0 +1,39 @@
+// 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/metrics/metrics_manager.h>
+
+namespace logdemon {
+
+using vespalib::metrics::Dimension;
+using vespalib::metrics::Counter;
+using vespalib::metrics::MetricsManager;
+using vespalib::metrics::Point;
+
+struct Metrics {
+ std::shared_ptr<MetricsManager> metrics;
+ const Dimension loglevel;
+ const Dimension servicename;
+ const Counter loglines;
+
+ Metrics(std::shared_ptr<MetricsManager> m)
+ : metrics(m),
+ loglevel(metrics->dimension("loglevel")),
+ servicename(metrics->dimension("service")),
+ loglines(metrics->counter("logd.processed.lines",
+ "how many log lines have been processed"))
+ {}
+
+ ~Metrics() {}
+
+ void countLine(const vespalib::string &level,
+ const vespalib::string &service) const
+ {
+ Point p = metrics->pointBuilder()
+ .bind(loglevel, level)
+ .bind(servicename, service);
+ loglines.add(1, p);
+ }
+};
+
+} // namespace logdemon
diff --git a/logd/src/logd/state.cpp b/logd/src/logd/state.cpp
index b73183d5987..e1df1412215 100644
--- a/logd/src/logd/state.cpp
+++ b/logd/src/logd/state.cpp
@@ -4,10 +4,20 @@
LOG_SETUP("");
#include "state.h"
+#include <vespa/vespalib/metrics/simple_metrics_manager.h>
namespace logdemon {
+using vespalib::metrics::SimpleMetricsManager;
+using vespalib::metrics::SimpleManagerConfig;
+
StateReporter::StateReporter()
+ : _port(-1),
+ _server(),
+ _health(),
+ _components(),
+ _metrics(SimpleMetricsManager::create(SimpleManagerConfig())),
+ _producer(_metrics)
{
}
@@ -16,7 +26,7 @@ StateReporter::setStatePort(int statePort)
{
if (statePort != _port) {
_port = statePort;
- _server.reset(new vespalib::StateServer(_port, _health, _metrics, _components));
+ _server.reset(new vespalib::StateServer(_port, _health, _producer, _components));
LOG(info, "state server listening on port %d", _server->getListenPort());
}
}
diff --git a/logd/src/logd/state.h b/logd/src/logd/state.h
index 7abcc7eb93c..59c9741e68f 100644
--- a/logd/src/logd/state.h
+++ b/logd/src/logd/state.h
@@ -3,9 +3,10 @@
#include <vespa/vespalib/net/state_server.h>
#include <vespa/vespalib/net/simple_health_producer.h>
-#include <vespa/vespalib/net/simple_metrics_producer.h>
#include <vespa/vespalib/net/simple_component_config_producer.h>
#include <vespa/vespalib/net/generic_state_handler.h>
+#include <vespa/vespalib/metrics/metrics_manager.h>
+#include <vespa/vespalib/metrics/producer.h>
namespace logdemon {
@@ -13,12 +14,15 @@ class StateReporter {
int _port;
std::unique_ptr<vespalib::StateServer> _server;
vespalib::SimpleHealthProducer _health;
- vespalib::SimpleMetricsProducer _metrics;
vespalib::SimpleComponentConfigProducer _components;
+ std::shared_ptr<vespalib::metrics::MetricsManager> _metrics;
+ vespalib::metrics::Producer _producer;
public:
StateReporter();
+ ~StateReporter() {}
void setStatePort(int statePort);
void gotConf(size_t generation);
+ std::shared_ptr<vespalib::metrics::MetricsManager> metrics() { return _metrics; }
};
} // namespace
diff --git a/logd/src/logd/watch.cpp b/logd/src/logd/watch.cpp
index ad866938ab6..2adc1cbbdbb 100644
--- a/logd/src/logd/watch.cpp
+++ b/logd/src/logd/watch.cpp
@@ -332,7 +332,10 @@ Watcher::watchfile()
dcf.saveState(already, _forwarder.knownServices);
- _confsubscriber.latch();
+ if (_confsubscriber.checkAvailable()) {
+ LOG(debug, "new config available, doing reconfigure");
+ return;
+ }
if (_confsubscriber.useLogserver()) {
cmdbuf.maybeRead(_forwarder.getLogserverFD());
diff --git a/logd/src/tests/forward/forward.cpp b/logd/src/tests/forward/forward.cpp
index 13e31cea464..db0a4e5de69 100644
--- a/logd/src/tests/forward/forward.cpp
+++ b/logd/src/tests/forward/forward.cpp
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/log/log.h>
#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/metrics/dummy_metrics_manager.h>
#include <logd/forward.h>
#include <sstream>
#include <fcntl.h>
@@ -49,14 +50,17 @@ struct ForwardFixture {
}
};
-TEST_FF("require that forwarder forwards if set", Forwarder(), ForwardFixture(f1, "forward.txt")) {
+std::shared_ptr<vespalib::metrics::MetricsManager> dummy = vespalib::metrics::DummyMetricsManager::create();
+Metrics m(dummy);
+
+TEST_FF("require that forwarder forwards if set", Forwarder(m), ForwardFixture(f1, "forward.txt")) {
ForwardMap forwardMap;
forwardMap[Logger::event] = true;
f1.setForwardMap(forwardMap);
f2.verifyForward(true);
}
-TEST_FF("require that forwarder does not forward if not set", Forwarder(), ForwardFixture(f1, "forward.txt")) {
+TEST_FF("require that forwarder does not forward if not set", Forwarder(m), ForwardFixture(f1, "forward.txt")) {
ForwardMap forwardMap;
forwardMap[Logger::event] = false;
f1.setForwardMap(forwardMap);
diff --git a/logserver/pom.xml b/logserver/pom.xml
index 6acdd8ed1d7..55c5c443cfb 100644
--- a/logserver/pom.xml
+++ b/logserver/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>logserver</artifactId>
<packaging>jar</packaging>
diff --git a/maven-plugins/pom.xml b/maven-plugins/pom.xml
index 2f7343e753c..0aabe630a63 100644
--- a/maven-plugins/pom.xml
+++ b/maven-plugins/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugins</artifactId>
diff --git a/memfilepersistence/src/tests/spi/memcachetest.cpp b/memfilepersistence/src/tests/spi/memcachetest.cpp
index 73815df563a..5e9f1a28225 100644
--- a/memfilepersistence/src/tests/spi/memcachetest.cpp
+++ b/memfilepersistence/src/tests/spi/memcachetest.cpp
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/memfilepersistence/memfile/memfilecache.h>
-#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h>
#include <tests/spi/memfiletestutils.h>
@@ -41,7 +40,6 @@ private:
framework::defaultimplementation::ComponentRegisterImpl::UP _register;
framework::Component::UP _component;
FakeClock::UP _clock;
- framework::defaultimplementation::MemoryManager::UP _memoryManager;
std::unique_ptr<MemFilePersistenceMetrics> _metrics;
std::unique_ptr<MemFileCache> _cache;
@@ -113,18 +111,10 @@ private:
new framework::defaultimplementation::ComponentRegisterImpl);
_clock.reset(new FakeClock);
_register->setClock(*_clock);
- _memoryManager.reset(
- new framework::defaultimplementation::MemoryManager(
- framework::defaultimplementation::AllocationLogic::UP(
- new framework::defaultimplementation::SimpleMemoryLogic(
- *_clock, maxMemory * 2))));
- _register->setMemoryManager(*_memoryManager);
_component.reset(new framework::Component(*_register, "testcomponent"));
_metrics.reset(new MemFilePersistenceMetrics(*_component));
_cache.reset(new MemFileCache(*_register, _metrics->_cache));
setCacheSize(maxMemory);
- _memoryManager->registerAllocationType(framework::MemoryAllocationType(
- "steal", framework::MemoryAllocationType::FORCE_ALLOCATE));
}
public:
@@ -133,7 +123,6 @@ public:
_metrics.reset(0);
_component.reset(0);
_register.reset(0);
- _memoryManager.reset(0);
_clock.reset(0);
}
};
diff --git a/memfilepersistence/src/tests/spi/memfiletestutils.cpp b/memfilepersistence/src/tests/spi/memfiletestutils.cpp
index bb35aa4d03e..cd14e989220 100644
--- a/memfilepersistence/src/tests/spi/memfiletestutils.cpp
+++ b/memfilepersistence/src/tests/spi/memfiletestutils.cpp
@@ -5,7 +5,6 @@
#include <tests/spi/memfiletestutils.h>
#include <tests/spi/simulatedfailurefile.h>
#include <vespa/memfilepersistence/memfile/memfilecache.h>
-#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h>
#include <vespa/document/update/assignvalueupdate.h>
#include <vespa/document/test/make_bucket_space.h>
#include <vespa/persistence/spi/test.h>
@@ -72,12 +71,6 @@ MemFileTestUtils::setupDisks(uint32_t numDisks) {
new framework::defaultimplementation::ComponentRegisterImpl);
_clock.reset(new FakeClock);
_componentRegister->setClock(*_clock);
- _memoryManager.reset(
- new framework::defaultimplementation::MemoryManager(
- framework::defaultimplementation::AllocationLogic::UP(
- new framework::defaultimplementation::SimpleMemoryLogic(
- *_clock, 1024 * 1024 * 1024))));
- _componentRegister->setMemoryManager(*_memoryManager);
_env.reset(new MemFileTestEnvironment(numDisks,
*_componentRegister,
*getTypeRepo()));
diff --git a/memfilepersistence/src/tests/spi/memfiletestutils.h b/memfilepersistence/src/tests/spi/memfiletestutils.h
index dde73c88164..450d87451b9 100644
--- a/memfilepersistence/src/tests/spi/memfiletestutils.h
+++ b/memfilepersistence/src/tests/spi/memfiletestutils.h
@@ -20,8 +20,6 @@
#include <vespa/document/update/documentupdate.h>
#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
#include <vespa/storageframework/defaultimplementation/component/componentregisterimpl.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h>
-
namespace storage {
namespace memfile {
@@ -70,7 +68,6 @@ private:
document::BucketIdFactory _bucketIdFactory;
framework::defaultimplementation::ComponentRegisterImpl::UP _componentRegister;
FakeClock::UP _clock;
- framework::defaultimplementation::MemoryManager::UP _memoryManager;
std::unique_ptr<MemFileTestEnvironment> _env;
public:
@@ -82,7 +79,6 @@ public:
void tearDown() override{
_env.reset();
_componentRegister.reset();
- _memoryManager.reset();
_clock.reset();
}
diff --git a/memfilepersistence/src/tests/spi/providerconformancetest.cpp b/memfilepersistence/src/tests/spi/providerconformancetest.cpp
index db02e619782..625549f0008 100644
--- a/memfilepersistence/src/tests/spi/providerconformancetest.cpp
+++ b/memfilepersistence/src/tests/spi/providerconformancetest.cpp
@@ -2,7 +2,6 @@
#include "memfiletestutils.h"
#include <vespa/persistence/conformancetest/conformancetest.h>
-#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h>
namespace storage {
namespace memfile {
@@ -11,19 +10,13 @@ struct ProviderConformanceTest : public spi::ConformanceTest {
struct Factory : public PersistenceFactory {
framework::defaultimplementation::ComponentRegisterImpl _compRegister;
framework::defaultimplementation::RealClock _clock;
- framework::defaultimplementation::MemoryManager _memoryManager;
std::unique_ptr<MemFileCache> cache;
Factory()
: _compRegister(),
- _clock(),
- _memoryManager(
- framework::defaultimplementation::AllocationLogic::UP(
- new framework::defaultimplementation::SimpleMemoryLogic(
- _clock, 1024 * 1024 * 1024)))
+ _clock()
{
_compRegister.setClock(_clock);
- _compRegister.setMemoryManager(_memoryManager);
}
spi::PersistenceProvider::UP
diff --git a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp
index 5f24d28a309..cfd73d1a7cb 100644
--- a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp
+++ b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp
@@ -31,7 +31,7 @@ MemFileCache::returnToCache(MemFileCache::Entry& entry)
MemoryUsage newUsage = entry._file.getCacheSize();
- if (_memoryToken->getSize() == 0 || newUsage.sum() == 0) {
+ if (_cacheLimit.sum() == 0 || newUsage.sum() == 0) {
entry._file.flushToDisk();
eraseNoLock(id);
return;
@@ -155,10 +155,6 @@ MemFileCache::MemFileCache(framework::ComponentRegister& componentRegister,
MemFilePersistenceCacheMetrics& metrics)
: Component(componentRegister, "memfilecache"),
_lastUsedCounter(0),
- _allocationType(getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType(
- "memfilecache", framework::MemoryAllocationType::CACHE))),
- _memoryToken(getMemoryManager().allocate(_allocationType, 0, 0, 200)),
_metrics(metrics),
_bodyEvicter(_metrics.body_evictions),
_headerEvicter(_metrics.header_evictions),
@@ -172,7 +168,6 @@ MemFileCache::setCacheSize(MemoryUsage cacheSize)
vespalib::LockGuard lock(_cacheLock);
_cacheLimit = cacheSize;
- _memoryToken->resize(std::min(_memoryToken->getSize(), _cacheLimit.sum()), _cacheLimit.sum());
evictWhileFull();
}
@@ -491,7 +486,7 @@ MemFileCache::Statistics
MemFileCache::getCacheStats() const
{
vespalib::LockGuard lock(_cacheLock);
- return Statistics(_memoryUsage, _memoryToken->getSize(), _entries.size());
+ return Statistics(_memoryUsage, _cacheLimit.sum(), _entries.size());
}
void
diff --git a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h
index e45f8f7f1f6..36e6e6b641a 100644
--- a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h
+++ b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h
@@ -21,7 +21,6 @@
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/generic/component/component.h>
@@ -191,8 +190,6 @@ private:
LRUCache _entries;
uint64_t _lastUsedCounter;
- const framework::MemoryAllocationType& _allocationType;
- framework::MemoryToken::UP _memoryToken;
MemFilePersistenceCacheMetrics& _metrics;
@@ -273,7 +270,6 @@ public:
/**
* Used for unit testing only.
*/
- framework::MemoryToken& getMemoryToken() { return *_memoryToken; }
const MemFilePersistenceCacheMetrics& getMetrics() const {
return _metrics;
}
@@ -283,7 +279,7 @@ public:
*/
void setCacheSize(MemoryUsage limits);
- uint64_t getCacheSize() { return _memoryToken->getSize(); }
+ uint64_t getCacheSize() { return _cacheLimit.sum(); }
/**
* NOTE: takes lock, never call from within memfilecache code.
diff --git a/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp b/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp
index 1dccd255ebb..9781c28823d 100644
--- a/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp
+++ b/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp
@@ -11,7 +11,6 @@
#include <vespa/memfilepersistence/spi/memfilepersistenceprovidermetrics.h>
#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
#include <vespa/storageframework/defaultimplementation/component/componentregisterimpl.h>
-#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h>
#include <vespa/vespalib/util/programoptions.h>
#include <vespa/config/helper/configgetter.hpp>
#include <vespa/config/subscription/configuri.h>
@@ -139,7 +138,6 @@ namespace {
framework::defaultimplementation::ComponentRegisterImpl _compReg;
framework::Component _component;
framework::defaultimplementation::RealClock _clock;
- framework::defaultimplementation::NoMemoryManager _memoryMan;
MemFilePersistenceMetrics _metrics;
MemFilePersistenceThreadMetrics* _threadMetrics;
std::unique_ptr<MemFileCache> _cache;
@@ -177,7 +175,6 @@ namespace {
_docType("foo", 1)
{
_compReg.setClock(_clock);
- _compReg.setMemoryManager(_memoryMan);
_cache.reset(new MemFileCache(_compReg, _metrics._cache));
if (documentConfigId == 0) {
_repo.reset(new DocumentTypeRepo(_docType));
diff --git a/messagebus-disc/pom.xml b/messagebus-disc/pom.xml
index 46158b0ca60..89741dce82b 100644
--- a/messagebus-disc/pom.xml
+++ b/messagebus-disc/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>messagebus-disc</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/messagebus/pom.xml b/messagebus/pom.xml
index 7fb44787cdf..7c37bccbd9a 100644
--- a/messagebus/pom.xml
+++ b/messagebus/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>messagebus</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/messagebus/src/vespa/messagebus/messagebus.cpp b/messagebus/src/vespa/messagebus/messagebus.cpp
index fa646b55221..fd1ad2908c7 100644
--- a/messagebus/src/vespa/messagebus/messagebus.cpp
+++ b/messagebus/src/vespa/messagebus/messagebus.cpp
@@ -8,6 +8,7 @@
#include "protocolrepository.h"
#include <vespa/messagebus/network/inetwork.h>
#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/log/log.h>
LOG_SETUP(".messagebus");
diff --git a/messagebus/src/vespa/messagebus/messenger.cpp b/messagebus/src/vespa/messagebus/messenger.cpp
index 4b612b66c31..63df7f2f482 100644
--- a/messagebus/src/vespa/messagebus/messenger.cpp
+++ b/messagebus/src/vespa/messagebus/messenger.cpp
@@ -2,6 +2,7 @@
#include "messenger.h"
#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/log/log.h>
LOG_SETUP(".messenger");
diff --git a/metrics/pom.xml b/metrics/pom.xml
index ec64d0d392c..628ab66552d 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>metrics</artifactId>
diff --git a/node-admin/pom.xml b/node-admin/pom.xml
index f7691cda3f7..983e4d3a832 100644
--- a/node-admin/pom.xml
+++ b/node-admin/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>node-admin</artifactId>
diff --git a/node-admin/src/main/application/services.xml b/node-admin/src/main/application/services.xml
new file mode 100644
index 00000000000..030c42ac8c5
--- /dev/null
+++ b/node-admin/src/main/application/services.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<jdisc id="node-admin" jetty="true" version="1.0">
+ <!-- Please update container test when changing this file -->
+ <accesslog type="vespa" fileNamePattern="logs/vespa/node-admin/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" />
+ <handler id="com.yahoo.vespa.hosted.node.admin.restapi.RestApiHandler" bundle="node-admin">
+ <binding>http://*/rest/*</binding>
+ </handler>
+ <component id="node-admin" class="com.yahoo.vespa.hosted.node.admin.provider.NodeAdminProvider" bundle="node-admin"/>
+ <component id="docker-api" class="com.yahoo.vespa.hosted.dockerapi.DockerImpl" bundle="docker-api"/>
+ <component id="metrics-wrapper" class="com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper" bundle="docker-api"/>
+
+ <config name='vespa.hosted.dockerapi.docker'>
+ <isRunningLocally>false</isRunningLocally>
+ </config>
+
+ <config name='vespa.hosted.node.admin.node-admin'>
+ <isRunningLocally>false</isRunningLocally>
+ <restartOnDeploy>true</restartOnDeploy>
+ </config>
+
+ <nodes type="host"/>
+</jdisc>
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
index d3af55cdff5..d2863468ee7 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
@@ -18,11 +18,13 @@ import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.yahoo.vespa.defaults.Defaults.getDefaults;
@@ -99,7 +101,9 @@ public class DockerOperationsImpl implements DockerOperations {
InetAddress nodeInetAddress = environment.getInetAddressForHost(nodeSpec.hostname);
final boolean isIPv6 = nodeInetAddress instanceof Inet6Address;
- String configServers = String.join(",", environment.getConfigServerHosts());
+ String configServers = environment.getConfigServerUris().stream()
+ .map(URI::getHost)
+ .collect(Collectors.joining(","));
Docker.CreateContainerCommand command = docker.createContainerCommand(
nodeSpec.wantedDockerImage.get(),
ContainerResources.from(nodeSpec.minCpuCores, nodeSpec.minMainMemoryAvailableGb),
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 a3a647e1d14..f41d7deb04b 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
@@ -21,6 +21,7 @@ import com.yahoo.vespa.hosted.node.admin.util.SecretAgentScheduleMaker;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -139,7 +140,7 @@ public class StorageMaintainer {
Process duCommand = new ProcessBuilder().command(command).start();
if (!duCommand.waitFor(60, TimeUnit.SECONDS)) {
duCommand.destroy();
- throw new RuntimeException("Disk usage command timedout, aborting.");
+ throw new RuntimeException("Disk usage command timed out, aborting.");
}
String output = IOUtils.readAll(new InputStreamReader(duCommand.getInputStream()));
String[] results = output.split("\t");
@@ -310,8 +311,8 @@ public class StorageMaintainer {
* @throws RuntimeException if exit code != 0
*/
public String getHardwareDivergence() {
- String configServers = environment.getConfigServerHosts().stream()
- .map(configServer -> "http://" + configServer + ":" + 4080)
+ String configServers = environment.getConfigServerUris().stream()
+ .map(URI::getHost)
.collect(Collectors.joining(","));
return executeMaintainer("com.yahoo.vespa.hosted.node.verification.spec.SpecVerifier", configServers);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java
index 906e6b55640..e380e7a2e26 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java
@@ -34,6 +34,7 @@ import java.util.stream.Collectors;
import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.RESUMED;
import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN;
+import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.TRANSITIONING;
/**
* Pulls information from node repository and forwards containers to run to node admin.
@@ -110,15 +111,15 @@ public class NodeAdminStateUpdater {
return this.getClass().getSimpleName() + "@" + Integer.toString(System.identityHashCode(this));
}
- public enum State { RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED}
+ public enum State { TRANSITIONING, RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED}
public Map<String, Object> getDebugPage() {
Map<String, Object> debug = new LinkedHashMap<>();
synchronized (monitor) {
debug.put("dockerHostHostName", dockerHostHostName);
+ debug.put("wantedState", wantedState);
+ debug.put("currentState", currentState);
debug.put("NodeAdmin", nodeAdmin.debugInfo());
- debug.put("Wanted State: ", wantedState);
- debug.put("Current State: ", currentState);
}
return debug;
}
@@ -185,7 +186,7 @@ public class NodeAdminStateUpdater {
log.log(LogLevel.ERROR, "Error while trying to converge to " + wantedStateCopy, e);
}
- if (wantedStateCopy != RESUMED && currentState == RESUMED) {
+ if (wantedStateCopy != RESUMED && currentState == TRANSITIONING) {
Duration subsystemFreezeDuration = nodeAdmin.subsystemFreezeDuration();
if (subsystemFreezeDuration.compareTo(FREEZE_CONVERGENCE_TIMEOUT) > 0) {
// We have spent too much time trying to freeze and node admin is still not frozen.
@@ -203,8 +204,9 @@ public class NodeAdminStateUpdater {
* with respect to: freeze, Orchestrator, and services running.
*/
private void convergeState(State wantedState) {
- if (currentState == wantedState) {
- return;
+ if (currentState == wantedState) return;
+ synchronized (monitor) {
+ currentState = TRANSITIONING;
}
boolean wantFrozen = wantedState != RESUMED;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index 09eb14039e8..b8a8bbfd6ad 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -524,6 +524,7 @@ public class NodeAgentImpl implements NodeAgent {
Docker.ContainerStats stats = containerStats.get();
final String APP = MetricReceiverWrapper.APPLICATION_NODE;
final int totalNumCpuCores = ((List<Number>) ((Map) stats.getCpuStats().get("cpu_usage")).get("percpu_usage")).size();
+ final long cpuContainerKernelTime = ((Number) ((Map) stats.getCpuStats().get("cpu_usage")).get("usage_in_kernelmode")).longValue();
final long cpuContainerTotalTime = ((Number) ((Map) stats.getCpuStats().get("cpu_usage")).get("total_usage")).longValue();
final long cpuSystemTotalTime = ((Number) stats.getCpuStats().get("system_cpu_usage")).longValue();
final long memoryTotalBytes = ((Number) stats.getMemoryStats().get("limit")).longValue();
@@ -532,26 +533,28 @@ public class NodeAgentImpl implements NodeAgent {
final long diskTotalBytes = (long) (nodeSpec.minDiskAvailableGb * BYTES_IN_GB);
final Optional<Long> diskTotalBytesUsed = storageMaintainer.getDiskUsageFor(containerName);
- // CPU usage by a container as percentage of total host CPU, cpuPercentageOfHost, is given by dividing used
- // CPU time by the container with CPU time used by the entire system.
- // CPU usage by a container as percentage of total CPU allocated to it is given by dividing the
- // cpuPercentageOfHost with the ratio of container minCpuCores by total number of CPU cores.
- double cpuPercentageOfHost = lastCpuMetric.getCpuUsagePercentage(cpuContainerTotalTime, cpuSystemTotalTime);
- double cpuPercentageOfAllocated = totalNumCpuCores * cpuPercentageOfHost / nodeSpec.minCpuCores;
+ lastCpuMetric.updateCpuDeltas(cpuSystemTotalTime, cpuContainerTotalTime, cpuContainerKernelTime);
+
+ // Ratio of CPU cores allocated to this container to total number of CPU cores on this host
+ final double allocatedCpuRatio = nodeSpec.minCpuCores / totalNumCpuCores;
+ double cpuUsageRatioOfAllocated = lastCpuMetric.getCpuUsageRatio() / allocatedCpuRatio;
+ double cpuKernelUsageRatioOfAllocated = lastCpuMetric.getCpuKernelUsageRatio() / allocatedCpuRatio;
+
long memoryTotalBytesUsed = memoryTotalBytesUsage - memoryTotalBytesCache;
- double memoryPercentUsed = 100.0 * memoryTotalBytesUsed / memoryTotalBytes;
- Optional<Double> diskPercentUsed = diskTotalBytesUsed.map(used -> 100.0 * used / diskTotalBytes);
+ double memoryUsageRatio = (double) memoryTotalBytesUsed / memoryTotalBytes;
+ Optional<Double> diskUsageRatio = diskTotalBytesUsed.map(used -> (double) used / diskTotalBytes);
List<DimensionMetrics> metrics = new ArrayList<>();
DimensionMetrics.Builder systemMetricsBuilder = new DimensionMetrics.Builder(APP, dimensions)
.withMetric("mem.limit", memoryTotalBytes)
.withMetric("mem.used", memoryTotalBytesUsed)
- .withMetric("mem.util", memoryPercentUsed)
- .withMetric("cpu.util", cpuPercentageOfAllocated)
+ .withMetric("mem.util", 100 * memoryUsageRatio)
+ .withMetric("cpu.util", 100 * cpuUsageRatioOfAllocated)
+ .withMetric("cpu.sys.util", 100 * cpuKernelUsageRatioOfAllocated)
.withMetric("disk.limit", diskTotalBytes);
diskTotalBytesUsed.ifPresent(diskUsed -> systemMetricsBuilder.withMetric("disk.used", diskUsed));
- diskPercentUsed.ifPresent(diskUtil -> systemMetricsBuilder.withMetric("disk.util", diskUtil));
+ diskUsageRatio.ifPresent(diskRatio -> systemMetricsBuilder.withMetric("disk.util", 100 * diskRatio));
metrics.add(systemMetricsBuilder.build());
stats.getNetworks().forEach((interfaceName, interfaceStats) -> {
@@ -612,17 +615,35 @@ public class NodeAgentImpl implements NodeAgent {
}
class CpuUsageReporter {
+ private long containerKernelUsage = 0;
private long totalContainerUsage = 0;
private long totalSystemUsage = 0;
- double getCpuUsagePercentage(long currentContainerUsage, long currentSystemUsage) {
- long deltaSystemUsage = currentSystemUsage - totalSystemUsage;
- double cpuUsagePct = (deltaSystemUsage == 0 || totalSystemUsage == 0) ?
- 0 : 100.0 * (currentContainerUsage - totalContainerUsage) / deltaSystemUsage;
+ private long deltaContainerKernelUsage;
+ private long deltaContainerUsage;
+ private long deltaSystemUsage;
+
+ private void updateCpuDeltas(long totalSystemUsage, long totalContainerUsage, long containerKernelUsage) {
+ deltaSystemUsage = this.totalSystemUsage == 0 ? 0 : (totalSystemUsage - this.totalSystemUsage);
+ deltaContainerUsage = totalContainerUsage - this.totalContainerUsage;
+ deltaContainerKernelUsage = containerKernelUsage - this.containerKernelUsage;
+
+ this.totalSystemUsage = totalSystemUsage;
+ this.totalContainerUsage = totalContainerUsage;
+ this.containerKernelUsage = containerKernelUsage;
+ }
+
+ /**
+ * Returns the CPU usage ratio for the docker container that this NodeAgent is managing
+ * in the time between the last two times updateCpuDeltas() was called. This is calculated
+ * by dividing the CPU time used by the container with the CPU time used by the entire system.
+ */
+ double getCpuUsageRatio() {
+ return deltaSystemUsage == 0 ? Double.NaN : (double) deltaContainerUsage / deltaSystemUsage;
+ }
- totalContainerUsage = currentContainerUsage;
- totalSystemUsage = currentSystemUsage;
- return cpuUsagePct;
+ double getCpuKernelUsageRatio() {
+ return deltaSystemUsage == 0 ? Double.NaN : (double) deltaContainerKernelUsage / deltaSystemUsage;
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java
index 8283c90e43d..b5c439e209a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java
@@ -31,11 +31,9 @@ public class NodeRepositoryImpl implements NodeRepository {
private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(NodeRepositoryImpl.class);
private final ConfigServerHttpRequestExecutor requestExecutor;
- private final int port;
- public NodeRepositoryImpl(ConfigServerHttpRequestExecutor requestExecutor, int port) {
+ public NodeRepositoryImpl(ConfigServerHttpRequestExecutor requestExecutor) {
this.requestExecutor = requestExecutor;
- this.port = port;
}
@Override
@@ -43,7 +41,6 @@ public class NodeRepositoryImpl implements NodeRepository {
try {
final GetNodesResponse nodesForHost = requestExecutor.get(
"/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true",
- port,
GetNodesResponse.class);
if (nodesForHost.nodes == null) {
@@ -71,7 +68,6 @@ public class NodeRepositoryImpl implements NodeRepository {
public Optional<ContainerNodeSpec> getContainerNodeSpec(String hostName) {
try {
GetNodesResponse.Node nodeResponse = requestExecutor.get("/nodes/v2/node/" + hostName,
- port,
GetNodesResponse.Node.class);
if (nodeResponse == null) {
return Optional.empty();
@@ -86,7 +82,7 @@ public class NodeRepositoryImpl implements NodeRepository {
public List<ContainerAclSpec> getContainerAclSpecs(String hostName) {
try {
final String path = String.format("/nodes/v2/acl/%s?children=true", hostName);
- final GetAclResponse response = requestExecutor.get(path, port, GetAclResponse.class);
+ final GetAclResponse response = requestExecutor.get(path, GetAclResponse.class);
return response.trustedNodes.stream()
.map(node -> new ContainerAclSpec(
node.hostname, node.ipAddress, ContainerName.fromHostname(node.trustedBy)))
@@ -96,7 +92,7 @@ public class NodeRepositoryImpl implements NodeRepository {
}
}
- public static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node)
+ private static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node)
throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(node.nodeState, "Unknown node state");
Node.State nodeState = Node.State.valueOf(node.nodeState);
@@ -145,7 +141,6 @@ public class NodeRepositoryImpl implements NodeRepository {
public void updateNodeAttributes(final String hostName, final NodeAttributes nodeAttributes) {
UpdateNodeAttributesResponse response = requestExecutor.patch(
"/nodes/v2/node/" + hostName,
- port,
new UpdateNodeAttributesRequestBody(nodeAttributes),
UpdateNodeAttributesResponse.class);
@@ -170,7 +165,6 @@ public class NodeRepositoryImpl implements NodeRepository {
private void markNodeToState(String hostName, String state) {
NodeMessageResponse response = requestExecutor.put(
"/nodes/v2/state/" + state + "/" + hostName,
- port,
Optional.empty(), /* body */
NodeMessageResponse.class);
NODE_ADMIN_LOGGER.info(response.message);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java
index d1e996f8e93..7093f3f12e7 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java
@@ -26,11 +26,9 @@ public class OrchestratorImpl implements Orchestrator {
= ORCHESTRATOR_PATH_PREFIX + HostSuspensionApi.PATH_PREFIX;
private final ConfigServerHttpRequestExecutor requestExecutor;
- private final int port;
- public OrchestratorImpl(ConfigServerHttpRequestExecutor requestExecutor, int port) {
+ public OrchestratorImpl(ConfigServerHttpRequestExecutor requestExecutor) {
this.requestExecutor = requestExecutor;
- this.port = port;
}
@Override
@@ -38,7 +36,6 @@ public class OrchestratorImpl implements Orchestrator {
UpdateHostResponse response;
try {
response = requestExecutor.put(getSuspendPath(hostName),
- port,
Optional.empty(), /* body */
UpdateHostResponse.class);
} catch (HttpException.NotFoundException n) {
@@ -61,7 +58,6 @@ public class OrchestratorImpl implements Orchestrator {
try {
batchOperationResult = requestExecutor.put(
ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
- port,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class);
} catch (HttpException e) {
@@ -81,7 +77,7 @@ public class OrchestratorImpl implements Orchestrator {
UpdateHostResponse response;
try {
String path = getSuspendPath(hostName);
- response = requestExecutor.delete(path, port, UpdateHostResponse.class);
+ response = requestExecutor.delete(path, UpdateHostResponse.class);
} catch (HttpException.NotFoundException n) {
throw new OrchestratorNotFoundException("Failed to resume " + hostName + ", host not found");
} catch (HttpException e) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
index 109dbab924c..a343e431b5a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
@@ -29,8 +29,6 @@ import java.time.Clock;
import java.time.Duration;
import java.util.function.Function;
-import static com.yahoo.vespa.defaults.Defaults.getDefaults;
-
/**
* Set up node admin for production.
*
@@ -38,7 +36,6 @@ import static com.yahoo.vespa.defaults.Defaults.getDefaults;
*/
public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
- private static final int WEB_SERVICE_PORT = getDefaults().vespaWebServicePort();
private static final Duration NODE_AGENT_SCAN_INTERVAL = Duration.ofSeconds(30);
private static final Duration NODE_ADMIN_CONVERGE_STATE_INTERVAL = Duration.ofSeconds(30);
@@ -51,9 +48,9 @@ public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
ProcessExecuter processExecuter = new ProcessExecuter();
Environment environment = new Environment();
- ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(environment.getConfigServerHosts());
- NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor, WEB_SERVICE_PORT);
- Orchestrator orchestrator = new OrchestratorImpl(requestExecutor, WEB_SERVICE_PORT);
+ ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(environment.getConfigServerUris());
+ NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor);
+ Orchestrator orchestrator = new OrchestratorImpl(requestExecutor);
DockerOperations dockerOperations = new DockerOperationsImpl(docker, environment, processExecuter);
StorageMaintainer storageMaintainer = new StorageMaintainer(docker, processExecuter, metricReceiver, environment, clock);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java
index e825fa4ac8f..3576f37eb9a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.node.admin.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpHeaders;
+import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
@@ -12,18 +13,27 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContextBuilder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
/**
* Retries request on config server a few times before giving up. Assumes that all requests should be sent with
@@ -33,11 +43,11 @@ import java.util.Set;
*/
public class ConfigServerHttpRequestExecutor {
private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(ConfigServerHttpRequestExecutor.class);
+ private static final int MAX_LOOPS = 2;
private final ObjectMapper mapper = new ObjectMapper();
private final CloseableHttpClient client;
- private final List<String> configServerHosts;
- private final static int MAX_LOOPS = 2;
+ private final List<URI> configServerHosts;
@Override
public void finalize() throws Throwable {
@@ -50,31 +60,56 @@ public class ConfigServerHttpRequestExecutor {
super.finalize();
}
- public static ConfigServerHttpRequestExecutor create(Set<String> configServerHosts) {
- if (configServerHosts.isEmpty()) {
- throw new IllegalStateException("Environment setting for config servers missing or empty.");
- }
+ public static ConfigServerHttpRequestExecutor create(Collection<URI> configServerUris) {
+ PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(getConnectionSocketFactoryRegistry());
+ cm.setMaxTotal(200); // Increase max total connections to 200, which should be enough
+
+ // Have experienced hang in socket read, which may have been because of
+ // system defaults, therefore set explicit timeouts. Set arbitrarily to
+ // 15s > 10s used by Orchestrator lock timeout.
+ int timeoutMs = 15_000;
+ RequestConfig requestBuilder = RequestConfig.custom()
+ .setConnectTimeout(timeoutMs) // establishment of connection
+ .setConnectionRequestTimeout(timeoutMs) // connection from connection manager
+ .setSocketTimeout(timeoutMs) // waiting for data
+ .build();
+
+ return new ConfigServerHttpRequestExecutor(randomizeConfigServerUris(configServerUris),
+ HttpClientBuilder.create()
+ .setDefaultRequestConfig(requestBuilder)
+ .disableAutomaticRetries()
+ .setUserAgent("node-admin")
+ .setConnectionManager(cm).build());
+ }
- PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
- // Increase max total connections to 200, which should be enough
- cm.setMaxTotal(200);
- return new ConfigServerHttpRequestExecutor(configServerHosts,
- HttpClientBuilder.create().disableAutomaticRetries().setConnectionManager(cm).build());
+ private static Registry<ConnectionSocketFactory> getConnectionSocketFactoryRegistry() {
+ try {
+ SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
+ new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(),
+ NoopHostnameVerifier.INSTANCE);
+
+ return RegistryBuilder.<ConnectionSocketFactory>create()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", sslSocketFactory)
+ .build();
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Failed to create SSL context", e);
+ }
}
- ConfigServerHttpRequestExecutor(Set<String> configServerHosts, CloseableHttpClient client) {
- this.configServerHosts = randomizeConfigServerHosts(configServerHosts);
+ ConfigServerHttpRequestExecutor(List<URI> configServerHosts, CloseableHttpClient client) {
+ this.configServerHosts = configServerHosts;
this.client = client;
}
public interface CreateRequest {
- HttpUriRequest createRequest(String configserver) throws JsonProcessingException, UnsupportedEncodingException;
+ HttpUriRequest createRequest(URI configServerUri) throws JsonProcessingException, UnsupportedEncodingException;
}
private <T> T tryAllConfigServers(CreateRequest requestFactory, Class<T> wantedReturnType) {
Exception lastException = null;
for (int loopRetry = 0; loopRetry < MAX_LOOPS; loopRetry++) {
- for (String configServer : configServerHosts) {
+ for (URI configServer : configServerHosts) {
final CloseableHttpResponse response;
try {
response = client.execute(requestFactory.createRequest(configServer));
@@ -118,9 +153,9 @@ public class ConfigServerHttpRequestExecutor {
+ configServerHosts + ") failed, last as follows:", lastException);
}
- public <T> T put(String path, int port, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType) {
+ public <T> T put(String path, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType) {
return tryAllConfigServers(configServer -> {
- HttpPut put = new HttpPut("http://" + configServer + ":" + port + path);
+ HttpPut put = new HttpPut(configServer.resolve(path));
setContentTypeToApplicationJson(put);
if (bodyJsonPojo.isPresent()) {
put.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo.get())));
@@ -129,28 +164,28 @@ public class ConfigServerHttpRequestExecutor {
}, wantedReturnType);
}
- public <T> T patch(String path, int port, Object bodyJsonPojo, Class<T> wantedReturnType) {
+ public <T> T patch(String path, Object bodyJsonPojo, Class<T> wantedReturnType) {
return tryAllConfigServers(configServer -> {
- HttpPatch patch = new HttpPatch("http://" + configServer + ":" + port + path);
+ HttpPatch patch = new HttpPatch(configServer.resolve(path));
setContentTypeToApplicationJson(patch);
patch.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo)));
return patch;
}, wantedReturnType);
}
- public <T> T delete(String path, int port, Class<T> wantedReturnType) {
+ public <T> T delete(String path, Class<T> wantedReturnType) {
return tryAllConfigServers(configServer ->
- new HttpDelete("http://" + configServer + ":" + port + path), wantedReturnType);
+ new HttpDelete(configServer.resolve(path)), wantedReturnType);
}
- public <T> T get(String path, int port, Class<T> wantedReturnType) {
+ public <T> T get(String path, Class<T> wantedReturnType) {
return tryAllConfigServers(configServer ->
- new HttpGet("http://" + configServer + ":" + port + path), wantedReturnType);
+ new HttpGet(configServer.resolve(path)), wantedReturnType);
}
- public <T> T post(String path, int port, Object bodyJsonPojo, Class<T> wantedReturnType) {
+ public <T> T post(String path, Object bodyJsonPojo, Class<T> wantedReturnType) {
return tryAllConfigServers(configServer -> {
- HttpPost post = new HttpPost("http://" + configServer + ":" + port + path);
+ HttpPost post = new HttpPost(configServer.resolve(path));
setContentTypeToApplicationJson(post);
post.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo)));
return post;
@@ -161,9 +196,9 @@ public class ConfigServerHttpRequestExecutor {
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
}
- // Shuffle config server hosts to balance load
- private List<String> randomizeConfigServerHosts(Set<String> configServerHosts) {
- List<String> shuffledConfigServerHosts = new ArrayList<>(configServerHosts);
+ // Shuffle config server URIs to balance load
+ private static List<URI> randomizeConfigServerUris(Collection<URI> configServerUris) {
+ List<URI> shuffledConfigServerHosts = new ArrayList<>(configServerUris);
Collections.shuffle(shuffledConfigServerHosts);
return shuffledConfigServerHosts;
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java
index 65a4d84b4c3..2b5e7990617 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java
@@ -6,6 +6,7 @@ import com.yahoo.net.HostName;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import java.net.InetAddress;
+import java.net.URI;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -15,9 +16,7 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.TimeZone;
import java.util.stream.Collectors;
@@ -31,13 +30,15 @@ public class Environment {
private static final DateFormat filenameFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
public static final String APPLICATION_STORAGE_CLEANUP_PATH_PREFIX = "cleanup_";
- private static final String ENV_CONFIGSERVERS = "services__addr_configserver";
+ private static final String ENV_CONFIGSERVER_SCHEME = "CONFIG_SERVER_SCHEME";
+ private static final String ENV_CONFIGSERVER_HOSTS = "CONFIG_SERVER_ADDRESS";
+ private static final String ENV_CONFIGSERVER_PORT = "CONFIG_SERVER_PORT";
private static final String ENVIRONMENT = "ENVIRONMENT";
private static final String REGION = "REGION";
private static final String LOGSTASH_NODES = "LOGSTASH_NODES";
private static final String COREDUMP_FEED_ENDPOINT = "COREDUMP_FEED_ENDPOINT";
- private final Set<String> configServerHosts;
+ private final List<URI> configServerHosts;
private final String environment;
private final String region;
private final String parentHostHostname;
@@ -51,7 +52,7 @@ public class Environment {
}
public Environment() {
- this(getConfigServerHostsFromEnvironment(),
+ this(getConfigServerUrlsFromEnvironment(),
getEnvironmentVariable(ENVIRONMENT),
getEnvironmentVariable(REGION),
HostName.getLocalhost(),
@@ -61,7 +62,7 @@ public class Environment {
getEnvironmentVariable(COREDUMP_FEED_ENDPOINT));
}
- public Environment(Set<String> configServerHosts,
+ public Environment(List<URI> configServerHosts,
String environment,
String region,
String parentHostHostname,
@@ -79,7 +80,7 @@ public class Environment {
this.feedEndpoint = feedEndpoint;
}
- public Set<String> getConfigServerHosts() { return configServerHosts; }
+ public List<URI> getConfigServerUris() { return configServerHosts; }
public String getEnvironment() {
return environment;
@@ -95,7 +96,9 @@ public class Environment {
private static String getEnvironmentVariable(String name) {
final String value = System.getenv(name);
- if (value == null) throw new IllegalStateException(String.format("Environment variable %s not set", name));
+ if (Strings.isNullOrEmpty(value)) {
+ throw new IllegalStateException(String.format("Environment variable %s not set", name));
+ }
return value;
}
@@ -103,14 +106,14 @@ public class Environment {
return getEnvironment() + "." + getRegion();
}
- private static Set<String> getConfigServerHostsFromEnvironment() {
- String configServerHosts = System.getenv(ENV_CONFIGSERVERS);
- if (configServerHosts == null) {
- return Collections.emptySet();
- }
+ private static List<URI> getConfigServerUrlsFromEnvironment() {
+ String scheme = getEnvironmentVariable(ENV_CONFIGSERVER_SCHEME);
+ String configServerHosts = getEnvironmentVariable(ENV_CONFIGSERVER_HOSTS);
+ String port = getEnvironmentVariable(ENV_CONFIGSERVER_PORT);
- final List<String> hostNameStrings = Arrays.asList(configServerHosts.split("[,\\s]+"));
- return new HashSet<>(hostNameStrings);
+ return Arrays.stream(configServerHosts.split("[,\\s]+"))
+ .map(hostname -> URI.create(scheme + "://" + hostname + ":" + port))
+ .collect(Collectors.toList());
}
private static List<String> getLogstashNodesFromEnvironment() {
@@ -188,7 +191,7 @@ public class Environment {
}
public static class Builder {
- private Set<String> configServerHosts = Collections.emptySet();
+ private List<URI> configServerHosts = Collections.emptyList();
private String environment;
private String region;
private String parentHostHostname;
@@ -197,8 +200,10 @@ public class Environment {
private List<String> logstashNodes = Collections.emptyList();
private String feedEndpoint;
- public Builder configServerHosts(String... hosts) {
- configServerHosts = Arrays.stream(hosts).collect(Collectors.toSet());
+ public Builder configServerUris(String... hosts) {
+ configServerHosts = Arrays.stream(hosts)
+ .map(URI::create)
+ .collect(Collectors.toList());
return this;
}
diff --git a/node-admin/src/main/sh/node-admin.sh b/node-admin/src/main/sh/node-admin.sh
new file mode 100755
index 00000000000..ff0ea492318
--- /dev/null
+++ b/node-admin/src/main/sh/node-admin.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# BEGIN environment bootstrap section
+# Do not edit between here and END as this section should stay identical in all scripts
+
+findpath () {
+ myname=${0}
+ mypath=${myname%/*}
+ myname=${myname##*/}
+ if [ "$mypath" ] && [ -d "$mypath" ]; then
+ return
+ fi
+ mypath=$(pwd)
+ if [ -f "${mypath}/${myname}" ]; then
+ return
+ fi
+ echo "FATAL: Could not figure out the path where $myname lives from $0"
+ exit 1
+}
+
+COMMON_ENV=libexec/vespa/common-env.sh
+
+source_common_env () {
+ if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then
+ export VESPA_HOME
+ common_env=$VESPA_HOME/$COMMON_ENV
+ if [ -f "$common_env" ]; then
+ . $common_env
+ return
+ fi
+ fi
+ return 1
+}
+
+findroot () {
+ source_common_env && return
+ if [ "$VESPA_HOME" ]; then
+ echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'"
+ exit 1
+ fi
+ if [ "$ROOT" ] && [ -d "$ROOT" ]; then
+ VESPA_HOME="$ROOT"
+ source_common_env && return
+ fi
+ findpath
+ while [ "$mypath" ]; do
+ VESPA_HOME=${mypath}
+ source_common_env && return
+ mypath=${mypath%/*}
+ done
+ echo "FATAL: missing VESPA_HOME environment variable"
+ echo "Could not locate $COMMON_ENV anywhere"
+ exit 1
+}
+
+findroot
+
+# END environment bootstrap section
+
+Usage() {
+ cat <<EOF
+Usage: ${0##*/} [start|stop]
+Manage standalone node admin
+EOF
+
+ exit 1
+}
+
+if (( $# != 1 )); then
+ Usage
+fi
+
+case "$1" in
+ start)
+ "$VESPA_HOME"/libexec/vespa/standalone-container.sh start -s node-admin -u root
+ ;;
+ stop)
+ "$VESPA_HOME"/libexec/vespa/standalone-container.sh stop -s node-admin -u root
+ ;;
+ *) Usage ;;
+esac
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java
index ce062702a3b..1058278a02c 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java
@@ -13,8 +13,9 @@ import org.junit.Test;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -50,26 +51,13 @@ public class NodeAdminStateUpdaterTest {
@Test
public void testStateConvergence() throws IOException {
- List<ContainerNodeSpec> containersToRun = new ArrayList<>();
- for (int i = 0; i < 10; i++) {
- containersToRun.add(
- new ContainerNodeSpec.Builder()
- .hostname("host" + i + ".test.yahoo.com")
- .nodeState(i % 3 == 0 ? Node.State.active : Node.State.ready)
- .nodeType("tenant")
- .nodeFlavor("docker")
- .minCpuCores(1)
- .minMainMemoryAvailableGb(1)
- .minDiskAvailableGb(1)
- .build());
- }
- List<String> activeHostnames = Arrays.asList(
- "host0.test.yahoo.com", "host3.test.yahoo.com", "host6.test.yahoo.com", "host9.test.yahoo.com");
+ mockNodeRepo(4);
+ List<String> activeHostnames = nodeRepository.getContainersToRun(parentHostname).stream()
+ .map(node -> node.hostname)
+ .collect(Collectors.toList());
List<String> suspendHostnames = new ArrayList<>(activeHostnames);
suspendHostnames.add(parentHostname);
- when(nodeRepository.getContainersToRun(eq(parentHostname))).thenReturn(containersToRun);
-
// Initially everything is frozen to force convergence
assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
when(nodeAdmin.setFrozen(eq(false))).thenReturn(true);
@@ -148,7 +136,7 @@ public class NodeAdminStateUpdaterTest {
assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.SUSPENDED));
verify(refresher, times(2)).signalWorkToBeDone(); // No change in desired state
verifyNoMoreInteractions(nodeAdmin);
-
+
// Lets try going back to resumed
when(nodeAdmin.setFrozen(eq(false))).thenReturn(false).thenReturn(true); // NodeAgents not converged to yet
assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
@@ -163,6 +151,50 @@ public class NodeAdminStateUpdaterTest {
assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
}
+ @Test
+ public void half_transition_revert() throws IOException {
+ mockNodeRepo(3);
+
+ // Initially everything is frozen to force convergence
+ when(nodeAdmin.setFrozen(eq(false))).thenReturn(true);
+ doNothing().when(orchestrator).resume(parentHostname);
+ tickAfter(0); // The first tick should unfreeze
+ assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
+ verify(nodeAdmin, times(1)).setFrozen(eq(false));
+
+ // Let's start suspending, we are able to freeze the nodes, but orchestrator denies suspension
+ when(nodeAdmin.subsystemFreezeDuration()).thenReturn(Duration.ofSeconds(1));
+ when(nodeAdmin.setFrozen(eq(true))).thenReturn(true);
+ doThrow(new RuntimeException("Cannot allow to suspend because some reason"))
+ .when(orchestrator).suspend(eq(parentHostname));
+
+ assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN));
+ tickAfter(0);
+ verify(nodeAdmin, times(1)).setFrozen(eq(true));
+
+ // We change our mind, want to remain resumed
+ assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
+ tickAfter(0);
+ assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED));
+ verify(nodeAdmin, times(2)).setFrozen(eq(false)); // Make sure that we unfreeze!
+ }
+
+ private void mockNodeRepo(int numberOfNodes) throws IOException {
+ List<ContainerNodeSpec> containersToRun = IntStream.range(0, numberOfNodes)
+ .mapToObj(i -> new ContainerNodeSpec.Builder()
+ .hostname("host" + i + ".test.yahoo.com")
+ .nodeState(Node.State.active)
+ .nodeType("tenant")
+ .nodeFlavor("docker")
+ .minCpuCores(1)
+ .minMainMemoryAvailableGb(1)
+ .minDiskAvailableGb(1)
+ .build())
+ .collect(Collectors.toList());
+
+ when(nodeRepository.getContainersToRun(eq(parentHostname))).thenReturn(containersToRun);
+ }
+
private void tickAfter(int seconds) {
clock.advance(Duration.ofSeconds(seconds));
refresher.tick();
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java
index 7456b07d2f4..45f5de3d1a5 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java
@@ -17,6 +17,7 @@ import org.junit.Test;
import java.io.IOException;
import java.net.ServerSocket;
+import java.net.URI;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
@@ -35,9 +36,7 @@ import static org.junit.Assert.fail;
*/
public class NodeRepositoryImplTest {
private JDisc container;
- private int port;
- private final ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(
- Collections.singleton("127.0.0.1"));
+ private ConfigServerHttpRequestExecutor requestExecutor;
private int findRandomOpenPort() throws IOException {
@@ -56,14 +55,15 @@ public class NodeRepositoryImplTest {
*/
@Before
public void startContainer() throws Exception {
- port = findRandomOpenPort();
- System.err.println("PORT IS " + port);
+ final int port = findRandomOpenPort();
+ requestExecutor = ConfigServerHttpRequestExecutor.create(
+ Collections.singleton(URI.create("http://127.0.0.1:" + port)));
container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(port), Networking.enable);
}
private void waitForJdiscContainerToServe() throws InterruptedException {
Instant start = Instant.now();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
while (Instant.now().minusSeconds(120).isBefore(start)) {
try {
nodeRepositoryApi.getContainersToRun("foobar");
@@ -85,7 +85,7 @@ public class NodeRepositoryImplTest {
@Test
public void testGetContainersToRunApi() throws IOException, InterruptedException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
String dockerHostHostname = "dockerhost1.yahoo.com";
final List<ContainerNodeSpec> containersToRun = nodeRepositoryApi.getContainersToRun(dockerHostHostname);
@@ -104,7 +104,7 @@ public class NodeRepositoryImplTest {
@Test
public void testGetContainer() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
String hostname = "host4.yahoo.com";
Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname);
assertThat(nodeSpec.isPresent(), is(true));
@@ -114,7 +114,7 @@ public class NodeRepositoryImplTest {
@Test
public void testGetContainerForNonExistingNode() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
String hostname = "host-that-does-not-exist";
Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname);
assertFalse(nodeSpec.isPresent());
@@ -123,7 +123,7 @@ public class NodeRepositoryImplTest {
@Test
public void testUpdateNodeAttributes() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
String hostname = "host4.yahoo.com";
nodeRepositoryApi.updateNodeAttributes(
hostname,
@@ -136,7 +136,7 @@ public class NodeRepositoryImplTest {
@Test(expected = RuntimeException.class)
public void testUpdateNodeAttributesWithBadValue() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
String hostname = "host4.yahoo.com";
nodeRepositoryApi.updateNodeAttributes(
hostname,
@@ -148,7 +148,7 @@ public class NodeRepositoryImplTest {
@Test
public void testMarkAsReady() throws InterruptedException, IOException {
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
waitForJdiscContainerToServe();
nodeRepositoryApi.markAsDirty("host5.yahoo.com");
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java
index 0a11ddd1e62..779a0a6a376 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java
@@ -24,15 +24,12 @@ public class OrchestratorImplTest {
private static final String hostName = "host123.yahoo.com";
private final ConfigServerHttpRequestExecutor requestExecutor = mock(ConfigServerHttpRequestExecutor.class);
- private final int port = 1234;
- private final OrchestratorImpl orchestrator = new OrchestratorImpl(requestExecutor, port);
-
+ private final OrchestratorImpl orchestrator = new OrchestratorImpl(requestExecutor);
@Test
public void testSuspendCall() {
when(requestExecutor.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
- port,
Optional.empty(),
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, null));
@@ -44,7 +41,6 @@ public class OrchestratorImplTest {
public void testSuspendCallWithFailureReason() {
when(requestExecutor.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
- port,
Optional.empty(),
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail")));
@@ -56,7 +52,6 @@ public class OrchestratorImplTest {
public void testSuspendCallWithNotFound() {
when(requestExecutor.put(
any(String.class),
- any(Integer.class),
any(),
any()
)).thenThrow(new HttpException.NotFoundException("Not Found"));
@@ -68,7 +63,6 @@ public class OrchestratorImplTest {
public void testSuspendCallWithSomeOtherException() {
when(requestExecutor.put(
any(String.class),
- any(Integer.class),
any(),
any()
)).thenThrow(new RuntimeException("Some parameter was wrong"));
@@ -81,7 +75,6 @@ public class OrchestratorImplTest {
public void testResumeCall() {
when(requestExecutor.delete(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
- port,
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, null));
@@ -92,7 +85,6 @@ public class OrchestratorImplTest {
public void testResumeCallWithFailureReason() {
when(requestExecutor.delete(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
- port,
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail")));
@@ -103,7 +95,6 @@ public class OrchestratorImplTest {
public void testResumeCallWithNotFound() {
when(requestExecutor.delete(
any(String.class),
- any(Integer.class),
any()
)).thenThrow(new HttpException.NotFoundException("Not Found"));
@@ -114,7 +105,6 @@ public class OrchestratorImplTest {
public void testResumeCallWithSomeOtherException() {
when(requestExecutor.put(
any(String.class),
- any(Integer.class),
any(),
any()
)).thenThrow(new RuntimeException("Some parameter was wrong"));
@@ -130,7 +120,6 @@ public class OrchestratorImplTest {
when(requestExecutor.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
- port,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
)).thenReturn(BatchOperationResult.successResult());
@@ -146,7 +135,6 @@ public class OrchestratorImplTest {
when(requestExecutor.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
- port,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
)).thenReturn(new BatchOperationResult(failureReason));
@@ -162,7 +150,6 @@ public class OrchestratorImplTest {
when(requestExecutor.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
- port,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
)).thenThrow(new RuntimeException(exceptionMessage));
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java
index d431f175b1c..67cd2c79034 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java
@@ -3,19 +3,22 @@ package com.yahoo.vespa.hosted.node.admin.util;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.yahoo.collections.ArraySet;
-import org.apache.http.HttpEntity;
-import org.apache.http.StatusLine;
+import org.apache.http.HttpVersion;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicStatusLine;
+import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
-import java.util.Set;
+import java.util.Arrays;
+import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
@@ -38,85 +41,76 @@ public class ConfigServerHttpRequestExecutorTest {
@JsonIgnoreProperties(ignoreUnknown = true)
public static class TestPojo {
@JsonProperty("foo")
- public String foo;
+ String foo;
@JsonProperty("error-code")
- public Integer errorCode;
+ Integer errorCode;
}
+ private final String uri1 = "http://host1:666";
+ private final String uri2 = "http://host2:666";
+ private final List<URI> configServers = Arrays.asList(URI.create(uri1), URI.create(uri2));
private final StringBuilder mockLog = new StringBuilder();
+
+ private ConfigServerHttpRequestExecutor executor;
private int mockReturnCode = 200;
- private CloseableHttpClient createClientMock() throws IOException {
+ @Before
+ public void initExecutor() throws IOException {
CloseableHttpClient httpMock = mock(CloseableHttpClient.class);
when(httpMock.execute(any())).thenAnswer(invocationOnMock -> {
HttpGet get = (HttpGet) invocationOnMock.getArguments()[0];
mockLog.append(get.getMethod()).append(" ").append(get.getURI()).append(" ");
- CloseableHttpResponse response = mock(CloseableHttpResponse.class);
- StatusLine statusLine = mock(StatusLine.class);
- when(statusLine.getStatusCode()).thenReturn(mockReturnCode);
- when(response.getStatusLine()).thenReturn(statusLine);
if (mockReturnCode == 100000) throw new RuntimeException("FAIL");
- HttpEntity entity = mock(HttpEntity.class);
- when(response.getEntity()).thenReturn(entity);
+
+ BasicStatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, mockReturnCode, null);
+ BasicHttpEntity entity = new BasicHttpEntity();
String returnMessage = "{\"foo\":\"bar\", \"no\":3, \"error-code\": " + mockReturnCode + "}";
InputStream stream = new ByteArrayInputStream(returnMessage.getBytes(StandardCharsets.UTF_8));
- when(entity.getContent()).thenReturn(stream);
+ entity.setContent(stream);
+
+ CloseableHttpResponse response = mock(CloseableHttpResponse.class);
+ when(response.getEntity()).thenReturn(entity);
+ when(response.getStatusLine()).thenReturn(statusLine);
+
return response;
});
doNothing().when(httpMock).close();
- return httpMock;
+ executor = new ConfigServerHttpRequestExecutor(configServers, httpMock);
}
@Test
public void testBasicParsingSingleServer() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
- ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock());
- TestPojo answer = executor.get("/path", 666, TestPojo.class);
+ TestPojo answer = executor.get("/path", TestPojo.class);
assertThat(answer.foo, is("bar"));
assertLogStringContainsGETForAHost();
}
@Test(expected = HttpException.class)
public void testBasicFailure() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Server is returning 400, no retries.
mockReturnCode = 400;
- ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock());
- TestPojo testPojo = executor.get("/path", 666, TestPojo.class);
+ TestPojo testPojo = executor.get("/path", TestPojo.class);
assertEquals(testPojo.errorCode.intValue(), mockReturnCode);
assertLogStringContainsGETForAHost();
}
@Test
public void testBasicSuccessWithNoRetries() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Server is returning 201, no retries.
mockReturnCode = 201;
- ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock());
- TestPojo testPojo = executor.get("/path", 666, TestPojo.class);
+ TestPojo testPojo = executor.get("/path", TestPojo.class);
assertEquals(testPojo.errorCode.intValue(), mockReturnCode);
assertLogStringContainsGETForAHost();
}
@Test
public void testRetries() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Client is throwing exception, should be retries.
mockReturnCode = 100000;
- ConfigServerHttpRequestExecutor executor =
- new ConfigServerHttpRequestExecutor(configServers, createClientMock());
try {
- executor.get("/path", 666, TestPojo.class);
+ executor.get("/path", TestPojo.class);
fail("Expected failure");
} catch (Exception e) {
// ignore
@@ -129,15 +123,10 @@ public class ConfigServerHttpRequestExecutorTest {
@Test
public void testRetriesOnBadHttpResponseCode() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Client is throwing exception, should be retries.
mockReturnCode = 503;
- ConfigServerHttpRequestExecutor executor =
- new ConfigServerHttpRequestExecutor(configServers, createClientMock());
try {
- executor.get("/path", 666, TestPojo.class);
+ executor.get("/path", TestPojo.class);
fail("Expected failure");
} catch (Exception e) {
// ignore
@@ -151,14 +140,10 @@ public class ConfigServerHttpRequestExecutorTest {
@Test
public void testNotFound() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Server is returning 404, special exception is thrown.
mockReturnCode = 404;
- ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock());
try {
- executor.get("/path", 666, TestPojo.class);
+ executor.get("/path", TestPojo.class);
fail("Expected exception");
} catch (HttpException.NotFoundException e) {
// ignore
@@ -168,14 +153,9 @@ public class ConfigServerHttpRequestExecutorTest {
@Test
public void testConflict() throws Exception {
- Set<String> configServers = new ArraySet<>(2);
- configServers.add("host1");
- configServers.add("host2");
// Server is returning 409, no exception is thrown.
mockReturnCode = 409;
- ConfigServerHttpRequestExecutor executor =
- new ConfigServerHttpRequestExecutor(configServers, createClientMock());
- executor.get("/path", 666, TestPojo.class);
+ executor.get("/path", TestPojo.class);
assertLogStringContainsGETForAHost();
}
diff --git a/node-admin/src/test/resources/docker.stats.json b/node-admin/src/test/resources/docker.stats.json
index 3b1087b9202..ff4a2fde943 100644
--- a/node-admin/src/test/resources/docker.stats.json
+++ b/node-admin/src/test/resources/docker.stats.json
@@ -36,7 +36,7 @@
44567860460,
39049895962
],
- "usage_in_kernelmode":44050000000,
+ "usage_in_kernelmode":44106083850,
"usage_in_usermode":158950000000
},
"system_cpu_usage":5876882680000000,
diff --git a/node-admin/src/test/resources/expected.container.system.metrics.txt b/node-admin/src/test/resources/expected.container.system.metrics.txt
index 8a4d696b08e..023d3958c60 100644
--- a/node-admin/src/test/resources/expected.container.system.metrics.txt
+++ b/node-admin/src/test/resources/expected.container.system.metrics.txt
@@ -11,6 +11,7 @@ s:
"mem.limit": 4294967296,
"mem.used": 1073741824,
"disk.used": 39625000000,
+ "cpu.sys.util": 3.402,
"disk.util": 15.85,
"cpu.util": 5.4,
"mem.util": 25.0,
diff --git a/node-admin/vespa-node-admin.spec b/node-admin/vespa-node-admin.spec
new file mode 100644
index 00000000000..049e66bf22f
--- /dev/null
+++ b/node-admin/vespa-node-admin.spec
@@ -0,0 +1,49 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# Force special prefix for Vespa
+%define _prefix /opt/vespa
+
+# Hack to speed up jar packing for now. This does not affect the rpm size.
+%define __jar_repack %{nil}
+
+Name: vespa-node-admin
+Version: %version
+Release: 1%{?dist}
+BuildArch: noarch
+Summary: Vespa Node Admin
+Group: Applications/Databases
+License: Commercial
+URL: http://vespa.ai
+
+Requires: bash
+Requires: java-1.8.0-openjdk-headless
+Requires: vespa-standalone-container
+
+Conflicts: vespa
+
+%description
+The Node Admin manages the machine so it is a suitable host for one or more
+Vespa nodes.
+
+%install
+app_dir=%?buildroot%_prefix/conf/node-admin-app
+mkdir -p "$app_dir"/components
+cp node-admin/src/main/application/services.xml "$app_dir"
+
+declare -a jar_components=(
+ node-admin/target/node-admin-jar-with-dependencies.jar
+ docker-api/target/docker-api-jar-with-dependencies.jar
+)
+for path in "${jar_components[@]}"; do
+ cp "$path" "$app_dir"/components
+done
+
+mkdir -p %buildroot%_prefix/libexec/vespa
+cp node-admin/src/main/sh/node-admin.sh %buildroot%_prefix/libexec/vespa
+
+%clean
+rm -rf %buildroot
+
+%files
+%defattr(-,vespa,vespa,-)
+%_prefix/*
diff --git a/node-maintainer/pom.xml b/node-maintainer/pom.xml
index d56e12d92c6..1fe3eb23857 100644
--- a/node-maintainer/pom.xml
+++ b/node-maintainer/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>node-maintainer</artifactId>
diff --git a/node-repository/pom.xml b/node-repository/pom.xml
index 09b0d3df3d9..6673ea487a1 100644
--- a/node-repository/pom.xml
+++ b/node-repository/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>node-repository</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/node-repository/src/main/config/node-repository.xml b/node-repository/src/main/config/node-repository.xml
index 72f9c44c8d8..d4b677cb0bb 100644
--- a/node-repository/src/main/config/node-repository.xml
+++ b/node-repository/src/main/config/node-repository.xml
@@ -11,6 +11,7 @@
<handler id="com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler" bundle="node-repository">
<binding>http://*/nodes/v2/*</binding>
+ <binding>https://*/nodes/v2/*</binding>
</handler>
<preprocess:include file="node-flavors.xml" required="false" />
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 88daa34d8ca..9b05478eb07 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
@@ -307,11 +307,16 @@ public class NodeRepository extends AbstractComponent {
/** Sets a list of nodes ready and returns the nodes in the ready state */
public List<Node> setReady(List<Node> nodes) {
- for (Node node : nodes)
- if (node.state() != Node.State.dirty)
- throw new IllegalArgumentException("Can not set " + node + " ready. It is not dirty.");
try (Mutex lock = lockUnallocated()) {
- return db.writeTo(Node.State.ready, nodes, Agent.system, Optional.empty());
+ List<Node> nodesWithResetFields = nodes.stream()
+ .map(node -> {
+ if (node.state() != Node.State.dirty)
+ throw new IllegalArgumentException("Can not set " + node + " ready. It is not dirty.");
+ return node.with(node.status().withWantToRetire(false).withWantToDeprovision(false));
+ })
+ .collect(Collectors.toList());
+
+ return db.writeTo(Node.State.ready, nodesWithResetFields, Agent.system, Optional.empty());
}
}
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 500861ad0d2..6a2376d748b 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
@@ -75,7 +75,7 @@ public abstract class ApplicationMaintainer extends Maintainer {
// Lock is acquired with a low timeout to reduce the chance of colliding with an external deployment.
try (Mutex lock = nodeRepository().lock(application, Duration.ofSeconds(1))) {
if ( ! isActive(application)) return; // became inactive since deployment was requested
- Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30));
+ Optional<Deployment> deployment = deployer.deployFromLocalActive(application);
if ( ! deployment.isPresent()) return; // this will be done at another config server
deployment.get().activate();
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 48ec71642c9..a3b4917147e 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,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.provision.maintenance;
-import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.History;
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 1e0202d4735..031d56e3164 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
@@ -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.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeType;
@@ -21,65 +22,108 @@ 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 its
+ * <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 OR system is CD,
- * as those environments have no protection against users running bogus applications, so
+ * <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>
- * Failed nodes are typically given a long expiry time to enable us to manually moved them back to
+ * 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>
- * The Chef recipe running locally on the node may set the hardwareFailureDescription to avoid the node
+ * The Chef recipe running locally on the node may set hardwareFailureDescription to avoid the node
* being automatically recycled in cases where an error has been positively detected.
*
* @author bratseth
+ * @author mpolden
*/
-public class FailedExpirer extends Expirer {
+public class FailedExpirer extends Maintainer {
private static final Logger log = Logger.getLogger(NodeRetirer.class.getName());
+
+ private static final Duration defaultExpiry = Duration.ofDays(4); // Grace period to allow recovery of data
+ private static final Duration containerExpiry = Duration.ofHours(1); // Stateless nodes, no data to recover
+ private static final int maxAllowedFailures = 5; // Stop recycling nodes after this number of failures
+
private final NodeRepository nodeRepository;
private final Zone zone;
+ private final Clock clock;
- public FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock,
- Duration failTimeout, JobControl jobControl) {
- super(Node.State.failed, History.Event.Type.failed, nodeRepository, clock, failTimeout, jobControl);
+ public FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, Duration interval,
+ JobControl jobControl) {
+ super(nodeRepository, interval, jobControl);
this.nodeRepository = nodeRepository;
this.zone = zone;
+ this.clock = clock;
}
@Override
- protected void expire(List<Node> expired) {
+ protected void maintain() {
+ List<Node> containerNodes = getExpiredNodes(containerExpiry)
+ .stream()
+ .filter(node -> node.allocation().isPresent() &&
+ node.allocation().get().membership().cluster().type() == ClusterSpec.Type.container)
+ .collect(Collectors.toList());
+ List<Node> remainingNodes = getExpiredNodes(defaultExpiry);
+ remainingNodes.removeAll(containerNodes);
+ recycle(containerNodes);
+ recycle(remainingNodes);
+ }
+
+ /** Get failed nodes that have expired according to given expiry */
+ private List<Node> getExpiredNodes(Duration expiry) {
+ return nodeRepository.getNodes(Node.State.failed).stream()
+ .filter(node -> node.history().event(History.Event.Type.failed)
+ .map(event -> event.at().plus(expiry).isBefore(clock.instant()))
+ .orElse(false))
+ .collect(Collectors.toList());
+ }
+
+ /** Move eligible nodes to dirty. This may be a subset of the given nodes */
+ private void recycle(List<Node> nodes) {
List<Node> nodesToRecycle = new ArrayList<>();
- for (Node recycleCandidate : expired) {
- if (recycleCandidate.status().hardwareFailureDescription().isPresent() || recycleCandidate.status().hardwareDivergence().isPresent()) {
- List<String> nonParkedChildren = recycleCandidate.type() != NodeType.host ? Collections.emptyList() :
- nodeRepository.getChildNodes(recycleCandidate.hostname()).stream()
+ for (Node candidate : nodes) {
+ if (hasHardwareIssue(candidate)) {
+ List<String> unparkedChildren = candidate.type() != NodeType.host ? Collections.emptyList() :
+ nodeRepository.getChildNodes(candidate.hostname()).stream()
.filter(node -> node.state() != Node.State.parked)
.map(Node::hostname)
.collect(Collectors.toList());
- if (nonParkedChildren.isEmpty()) {
- nodeRepository.park(recycleCandidate.hostname(), Agent.system, "Parked by FailedExpirer due to HW failure/divergence on node");
+ if (unparkedChildren.isEmpty()) {
+ nodeRepository.park(candidate.hostname(), Agent.system,
+ "Parked by FailedExpirer due to hardware issue");
} else {
- log.info(String.format("Expired failed node %s with HW failure/divergence is not parked because some of its children" +
- " (%s) are not yet parked", recycleCandidate.hostname(), String.join(", ", nonParkedChildren)));
+ log.info(String.format("Expired failed node %s with hardware issue was not parked because of " +
+ "unparked children: %s", candidate.hostname(),
+ String.join(", ", unparkedChildren)));
}
- } else if (! failCountIndicatesHwFail(zone, recycleCandidate) || recycleCandidate.status().failCount() < 5) {
- nodesToRecycle.add(recycleCandidate);
+ } else if (!failCountIndicatesHardwareIssue(candidate)) {
+ nodesToRecycle.add(candidate);
}
}
nodeRepository.setDirty(nodesToRecycle);
}
- private boolean failCountIndicatesHwFail(Zone zone, Node node) {
+ /** 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;
+ return (zone.environment() == Environment.prod || zone.environment() == Environment.staging) &&
+ node.status().failCount() >= maxAllowedFailures;
+ }
+
+ /** Returns whether node has any kind of hardware issue */
+ private static boolean hasHardwareIssue(Node node) {
+ return node.status().hardwareFailureDescription().isPresent() ||
+ node.status().hardwareDivergence().isPresent();
}
}
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 1fffde874fd..9e826bfcb9a 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
@@ -70,9 +70,9 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
zooKeeperAccessMaintainer = new ZooKeeperAccessMaintainer(nodeRepository, curator, durationFromEnv("zookeeper_access_maintenance_interval").orElse(defaults.zooKeeperAccessMaintenanceInterval), jobControl);
reservationExpirer = new ReservationExpirer(nodeRepository, clock, durationFromEnv("reservation_expiry").orElse(defaults.reservationExpiry), jobControl);
retiredExpirer = new RetiredExpirer(nodeRepository, deployer, clock, durationFromEnv("retired_expiry").orElse(defaults.retiredExpiry), jobControl);
- retiredEarlyExpirer = new RetiredEarlyExpirer(nodeRepository, zone, durationFromEnv("retired_early_interval").orElse(defaults.retiredEarlyInterval), jobControl, deployer, orchestrator);
+ retiredEarlyExpirer = new RetiredEarlyExpirer(nodeRepository, durationFromEnv("retired_early_interval").orElse(defaults.retiredEarlyInterval), jobControl, deployer, orchestrator);
inactiveExpirer = new InactiveExpirer(nodeRepository, clock, durationFromEnv("inactive_expiry").orElse(defaults.inactiveExpiry), jobControl);
- failedExpirer = new FailedExpirer(nodeRepository, zone, clock, durationFromEnv("failed_expiry").orElse(defaults.failedExpiry), jobControl);
+ failedExpirer = new FailedExpirer(nodeRepository, zone, clock, durationFromEnv("failed_expirer_interval").orElse(defaults.failedExpirerInterval), jobControl);
dirtyExpirer = new DirtyExpirer(nodeRepository, clock, durationFromEnv("dirty_expiry").orElse(defaults.dirtyExpiry), jobControl);
provisionedExpirer = new ProvisionedExpirer(nodeRepository, clock, durationFromEnv("provisioned_expiry").orElse(defaults.provisionedExpiry), jobControl);
nodeRebooter = new NodeRebooter(nodeRepository, clock, durationFromEnv("reboot_interval").orElse(defaults.rebootInterval), jobControl);
@@ -134,7 +134,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final Duration reservationExpiry;
private final Duration inactiveExpiry;
private final Duration retiredExpiry;
- private final Duration failedExpiry;
+ private final Duration failedExpirerInterval;
private final Duration dirtyExpiry;
private final Duration provisionedExpiry;
private final Duration rebootInterval;
@@ -156,7 +156,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
inactiveExpiry = Duration.ofHours(4); // enough time for the application owner to discover and redeploy
retiredExpiry = Duration.ofDays(4); // enough time to migrate data
retiredEarlyInterval = Duration.ofMinutes(29);
- failedExpiry = Duration.ofDays(4); // enough time to recover data even if it happens friday night
+ failedExpirerInterval = Duration.ofMinutes(10);
dirtyExpiry = Duration.ofHours(2); // enough time to clean the node
provisionedExpiry = Duration.ofHours(4);
rebootInterval = Duration.ofDays(30);
@@ -174,7 +174,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
inactiveExpiry = Duration.ofSeconds(2); // support interactive wipe start over
retiredExpiry = Duration.ofMinutes(1);
retiredEarlyInterval = Duration.ofMinutes(5);
- failedExpiry = Duration.ofMinutes(10);
+ failedExpirerInterval = Duration.ofMinutes(10);
dirtyExpiry = Duration.ofMinutes(30);
provisionedExpiry = Duration.ofHours(4);
rebootInterval = Duration.ofDays(30);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java
index 12b63f66d3f..30b5f6f737d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java
@@ -123,7 +123,7 @@ public class NodeRetirer extends Maintainer {
entry -> filterRetireableNodes(entry.getValue())));
if (retireableNodesByCluster.values().stream().mapToInt(Set::size).sum() == 0) continue;
- Optional<Deployment> deployment = deployer.deployFromLocalActive(applicationId, Duration.ofMinutes(30));
+ Optional<Deployment> deployment = deployer.deployFromLocalActive(applicationId);
if ( ! deployment.isPresent()) continue; // this will be done at another config server
Set<Node> replaceableNodes = retireableNodesByCluster.entrySet().stream()
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java
index cb2fcb89284..00543058520 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java
@@ -5,7 +5,6 @@ import com.yahoo.collections.ListMap;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.Deployment;
-import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -20,12 +19,19 @@ import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;
+/**
+ * Maintenance job which deactivates retired nodes, if given permission by orchestrator.
+ *
+ * @author hakon
+ */
+// TODO: This should be consolidated with RetiredExpirer. The only difference between this and RetiredExpirer is that
+// this runs more often by default and asks orchestrator for permission to retire nodes.
public class RetiredEarlyExpirer extends Maintainer {
+
private final Deployer deployer;
private final Orchestrator orchestrator;
public RetiredEarlyExpirer(NodeRepository nodeRepository,
- Zone zone,
Duration interval,
JobControl jobControl,
Deployer deployer,
@@ -51,12 +57,12 @@ public class RetiredEarlyExpirer extends Maintainer {
List<Node> retiredNodes = entry.getValue();
try {
- Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30));
+ Optional<Deployment> deployment = deployer.deployFromLocalActive(application);
if ( ! deployment.isPresent()) continue; // this will be done at another config server
List<Node> nodesToRemove = new ArrayList<>();
for (Node node : retiredNodes) {
- if (nodeCanBeRemoved(node)) {
+ if (canRemove(node)) {
nodesToRemove.add(node);
}
}
@@ -79,7 +85,8 @@ public class RetiredEarlyExpirer extends Maintainer {
}
}
- boolean nodeCanBeRemoved(Node node) {
+ /** Returns whether orchestrator permits given node to be removed */
+ private boolean canRemove(Node node) {
try {
orchestrator.acquirePermissionToRemove(new HostName(node.hostname()));
return true;
@@ -88,4 +95,5 @@ public class RetiredEarlyExpirer extends Maintainer {
return false;
}
}
+
}
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 9ef09858bfa..4c5c8adf576 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
@@ -50,7 +50,7 @@ public class RetiredExpirer extends Expirer {
ApplicationId application = entry.getKey();
List<Node> nodesToRemove = entry.getValue();
try {
- Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30));
+ Optional<Deployment> deployment = deployer.deployFromLocalActive(application);
if ( ! deployment.isPresent()) continue; // this will be done at another config server
nodeRepository.setRemovable(application, nodesToRemove);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
index 1de15f4e31a..7ef609d6311 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
@@ -52,7 +52,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
@Inject
public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone) {
- this(nodeRepository, flavors, zone, Clock.systemUTC(), (x,y) -> {});
+ this(nodeRepository, flavors, zone, Clock.systemUTC(), (x, y) -> {});
}
public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone, Clock clock, BiConsumer<List<Node>, String> debugRecorder) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
index 797453b12c9..b47b3544d17 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
@@ -184,8 +184,7 @@ public class NodesApiHandler extends LoggingRequestHandler {
}
private Node nodeFromRequest(HttpRequest request) {
- String path = request.getUri().getPath();
- String hostname = path.substring(path.lastIndexOf("/"));
+ String hostname = lastElement(request.getUri().getPath());
return nodeRepository.getNode(hostname).orElseThrow(() ->
new NotFoundException("No node found with hostname " + hostname));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
index a05b0be344d..c880c66abc7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
@@ -46,6 +46,11 @@ public class MockDeployer implements Deployer {
}
@Override
+ public Optional<Deployment> deployFromLocalActive(ApplicationId application) {
+ return deployFromLocalActive(application, Duration.ofSeconds(60));
+ }
+
+ @Override
public Optional<Deployment> deployFromLocalActive(ApplicationId id, Duration timeout) {
return Optional.of(new MockDeployment(provisioner, applications.get(id)));
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index bcc18cfd876..928d8082c6b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -45,8 +45,12 @@ public class MockNodeRepository extends NodeRepository {
*/
public MockNodeRepository(MockCurator curator, NodeFlavors flavors) throws Exception {
super(flavors, curator, Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(),
- new MockNameResolver().mockAnyLookup(), new DockerImage("docker-registry.domain.tld:8080/dist/vespa"));
+ new MockNameResolver()
+ .addRecord("test-container-1", "::2")
+ .mockAnyLookup(),
+ new DockerImage("docker-registry.domain.tld:8080/dist/vespa"));
this.flavors = flavors;
+
curator.setConnectionSpec("cfg1:1234,cfg2:1234,cfg3:1234");
populate();
}
@@ -91,9 +95,10 @@ public class MockNodeRepository extends NodeRepository {
node10 = node10.with(node10newStatus);
nodes.add(node10);
- nodes.add(createNode("node55", "host55.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant));
+ Node node55 = createNode("node55", "host55.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant);
+ nodes.add(node55.with(node55.status().withWantToRetire(true).withWantToDeprovision(true)));
- /** Setup docker hosts (two of these will be reserved for spares */
+ /* Setup docker hosts (two of these will be reserved for spares */
nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host));
nodes.add(createNode("dockerhost2", "dockerhost2.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host));
nodes.add(createNode("dockerhost3", "dockerhost3.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host));
@@ -133,4 +138,4 @@ public class MockNodeRepository extends NodeRepository {
provisioner.activate(transaction, application, hosts);
transaction.commit();
}
-} \ No newline at end of file
+}
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 51991a844d7..720c5b05443 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
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
+import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeType;
@@ -18,7 +19,6 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.test.ManualClock;
import com.yahoo.transaction.NestedTransaction;
-import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.curator.transaction.CuratorTransaction;
import com.yahoo.vespa.hosted.provision.Node;
@@ -26,15 +26,15 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner;
-import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver;
import org.junit.Test;
import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -42,155 +42,268 @@ import static org.junit.Assert.assertEquals;
/**
* @author bratseth
+ * @author mpolden
*/
public class FailedExpirerTest {
- private final Curator curator = new MockCurator();
- private final ManualClock clock = new ManualClock();
- private FailedExpirer failedExpirer;
-
@Test
- public void ensure_failed_nodes_are_deallocated_in_prod() throws InterruptedException {
- failureScenarioIn(SystemName.main, Environment.prod, "default");
- clock.advance(Duration.ofDays(5));
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.failed, "node1");
- assertNodeHostnames(Node.State.parked, "node2", "node3");
+ public void ensure_failed_nodes_are_deallocated_in_prod() {
+ FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod)
+ .withNode("node1")
+ .withNode("node2")
+ .withNode("node3")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, "node1", "node2", "node3")
+ .failNode(4, "node1")
+ .failWithHardwareFailure("node2", "node3");
+
+ scenario.clock().advance(Duration.ofDays(3));
+ scenario.expirer().run();
+ scenario.assertNodesIn(Node.State.failed, "node1", "node2", "node3"); // None moved yet
+
+ scenario.clock().advance(Duration.ofDays(2));
+ scenario.expirer().run();
+ scenario.assertNodesIn(Node.State.failed, "node1");
+ scenario.assertNodesIn(Node.State.parked, "node2", "node3");
}
@Test
- public void ensure_failed_nodes_are_deallocated_in_dev() throws InterruptedException {
- failureScenarioIn(SystemName.main, Environment.dev, "default");
- clock.advance(Duration.ofDays(5));
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.parked, "node2", "node3");
- assertNodeHostnames(Node.State.dirty, "node1");
+ public void ensure_failed_nodes_are_deallocated_in_dev() {
+ FailureScenario scenario = new FailureScenario(SystemName.main, Environment.dev)
+ .withNode("node1")
+ .withNode("node2")
+ .withNode("node3")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, "node1", "node2", "node3")
+ .failNode(4, "node1")
+ .failWithHardwareFailure("node2", "node3");
+
+ scenario.clock().advance(Duration.ofDays(5));
+ scenario.expirer().run();
+
+ scenario.assertNodesIn(Node.State.parked, "node2", "node3");
+ scenario.assertNodesIn(Node.State.dirty, "node1");
}
@Test
- public void ensure_failed_nodes_are_deallocated_in_cd() throws InterruptedException {
- failureScenarioIn(SystemName.cd, Environment.prod, "default");
- clock.advance(Duration.ofDays(5));
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.failed, "node1");
- assertNodeHostnames(Node.State.parked, "node2", "node3");
+ public void ensure_failed_nodes_are_deallocated_in_cd() {
+ FailureScenario scenario = new FailureScenario(SystemName.cd, Environment.prod)
+ .withNode("node1")
+ .withNode("node2")
+ .withNode("node3")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, "node1", "node2", "node3")
+ .failNode(4, "node1")
+ .failWithHardwareFailure("node2", "node3");
+
+ scenario.clock().advance(Duration.ofDays(5));
+ scenario.expirer().run();
+
+ scenario.assertNodesIn(Node.State.failed, "node1");
+ scenario.assertNodesIn(Node.State.parked, "node2", "node3");
}
@Test
- public void ensure_failed_docker_nodes_are_deallocated() throws InterruptedException {
- failureScenarioIn(SystemName.main, Environment.prod, "docker");
- clock.advance(Duration.ofDays(5));
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.parked, "node2", "node3");
- assertNodeHostnames(Node.State.dirty, "node1");
+ public void ensure_failed_docker_nodes_are_deallocated() {
+ FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod)
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3")
+ .failNode(4, "node1")
+ .failWithHardwareFailure("node2", "node3");
+
+ scenario.clock().advance(Duration.ofDays(5));
+ scenario.expirer().run();
+
+ scenario.assertNodesIn(Node.State.parked, "node2", "node3");
+ scenario.assertNodesIn(Node.State.dirty, "node1");
}
@Test
- public void ensure_parked_docker_host() throws InterruptedException {
- failureScenarioIn(SystemName.main, Environment.prod, "docker");
-
- failNode("parent2");
- setHWFailureForNode("parent2");
-
- clock.advance(Duration.ofDays(5));
- failedExpirer.run(); // Run twice because parent can only be parked after the child
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.parked, "parent2", "node2", "node3");
+ public void ensure_parked_docker_host() {
+ FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod)
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3")
+ .failNode(8, "node3")
+ .failWithHardwareFailure("node2", "node3")
+ .failWithHardwareFailure("parent2");
+
+ scenario.clock.advance(Duration.ofDays(5));
+ scenario.expirer().run(); // Run twice because parent can only be parked after the child
+ scenario.expirer().run();
+ scenario.assertNodesIn(Node.State.parked, "parent2", "node2", "node3");
}
@Test
- public void ensure_failed_docker_host_is_not_parked_unless_all_children_are() throws InterruptedException {
- failureScenarioIn(SystemName.cd, Environment.prod, "docker");
-
- failNode("parent1");
- setHWFailureForNode("parent1");
- clock.advance(Duration.ofDays(2));
- failNode("node4");
- failNode("node5");
- clock.advance(Duration.ofDays(3));
-
- failedExpirer.run(); // Run twice because parent can only be parked after the child
- failedExpirer.run();
-
- assertNodeHostnames(Node.State.failed, "parent1", "node4", "node5");
+ public void ensure_failed_docker_host_is_not_parked_unless_all_children_are() {
+ FailureScenario scenario = new FailureScenario(SystemName.cd, Environment.prod)
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2")
+ .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node4", "parent1")
+ .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node5", "parent1")
+ .setReady("node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3")
+ .failWithHardwareFailure("parent1");
+
+ scenario.clock().advance(Duration.ofDays(2));
+ scenario.failNode(1, "node4", "node5");
+ scenario.clock().advance(Duration.ofDays(3));
+
+ scenario.expirer().run(); // Run twice because parent can only be parked after the child
+ scenario.expirer().run();
+
+ scenario.assertNodesIn(Node.State.failed, "parent1", "node4", "node5");
}
- private void assertNodeHostnames(Node.State state, String... hostnames) {
- assertEquals(Stream.of(hostnames).collect(Collectors.toSet()),
- failedExpirer.nodeRepository().getNodes(state).stream().map(Node::hostname).collect(Collectors.toSet()));
- }
-
- private void setHWFailureForNode(String hostname) {
- Node node2 = failedExpirer.nodeRepository().getNode(hostname).get();
- node2 = node2.with(node2.status().withHardwareFailureDescription(Optional.of("memory_mcelog")));
- failedExpirer.nodeRepository().write(node2);
+ @Test
+ public void ensure_container_nodes_are_recycled_early() {
+ FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod)
+ .withNode("node1")
+ .withNode("node2")
+ .withNode("node3")
+ .withNode("node4")
+ .withNode("node5")
+ .withNode("node6")
+ .setReady("node1", "node2", "node3", "node4", "node5", "node6")
+ .allocate(ClusterSpec.Type.content, "node1", "node2", "node3")
+ .allocate(ClusterSpec.Type.container, "node4", "node5", "node6");
+
+ // Vespa container fails
+ scenario.failNode(1, "node4");
+
+ // 30 minutes pass, nothing happens
+ scenario.clock().advance(Duration.ofMinutes(30));
+ scenario.expirer().run();
+ scenario.assertNodesIn(Node.State.dirty);
+
+ // Recycles container when more than 1 hour passes
+ scenario.clock().advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1)));
+ scenario.expirer().run();
+ scenario.assertNodesIn(Node.State.dirty, "node4");
}
- private void failNode(String hostname) {
- failedExpirer.nodeRepository().fail(hostname, Agent.system, "Failing to unit test");
+ private static class FailureScenario {
+
+ private static final NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default", "docker");
+ public static final Flavor defaultFlavor = nodeFlavors.getFlavorOrThrow("default");
+ public static final Flavor dockerFlavor = nodeFlavors.getFlavorOrThrow("docker");
+
+ private final MockCurator curator = new MockCurator();
+ private final ManualClock clock = new ManualClock();
+ private final ApplicationId applicationId = ApplicationId.from(TenantName.from("foo"),
+ ApplicationName.from("bar"),
+ InstanceName.from("default"));
+
+ private final NodeRepository nodeRepository;
+ private final NodeRepositoryProvisioner provisioner;
+ private final FailedExpirer expirer;
+
+ public FailureScenario(SystemName system, Environment environment) {
+ Zone zone = new Zone(system, environment, RegionName.defaultName());
+ this.nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone,
+ new MockNameResolver().mockAnyLookup(),
+ new DockerImage("docker-image"));
+ this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), clock,
+ (x, y) -> {});
+ this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30),
+ new JobControl(nodeRepository.database()));
+ }
+
+ public ManualClock clock() {
+ return clock;
+ }
+
+ public FailedExpirer expirer() {
+ return expirer;
+ }
+
+ public Node get(String hostname) {
+ return nodeRepository.getNode(hostname)
+ .orElseThrow(() -> new IllegalArgumentException("No such node: " + hostname));
+ }
+
+ public FailureScenario withNode(NodeType type, Flavor flavor, String hostname, String parentHostname) {
+ nodeRepository.addNodes(Collections.singletonList(
+ nodeRepository.createNode(UUID.randomUUID().toString(), hostname,
+ Optional.ofNullable(parentHostname), flavor, type)
+ ));
+ return this;
+ }
+
+ public FailureScenario withNode(NodeType type, Flavor flavor, String hostname) {
+ return withNode(type, flavor, hostname,null);
+ }
+
+ public FailureScenario withNode(String hostname) {
+ return withNode(NodeType.tenant, defaultFlavor, hostname, null);
+ }
+
+ public FailureScenario failNode(int times, String... hostname) {
+ Stream.of(hostname).forEach(h -> {
+ Node node = get(h);
+ nodeRepository.write(node.with(node.status().setFailCount(times)));
+ nodeRepository.fail(h, Agent.system, "Failed by unit test");
+ });
+ return this;
+ }
+
+ public FailureScenario failWithHardwareFailure(String... hostname) {
+ Stream.of(hostname).forEach(h -> {
+ Node node = get(h);
+ nodeRepository.write(node.with(node.status().withHardwareFailureDescription(
+ Optional.of("memory_mcelog"))));
+ nodeRepository.fail(h, Agent.system, "Failed by unit test");
+ });
+ return this;
+ }
+
+ public FailureScenario setReady(String... hostname) {
+ List<Node> nodes = Stream.of(hostname)
+ .map(this::get)
+ .collect(Collectors.toList());
+ nodeRepository.setReady(nodeRepository.setDirty(nodes));
+ return this;
+ }
+
+ public FailureScenario allocate(ClusterSpec.Type clusterType, String... hostname) {
+ return allocate(clusterType, defaultFlavor, hostname);
+ }
+
+ public FailureScenario allocate(ClusterSpec.Type clusterType, Flavor flavor, String... hostname) {
+ Set<HostSpec> hosts = Stream.of(hostname)
+ .map(h -> new HostSpec(h, Optional.empty()))
+ .collect(Collectors.toSet());
+ ClusterSpec clusterSpec = ClusterSpec.request(clusterType, ClusterSpec.Id.from("test"),
+ Version.fromString("6.42"));
+ provisioner.prepare(applicationId, clusterSpec, Capacity.fromNodeCount(hostname.length, flavor.name()),
+ 1, null);
+ NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator));
+ provisioner.activate(transaction, applicationId, hosts);
+ transaction.commit();
+ return this;
+ }
+
+ public void assertNodesIn(Node.State state, String... hostnames) {
+ assertEquals(Stream.of(hostnames).collect(Collectors.toSet()),
+ nodeRepository.getNodes(state).stream()
+ .map(Node::hostname)
+ .collect(Collectors.toSet()));
+ }
}
- private void failureScenarioIn(SystemName system, Environment environment, String flavorName) {
- NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default", flavorName);
- NodeRepository nodeRepository = new NodeRepository(nodeFlavors, curator, clock, Zone.defaultZone(),
- new MockNameResolver().mockAnyLookup(),
- new DockerImage("docker-registry.domain.tld:8080/dist/vespa"));
- NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), clock, (x,y) -> {});
- failedExpirer = new FailedExpirer(nodeRepository, new Zone(system, environment, RegionName.from("us-west-1")), clock, Duration.ofDays(4), new JobControl(nodeRepository.database()));
-
- Flavor defaultFlavor = nodeFlavors.getFlavorOrThrow("default");
- List<Node> hostNodes = new ArrayList<>(3);
- hostNodes.add(nodeRepository.createNode("parent1", "parent1", Optional.empty(), defaultFlavor, NodeType.host));
- hostNodes.add(nodeRepository.createNode("parent2", "parent2", Optional.empty(), defaultFlavor, NodeType.host));
- hostNodes.add(nodeRepository.createNode("parent3", "parent3", Optional.empty(), defaultFlavor, NodeType.host));
- nodeRepository.addNodes(hostNodes);
-
- Flavor flavor = nodeFlavors.getFlavorOrThrow(flavorName);
- List<Node> nodes = new ArrayList<>(3);
- Optional<String> parentHost1 = flavorName.equals("docker") ? Optional.of("parent1") : Optional.empty();
- Optional<String> parentHost2 = flavorName.equals("docker") ? Optional.of("parent2") : Optional.empty();
- Optional<String> parentHost3 = flavorName.equals("docker") ? Optional.of("parent3") : Optional.empty();
- nodes.add(nodeRepository.createNode("node1", "node1", parentHost1, flavor, NodeType.tenant));
- nodes.add(nodeRepository.createNode("node2", "node2", parentHost2, flavor, NodeType.tenant));
- nodes.add(nodeRepository.createNode("node3", "node3", parentHost3, flavor, NodeType.tenant));
- nodeRepository.addNodes(nodes);
-
- // Set node1 to have failed 4 times before
- Node node1 = nodeRepository.getNode("node1").get();
- node1 = node1.with(node1.status().setFailCount(4));
- nodeRepository.write(node1);
-
- // Set node2 to have a detected hardware failure
- setHWFailureForNode("node2");
-
- // Set node3 to have failed 8 times before and have a HW failure
- Node node3 = nodeRepository.getNode("node3").get();
- node3 = node1.with(node3.status().setFailCount(8));
- nodeRepository.write(node3);
- setHWFailureForNode("node3");
-
- // Allocate the nodes
- List<Node> provisioned = nodeRepository.getNodes(NodeType.tenant, Node.State.provisioned);
- nodeRepository.setReady(nodeRepository.setDirty(provisioned));
- nodeRepository.addNodes(Arrays.asList(
- nodeRepository.createNode("node4", "node4", parentHost1, flavor, NodeType.tenant),
- nodeRepository.createNode("node5", "node5", parentHost1, flavor, NodeType.tenant)));
-
- ApplicationId applicationId = ApplicationId.from(TenantName.from("foo"), ApplicationName.from("bar"), InstanceName.from("fuz"));
- ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"));
- provisioner.prepare(applicationId, cluster, Capacity.fromNodeCount(3, flavorName), 1, null);
- NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator));
- provisioner.activate(transaction, applicationId, ProvisioningTester.toHostSpecs(nodes));
- transaction.commit();
- assertEquals(3, nodeRepository.getNodes(NodeType.tenant, Node.State.active).size());
-
- // Fail the nodes
- nodes.forEach(node -> failNode(node.hostname()));
- assertEquals(3, nodeRepository.getNodes(NodeType.tenant, Node.State.failed).size());
- }
}
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 704ded54479..048856bc698 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
@@ -40,7 +40,7 @@ public class InactiveAndFailedExpirerTest {
InstanceName.from("fuz"));
@Test
- public void inactive_and_failed_times_out() throws InterruptedException {
+ public void inactive_and_failed_times_out() {
ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east")));
List<Node> nodes = tester.makeReadyNodes(2, "default");
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
index 8195321be03..12e2eb3f323 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
@@ -52,7 +52,7 @@ public class RetiredExpirerTest {
private Curator curator = new MockCurator();
@Test
- public void ensure_retired_nodes_time_out() throws InterruptedException {
+ public void ensure_retired_nodes_time_out() {
ManualClock clock = new ManualClock();
Zone zone = new Zone(Environment.prod, RegionName.from("us-east"));
NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default");
@@ -92,7 +92,7 @@ public class RetiredExpirerTest {
}
@Test
- public void ensure_retired_groups_time_out() throws InterruptedException {
+ public void ensure_retired_groups_time_out() {
ManualClock clock = new ManualClock();
Zone zone = new Zone(Environment.prod, RegionName.from("us-east"));
NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default");
@@ -170,7 +170,6 @@ public class RetiredExpirerTest {
new RetiredEarlyExpirer(
nodeRepository,
- zone,
Duration.ofDays(30),
new JobControl(nodeRepository.database()),
deployer,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
index 37b2f54da4d..c23a7f9990a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
@@ -13,7 +13,6 @@ import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.provision.testutils.ContainerConfig;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
@@ -40,9 +39,20 @@ public class RestApiTest {
private final static String responsesPath = "src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/";
+ private JDisc container;
+
+ @Before
+ public void startContainer() {
+ container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(0), Networking.disable);
+ }
+
+ @After
+ public void stopContainer() {
+ container.close();
+ }
+
/** This test gives examples of all the requests that can be made to nodes/v2 */
@Test
- @Ignore /** TODO re-enable this and verify correctness */
public void test_requests() throws Exception {
// GET
assertFile(new Request("http://localhost:8080/nodes/v2/"), "root.json");
@@ -54,28 +64,28 @@ public class RestApiTest {
assertFile(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"), "node2.json");
// GET with filters
- assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&hostname=host2.yahoo.com%20host1.yahoo.com"), "application2-nodes.json");
- assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterType=content"), "active-nodes.json");
+ assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&hostname=host6.yahoo.com%20host2.yahoo.com"), "application2-nodes.json");
+ assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterType=content"), "content-nodes.json");
assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterId=id2"), "application2-nodes.json");
assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&application=tenant2.application2.instance2"), "application2-nodes.json");
- assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&parentHost=parent1.yahoo.com,parent.host.yahoo.com"), "parent-nodes.json");
+ assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&parentHost=dockerhost1.yahoo.com"), "child-nodes.json");
// POST restart command
assertRestart(1, new Request("http://localhost:8080/nodes/v2/command/restart?hostname=host2.yahoo.com",
new byte[0], Request.Method.POST));
assertRestart(2, new Request("http://localhost:8080/nodes/v2/command/restart?application=tenant2.application2.instance2",
new byte[0], Request.Method.POST));
- assertRestart(4, new Request("http://localhost:8080/nodes/v2/command/restart",
+ assertRestart(9, new Request("http://localhost:8080/nodes/v2/command/restart",
new byte[0], Request.Method.POST));
assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"),
"\"restartGeneration\":3");
// POST reboot command
- assertReboot(5, new Request("http://localhost:8080/nodes/v2/command/reboot?state=failed%20active",
+ assertReboot(10, new Request("http://localhost:8080/nodes/v2/command/reboot?state=failed%20active",
new byte[0], Request.Method.POST));
assertReboot(2, new Request("http://localhost:8080/nodes/v2/command/reboot?application=tenant2.application2.instance2",
new byte[0], Request.Method.POST));
- assertReboot(10, new Request("http://localhost:8080/nodes/v2/command/reboot",
+ assertReboot(15, new Request("http://localhost:8080/nodes/v2/command/reboot",
new byte[0], Request.Method.POST));
assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"),
"\"rebootGeneration\":4");
@@ -106,9 +116,9 @@ public class RestApiTest {
assertFile(new Request("http://localhost:8080/nodes/v2/node/parent2.yahoo.com"), "parent2.json");
// DELETE a provisioned node
- assertResponse(new Request("http://localhost:8080/nodes/v2/node/host11.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/node/host9.yahoo.com",
new byte[0], Request.Method.DELETE),
- "{\"message\":\"Removed host11.yahoo.com\"}");
+ "{\"message\":\"Removed host9.yahoo.com\"}");
// PUT nodes ready
assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/host8.yahoo.com",
@@ -125,15 +135,15 @@ public class RestApiTest {
"{\"message\":\"Moved host8.yahoo.com to ready\"}");
// PUT a node in failed ...
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host3.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host2.yahoo.com",
new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host3.yahoo.com to failed\"}");
- assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host3.yahoo.com"),
+ "{\"message\":\"Moved host2.yahoo.com to failed\"}");
+ assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"),
"\"state\":\"failed\"");
// ... and put it back in active (after fixing). This is useful to restore data when multiple nodes fail.
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/active/host3.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/active/host2.yahoo.com",
new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host3.yahoo.com to active\"}");
+ "{\"message\":\"Moved host2.yahoo.com to active\"}");
// PUT a node in parked ...
assertResponse(new Request("http://localhost:8080/nodes/v2/state/parked/host8.yahoo.com",
@@ -147,30 +157,29 @@ public class RestApiTest {
"{\"message\":\"Removed host8.yahoo.com\"}");
// or, PUT a node in failed ...
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host6.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/test-container-1",
new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host6.yahoo.com to failed\"}");
- assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"),
+ "{\"message\":\"Moved test-container-1 to failed\"}");
+ assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/test-container-1"),
"\"state\":\"failed\"");
// ... and deallocate it such that it moves to dirty and is recycled
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/host6.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/test-container-1",
new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host6.yahoo.com to dirty\"}");
+ "{\"message\":\"Moved test-container-1 to dirty\"}");
// ... and set it back to ready as if this was from the node-admin with the temporary state rest api
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/availablefornewallocations/host6.yahoo.com",
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/availablefornewallocations/test-container-1",
new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host6.yahoo.com to ready\"}");
+ "{\"message\":\"Marked following nodes as available for new allocation: test-container-1\"}");
// Put a host in failed and make sure it's children are also failed
- assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/parent1.yahoo.com", new byte[0], Request.Method.PUT),
- "{\"message\":\"Moved host10.yahoo.com, host5.yahoo.com, parent1.yahoo.com to failed\"}");
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/dockerhost1.yahoo.com", new byte[0], Request.Method.PUT),
+ "{\"message\":\"Moved dockerhost1.yahoo.com, host4.yahoo.com to failed\"}");
assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed"), "{\"nodes\":[" +
- "{\"url\":\"http://localhost:8080/nodes/v2/node/parent1.yahoo.com\"}," +
+ "{\"url\":\"http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com\"}," +
"{\"url\":\"http://localhost:8080/nodes/v2/node/host5.yahoo.com\"}," +
- "{\"url\":\"http://localhost:8080/nodes/v2/node/host10.yahoo.com\"}]}");
-
+ "{\"url\":\"http://localhost:8080/nodes/v2/node/host4.yahoo.com\"}]}");
// Update (PATCH) a node (multiple fields can also be sent in one request body)
assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com",
@@ -299,6 +308,15 @@ public class RestApiTest {
}
@Test
+ public void setting_node_to_ready_will_reset_certain_fields() throws Exception {
+ final String hostname = "host55.yahoo.com";
+ assertResponse(new Request("http://localhost:8080/nodes/v2/state/ready/" + hostname,
+ new byte[0], Request.Method.PUT),
+ "{\"message\":\"Moved " + hostname + " to ready\"}");
+ assertFile(new Request("http://localhost:8080/nodes/v2/node/" + hostname), "node55-after-changes.json");
+ }
+
+ @Test
public void acl_request_by_tenant_node() throws Exception {
String hostname = "foo.yahoo.com";
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
@@ -437,6 +455,11 @@ public class RestApiTest {
Request.Method.PATCH),
"{\"message\":\"Updated host4.yahoo.com\"}");
+ assertResponse(new Request("http://localhost:8080/nodes/v2/node/doesnotexist.yahoo.com",
+ Utf8.toBytes("{\"currentRestartGeneration\": 1}"),
+ Request.Method.PATCH),
+ 404, "{\"error-code\":\"NOT_FOUND\",\"message\":\"No node found with hostname doesnotexist.yahoo.com\"}");
+
assertResponse(new Request("http://localhost:8080/nodes/v2/node/host5.yahoo.com",
Utf8.toBytes("{\"currentRestartGeneration\": 1}"),
Request.Method.PATCH),
@@ -514,13 +537,6 @@ public class RestApiTest {
}
}
- private JDisc container;
- @Before
- public void startContainer() {
- container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(0), Networking.disable); }
- @After
- public void stopContainer() { container.close(); }
-
private String asDockerNodeJson(String hostname, String parentHostname, int additionalIpCount, String... ipAddress) {
return "{\"hostname\":\"" + hostname + "\", \"parentHostname\":\"" + parentHostname + "\"," +
createIpAddresses(ipAddress) +
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json
index d1df5b83f24..c67ba904f9a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json
@@ -1,8 +1,13 @@
{
"nodes": [
+ @include(docker-node4.json),
+ @include(docker-node5.json),
+ @include(docker-node2.json),
+ @include(docker-node1.json),
+ @include(docker-node3.json),
@include(node6.json),
- @include(node3.json),
@include(node2.json),
- @include(node1.json)
+ @include(docker-container1.json),
+ @include(node4.json)
]
-} \ No newline at end of file
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json
index 1d4d97315cd..4581ecba73d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json
@@ -1,6 +1,6 @@
{
"nodes": [
- @include(node2.json),
- @include(node1.json)
+ @include(node6.json),
+ @include(node2.json)
]
-} \ No newline at end of file
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json
new file mode 100644
index 00000000000..dae92aae091
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json
@@ -0,0 +1,5 @@
+{
+ "nodes": [
+ @include(node4.json)
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json
new file mode 100644
index 00000000000..47a2c012b17
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json
@@ -0,0 +1,8 @@
+{
+ "nodes": [
+ @include(node6.json),
+ @include(node2.json),
+ @include(docker-container1.json),
+ @include(node4.json)
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json
new file mode 100644
index 00000000000..7823ed0431d
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json
@@ -0,0 +1,56 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/test-container-1",
+ "id": "test-container-1",
+ "state": "active",
+ "type": "tenant",
+ "hostname": "test-container-1",
+ "parentHostname": "dockerhost3.yahoo.com",
+ "openStackId": "fake-test-container-1",
+ "flavor": "docker",
+ "canonicalFlavor": "docker",
+ "minDiskAvailableGb": 100.0,
+ "minMainMemoryAvailableGb": 0.5,
+ "description": "Flavor-name-is-docker",
+ "minCpuCores": 0.2,
+ "fastDisk": true,
+ "environment": "DOCKER_CONTAINER",
+ "owner": {
+ "tenant": "tenant3",
+ "application": "application3",
+ "instance": "instance3"
+ },
+ "membership": {
+ "clustertype": "content",
+ "clusterid": "id3",
+ "group": "0",
+ "index": 1,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 0,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::2"
+ ],
+ "additionalIpAddresses": []
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json
new file mode 100644
index 00000000000..a13dfae927f
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json
@@ -0,0 +1,70 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com",
+ "id": "dockerhost1.yahoo.com",
+ "state": "active",
+ "type": "host",
+ "hostname": "dockerhost1.yahoo.com",
+ "openStackId": "dockerhost1",
+ "flavor": "large",
+ "canonicalFlavor": "large",
+ "minDiskAvailableGb": 1600.0,
+ "minMainMemoryAvailableGb": 32.0,
+ "description": "Flavor-name-is-large",
+ "minCpuCores": 4.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "owner": {
+ "tenant": "zoneapp",
+ "application": "zoneapp",
+ "instance": "zoneapp"
+ },
+ "membership": {
+ "clustertype": "container",
+ "clusterid": "node-admin",
+ "group": "0",
+ "index": 0,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": [
+ "::2",
+ "::3",
+ "::4"
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json
new file mode 100644
index 00000000000..f7a1d6ab9a9
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json
@@ -0,0 +1,70 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost2.yahoo.com",
+ "id": "dockerhost2.yahoo.com",
+ "state": "active",
+ "type": "host",
+ "hostname": "dockerhost2.yahoo.com",
+ "openStackId": "dockerhost2",
+ "flavor": "large",
+ "canonicalFlavor": "large",
+ "minDiskAvailableGb": 1600.0,
+ "minMainMemoryAvailableGb": 32.0,
+ "description": "Flavor-name-is-large",
+ "minCpuCores": 4.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "owner": {
+ "tenant": "zoneapp",
+ "application": "zoneapp",
+ "instance": "zoneapp"
+ },
+ "membership": {
+ "clustertype": "container",
+ "clusterid": "node-admin",
+ "group": "0",
+ "index": 1,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": [
+ "::2",
+ "::3",
+ "::4"
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json
new file mode 100644
index 00000000000..f877d33672f
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json
@@ -0,0 +1,70 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost3.yahoo.com",
+ "id": "dockerhost3.yahoo.com",
+ "state": "active",
+ "type": "host",
+ "hostname": "dockerhost3.yahoo.com",
+ "openStackId": "dockerhost3",
+ "flavor": "large",
+ "canonicalFlavor": "large",
+ "minDiskAvailableGb": 1600.0,
+ "minMainMemoryAvailableGb": 32.0,
+ "description": "Flavor-name-is-large",
+ "minCpuCores": 4.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "owner": {
+ "tenant": "zoneapp",
+ "application": "zoneapp",
+ "instance": "zoneapp"
+ },
+ "membership": {
+ "clustertype": "container",
+ "clusterid": "node-admin",
+ "group": "0",
+ "index": 2,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": [
+ "::2",
+ "::3",
+ "::4"
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json
new file mode 100644
index 00000000000..913cf9852aa
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json
@@ -0,0 +1,70 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost4.yahoo.com",
+ "id": "dockerhost4.yahoo.com",
+ "state": "active",
+ "type": "host",
+ "hostname": "dockerhost4.yahoo.com",
+ "openStackId": "dockerhost4",
+ "flavor": "large",
+ "canonicalFlavor": "large",
+ "minDiskAvailableGb": 1600.0,
+ "minMainMemoryAvailableGb": 32.0,
+ "description": "Flavor-name-is-large",
+ "minCpuCores": 4.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "owner": {
+ "tenant": "zoneapp",
+ "application": "zoneapp",
+ "instance": "zoneapp"
+ },
+ "membership": {
+ "clustertype": "container",
+ "clusterid": "node-admin",
+ "group": "0",
+ "index": 3,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": [
+ "::2",
+ "::3",
+ "::4"
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json
new file mode 100644
index 00000000000..685b0a52b15
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json
@@ -0,0 +1,70 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost5.yahoo.com",
+ "id": "dockerhost5.yahoo.com",
+ "state": "active",
+ "type": "host",
+ "hostname": "dockerhost5.yahoo.com",
+ "openStackId": "dockerhost5",
+ "flavor": "large",
+ "canonicalFlavor": "large",
+ "minDiskAvailableGb": 1600.0,
+ "minMainMemoryAvailableGb": 32.0,
+ "description": "Flavor-name-is-large",
+ "minCpuCores": 4.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "owner": {
+ "tenant": "zoneapp",
+ "application": "zoneapp",
+ "instance": "zoneapp"
+ },
+ "membership": {
+ "clustertype": "container",
+ "clusterid": "node-admin",
+ "group": "0",
+ "index": 4,
+ "retired": false
+ },
+ "restartGeneration": 0,
+ "currentRestartGeneration": 0,
+ "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
+ "wantedVespaVersion": "6.42.0",
+ "allowedToBeDown": false,
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "reserved",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": [
+ "::2",
+ "::3",
+ "::4"
+ ]
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
index fea4fb8d4d2..c09829e7f85 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
@@ -28,6 +28,9 @@
"name":"OperatorChangeApplicationMaintainer"
},
{
+ "name":"ProvisionedExpirer"
+ },
+ {
"name":"RetiredEarlyExpirer"
},
{
@@ -43,4 +46,4 @@
"inactive":[
"NodeFailer"
]
-} \ No newline at end of file
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json
index f311c240b1d..cb250e2033b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json
@@ -1,7 +1,7 @@
{
"url": "http://localhost:8080/nodes/v2/node/host4.yahoo.com",
"id": "host4.yahoo.com",
- "state": "reserved",
+ "state": "failed",
"type": "tenant",
"hostname": "host4.yahoo.com",
"parentHostname": "parent.yahoo.com",
@@ -12,36 +12,36 @@
"minMainMemoryAvailableGb": 12.0,
"description": "Flavor-name-is-medium-disk",
"minCpuCores": 6.0,
- "fastDisk":true,
+ "fastDisk": true,
"environment": "BARE_METAL",
"owner": {
- "tenant": "tenant1",
- "application": "application1",
- "instance": "instance1"
+ "tenant": "tenant3",
+ "application": "application3",
+ "instance": "instance3"
},
"membership": {
- "clustertype": "container",
- "clusterid": "id1",
+ "clustertype": "content",
+ "clusterid": "id3",
"group": "0",
- "index": 1,
+ "index": 0,
"retired": false
},
- "restartGeneration": 0,
+ "restartGeneration": 1,
"currentRestartGeneration": 1,
"wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0",
"wantedVespaVersion": "6.42.0",
"allowedToBeDown": false,
- "rebootGeneration": 2,
+ "rebootGeneration": 3,
"currentRebootGeneration": 1,
"vespaVersion": "6.43.0",
"currentDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.43.0",
"hostedVersion": "6.43.0",
"convergedStateVersion": "6.43.0",
- "failCount": 0,
+ "failCount": 1,
"hardwareFailure": true,
"hardwareFailureDescription": "memory_mcelog",
- "wantToRetire" : true,
- "wantToDeprovision" : true,
+ "wantToRetire": true,
+ "wantToDeprovision": true,
"history": [
{
"event": "provisioned",
@@ -51,7 +51,7 @@
{
"event": "readied",
"at": 123,
- "agent": "system"
+ "agent": "system"
},
{
"event": "reserved",
@@ -59,11 +59,24 @@
"agent": "application"
},
{
+ "event": "activated",
+ "at": 123,
+ "agent": "application"
+ },
+ {
+ "event": "failed",
+ "at": 123,
+ "agent": "operator"
+ },
+ {
"event": "rebooted",
"at": 123,
"agent": "system"
}
],
- "ipAddresses":["127.0.0.1", "::1"],
- "additionalIpAddresses":[]
+ "ipAddresses": [
+ "127.0.0.1",
+ "::1"
+ ],
+ "additionalIpAddresses": []
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json
new file mode 100644
index 00000000000..4560e5e1be4
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json
@@ -0,0 +1,39 @@
+{
+ "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com",
+ "id": "host55.yahoo.com",
+ "state": "ready",
+ "type": "tenant",
+ "hostname": "host55.yahoo.com",
+ "openStackId": "node55",
+ "flavor": "default",
+ "canonicalFlavor": "default",
+ "minDiskAvailableGb": 400.0,
+ "minMainMemoryAvailableGb": 16.0,
+ "description": "Flavor-name-is-default",
+ "minCpuCores": 2.0,
+ "fastDisk": true,
+ "environment": "BARE_METAL",
+ "rebootGeneration": 1,
+ "currentRebootGeneration": 0,
+ "failCount": 0,
+ "hardwareFailure": false,
+ "wantToRetire": false,
+ "wantToDeprovision": false,
+ "history": [
+ {
+ "event": "provisioned",
+ "at": 123,
+ "agent": "system"
+ },
+ {
+ "event": "readied",
+ "at": 123,
+ "agent": "system"
+ }
+ ],
+ "ipAddresses": [
+ "::1",
+ "127.0.0.1"
+ ],
+ "additionalIpAddresses": []
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json
index 7d07037d076..4f8b988ccbf 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json
@@ -17,8 +17,8 @@
"currentRebootGeneration": 0,
"failCount": 0,
"hardwareFailure": false,
- "wantToRetire": false,
- "wantToDeprovision": false,
+ "wantToRetire": true,
+ "wantToDeprovision": true,
"history": [
{
"event": "provisioned",
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json
index a9feed81674..475b914989b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json
@@ -1,14 +1,19 @@
{
"nodes": [
@include(node7.json),
- @include(parent1.json),
+ @include(node3.json),
@include(node10.json),
- @include(node4.json),
+ @include(node1.json),
+ @include(docker-node4.json),
@include(node6.json),
- @include(node3.json),
+ @include(docker-node5.json),
+ @include(docker-node2.json),
@include(node2.json),
- @include(node1.json),
+ @include(docker-node1.json),
+ @include(docker-node3.json),
+ @include(docker-container1.json),
+ @include(node4.json),
@include(node55.json),
@include(node5.json)
]
-} \ No newline at end of file
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json
index 67b65259f8a..3bfaa95d5ee 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json
@@ -4,31 +4,46 @@
"url": "http://localhost:8080/nodes/v2/node/host7.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/parent1.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host3.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host10.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host10.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host4.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host1.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host6.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost4.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host3.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host2.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost5.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host1.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost2.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host55.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host2.yahoo.com"
},
{
- "url":"http://localhost:8080/nodes/v2/node/host5.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"
+ },
+ {
+ "url": "http://localhost:8080/nodes/v2/node/dockerhost3.yahoo.com"
+ },
+ {
+ "url": "http://localhost:8080/nodes/v2/node/test-container-1"
+ },
+ {
+ "url": "http://localhost:8080/nodes/v2/node/host4.yahoo.com"
+ },
+ {
+ "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com"
+ },
+ {
+ "url": "http://localhost:8080/nodes/v2/node/host5.yahoo.com"
}
]
-} \ No newline at end of file
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json
deleted file mode 100644
index 81ca0465c4b..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "nodes": [
- @include(node10.json),
- @include(node5.json)
- ]
-} \ No newline at end of file
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json
index 4ee1d5ed9b9..183f81ee3e1 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json
@@ -9,23 +9,28 @@
"ready": {
"url": "http://localhost:8080/nodes/v2/state/ready",
"nodes": [
- @include(parent1.json)
+ @include(node3.json)
]
},
"reserved": {
"url": "http://localhost:8080/nodes/v2/state/reserved",
"nodes": [
@include(node10.json),
- @include(node4.json)
+ @include(node1.json)
]
},
"active": {
"url": "http://localhost:8080/nodes/v2/state/active",
"nodes": [
+ @include(docker-node4.json),
+ @include(docker-node5.json),
+ @include(docker-node2.json),
+ @include(docker-node1.json),
+ @include(docker-node3.json),
@include(node6.json),
- @include(node3.json),
@include(node2.json),
- @include(node1.json)
+ @include(docker-container1.json),
+ @include(node4.json)
]
},
"inactive": {
diff --git a/orchestrator-restapi/pom.xml b/orchestrator-restapi/pom.xml
index db823f3fb15..310512812e2 100644
--- a/orchestrator-restapi/pom.xml
+++ b/orchestrator-restapi/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>orchestrator-restapi</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/orchestrator/pom.xml b/orchestrator/pom.xml
index c50f9ddcb29..ae22b6718f9 100644
--- a/orchestrator/pom.xml
+++ b/orchestrator/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>orchestrator</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/parent/pom.xml b/parent/pom.xml
new file mode 100644
index 00000000000..0a19d66d674
--- /dev/null
+++ b/parent/pom.xml
@@ -0,0 +1,718 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>parent</artifactId>
+ <packaging>pom</packaging>
+ <version>6-SNAPSHOT</version>
+ <name>parent</name>
+ <description>Parent artifact for all Vespa maven projects.</description>
+ <url>https://github.com/vespa-engine</url>
+
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>container-dependency-versions</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../container-dependency-versions/pom.xml</relativePath>
+ </parent>
+
+ <licenses>
+ <license>
+ <name>The Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ </license>
+ </licenses>
+ <developers>
+ <developer>
+ <name>Vespa</name>
+ <url>https://github.com/vespa-engine</url>
+ </developer>
+ </developers>
+ <distributionManagement>
+ <repository>
+ <id>bintray-vespa-repo</id>
+ <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url>
+ </repository>
+ </distributionManagement>
+ <scm>
+ <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection>
+ <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection>
+ <url>git@github.com:vespa-engine/vespa.git</url>
+ </scm>
+
+ <repositories>
+ <!-- Required for Athenz libraries -->
+ <repository>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>bintray-yahoo-maven</id>
+ <name>bintray</name>
+ <url>https://yahoo.bintray.com/maven</url>
+ </repository>
+ </repositories>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <extensions>
+ <extension>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-ssh-external</artifactId>
+ <version>2.7</version>
+ </extension>
+ <extension>
+ <groupId>org.apache.maven.archetype</groupId>
+ <artifactId>archetype-packaging</artifactId>
+ <version>2.0</version>
+ </extension>
+ </extensions>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr3-maven-plugin</artifactId>
+ <version>${antlr.version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.7</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.4.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.6.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <showWarnings>true</showWarnings>
+ <optimize>true</optimize>
+ <showDeprecation>false</showDeprecation>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-serial</arg>
+ <arg>-Xlint:-try</arg>
+ <arg>-Xlint:-processing</arg>
+ <arg>-Xlint:-varargs</arg>
+ <arg>-Werror</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.10</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.5</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>2.5.2</version>
+ <configuration>
+ <updateReleaseInfo>true</updateReleaseInfo>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.0.2</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam>
+ </configuration>
+ <version>2.10.4</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ <version>3.5</version>
+ <configuration>
+ <!-- see http://jira.codehaus.org/browse/MNG-5346 -->
+ <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+ </configuration>
+ <executions>
+ <execution>
+ <id>mojo-descriptor</id>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <escapeString>\</escapeString>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.3</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ <configuration>
+ <includePom>true</includePom>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.version}</version>
+ <configuration>
+ <redirectTestOutputToFile>${test.hide}</redirectTestOutputToFile>
+ <systemPropertyVariables>
+ <java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${surefire.version}</version>
+ <configuration>
+ <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
+ <showSuccess>false</showSuccess>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.9.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.6</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>properties-maven-plugin</artifactId>
+ <version>1.0.0</version>
+ </plugin>
+ <plugin>
+ <groupId>net.alchim31.maven</groupId>
+ <artifactId>scala-maven-plugin</artifactId>
+ <version>3.2.2</version>
+ <configuration>
+ <args>
+ <arg>-unchecked</arg>
+ <arg>-deprecation</arg>
+ <arg>-feature</arg>
+ <arg>-Xfatal-warnings</arg>
+ </args>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <configGenVersion>${project.version}</configGenVersion>
+ <useCommonAssemblyIds>true</useCommonAssemblyIds>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ <profiles>
+ <profile>
+ <id>attach-sources</id>
+ <activation>
+ <property>
+ <name>!skipSources</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>generate-javadoc</id>
+ <activation>
+ <property>
+ <name>!skipJavadoc</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-javadoc</id>
+ <phase>package</phase>
+ <goals>
+ <goal>javadoc</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam>
+ <failOnError>${javadoc.failOnError}</failOnError>
+ <quiet>true</quiet>
+ <show>private</show>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>coverage</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <includePluginDependencies>true</includePluginDependencies>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src/main/scala</source>
+ </sources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>add-test-source</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src/test/scala</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>sign-artifacts</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-ssh-external</artifactId>
+ <version>2.7</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.cverges.expect4j</groupId>
+ <artifactId>expect4j</artifactId>
+ <version>1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <version>1.11</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-exec</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>io.airlift</groupId>
+ <artifactId>airline</artifactId>
+ <version>0.7</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>5.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <version>18.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>2.4.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.jmockit</groupId>
+ <artifactId>jmockit</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.goldmansachs</groupId>
+ <artifactId>gs-collections</artifactId>
+ <version>6.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.infradna.tool</groupId>
+ <artifactId>bridge-method-annotation</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ <version>1.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ <version>3.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-configuration</groupId>
+ <artifactId>commons-configuration</artifactId>
+ <version>1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>${commons-lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-net</groupId>
+ <artifactId>commons-net</artifactId>
+ <version>2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-pool</groupId>
+ <artifactId>commons-pool</artifactId>
+ <version>1.5.6</version>
+ </dependency>
+ <!-- Explicitly included to get Zookeeper version 3.4.10,
+ can be excluded if you want the Zookeeper version
+ used by curator by default
+ -->
+ <dependency>
+ <groupId>org.apache.zookeeper</groupId>
+ <artifactId>zookeeper</artifactId>
+ <version>3.4.10</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.curator</groupId>
+ <artifactId>curator-recipes</artifactId>
+ <version>${curator.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.curator</groupId>
+ <artifactId>curator-test</artifactId>
+ <version>${curator.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ </dependency>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr-runtime</artifactId>
+ <version>${antlr.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-runtime</artifactId>
+ <version>${antlr4.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.aries.spifly</groupId>
+ <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
+ <version>${aries.spifly.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>fluent-hc</artifactId>
+ <version>4.3.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>4.3.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>3.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>3.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-model</artifactId>
+ <version>3.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>3.5</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>3.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-project</artifactId>
+ <version>2.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-junit4</artifactId>
+ <version>${surefire.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-providers</artifactId>
+ <version>${surefire.version}</version>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jettison</groupId>
+ <artifactId>jettison</artifactId>
+ <version>1.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.cthul</groupId>
+ <artifactId>cthul-matchers</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.co.datumedge</groupId>
+ <artifactId>hamcrest-json</artifactId>
+ <version>0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hdrhistogram</groupId>
+ <artifactId>HdrHistogram</artifactId>
+ <version>2.1.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.9.5</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ <version>${scala.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang.modules</groupId>
+ <artifactId>scala-parser-combinators_${scala.major-version}</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang.modules</groupId>
+ <artifactId>scala-xml_${scala.major-version}</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scalatest</groupId>
+ <artifactId>scalatest_${scala.major-version}</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <version>4.0.6.RELEASE</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.10</version>
+ </dependency>
+ <dependency>
+ <groupId>org.twdata.maven</groupId>
+ <artifactId>mojo-executor</artifactId>
+ <version>2.3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>net.spy</groupId>
+ <artifactId>spymemcached</artifactId>
+ <version>2.10.1</version>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ <version>2.11.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ibm.icu</groupId>
+ <artifactId>icu4j</artifactId>
+ <version>57.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.athenz</groupId>
+ <artifactId>athenz-zms-java-client</artifactId>
+ <version>${athenz.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.athenz</groupId>
+ <artifactId>athenz-zts-java-client</artifactId>
+ <version>${athenz.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <properties>
+ <antlr.version>3.5.2</antlr.version>
+ <antlr4.version>4.5</antlr4.version>
+ <aries.spifly.version>1.0.8</aries.spifly.version>
+ <aries.util.version>1.0.0</aries.util.version>
+ <asm-debug-all.version>5.0.3</asm-debug-all.version>
+ <!-- Athenz dependencies. Make sure these dependencies matches those in Vespa's internal repositories -->
+ <athenz.version>1.7.28</athenz.version>
+ <bouncycastle.version>1.58</bouncycastle.version>
+ <commons-lang.version>2.6</commons-lang.version>
+ <!-- WARNING: If you change curator version, you also need to update
+ zkfacade/src/main/java/org/apache/curator/**/package-info.java
+ using something like
+ find zkfacade/src/main/java/org/apache/curator -name package-info.java | \
+ xargs perl -pi -e 's/major = [0-9]+, minor = [0-9]+, micro = [0-9]+/major = 2, minor = 9, micro = 1/g'
+ -->
+ <curator.version>2.9.1</curator.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <test.hide>true</test.hide>
+ <doclint>all</doclint>
+ <scala.major-version>2.11</scala.major-version>
+ <scala.version>${scala.major-version}.4</scala.version>
+ <surefire.version>2.19.1</surefire.version> <!-- NOTE bjorncs 15.06.2017: Version 2.20 has OoM issues -->
+ </properties>
+
+</project>
diff --git a/pom.xml b/pom.xml
index 2b7c1abc0d4..eb1f954ce13 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,11 +3,10 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yahoo.vespa</groupId>
- <artifactId>parent</artifactId>
+ <artifactId>vespa</artifactId>
<packaging>pom</packaging>
<version>6-SNAPSHOT</version>
- <name>parent</name>
- <description>Parent artifact for all Vespa maven projects.</description>
+ <description>Aggregator pom for vespa.</description>
<url>https://github.com/vespa-engine</url>
<licenses>
@@ -24,941 +23,6 @@
</developer>
</developers>
- <distributionManagement>
- <repository>
- <id>bintray-vespa-repo</id>
- <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url>
- </repository>
- </distributionManagement>
-
- <repositories>
- <!-- Required for Athenz libraries -->
- <repository>
- <snapshots>
- <enabled>false</enabled>
- </snapshots>
- <id>bintray-yahoo-maven</id>
- <name>bintray</name>
- <url>https://yahoo.bintray.com/maven</url>
- </repository>
- </repositories>
-
- <scm>
- <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection>
- <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection>
- <url>git@github.com:vespa-engine/vespa.git</url>
- </scm>
-
- <build>
- <finalName>${project.artifactId}</finalName>
- <extensions>
- <extension>
- <groupId>org.apache.maven.wagon</groupId>
- <artifactId>wagon-ssh-external</artifactId>
- <version>2.7</version>
- </extension>
- <extension>
- <groupId>org.apache.maven.archetype</groupId>
- <artifactId>archetype-packaging</artifactId>
- <version>2.0</version>
- </extension>
- </extensions>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.antlr</groupId>
- <artifactId>antlr3-maven-plugin</artifactId>
- <version>${antlr.version}</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-antrun-plugin</artifactId>
- <version>1.7</version>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <version>2.4.0</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <version>2.4</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.6.1</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- <showWarnings>true</showWarnings>
- <optimize>true</optimize>
- <showDeprecation>false</showDeprecation>
- <compilerArgs>
- <arg>-Xlint:all</arg>
- <arg>-Xlint:-serial</arg>
- <arg>-Xlint:-try</arg>
- <arg>-Xlint:-processing</arg>
- <arg>-Xlint:-varargs</arg>
- <arg>-Werror</arg>
- </compilerArgs>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-dependency-plugin</artifactId>
- <version>2.10</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-deploy-plugin</artifactId>
- <version>2.5</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-install-plugin</artifactId>
- <version>2.5.2</version>
- <configuration>
- <updateReleaseInfo>true</updateReleaseInfo>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <version>3.0.2</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <configuration>
- <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam>
- </configuration>
- <version>2.10.4</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-plugin-plugin</artifactId>
- <version>3.5</version>
- <configuration>
- <!-- see http://jira.codehaus.org/browse/MNG-5346 -->
- <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
- </configuration>
- <executions>
- <execution>
- <id>mojo-descriptor</id>
- <goals>
- <goal>descriptor</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-resources-plugin</artifactId>
- <version>2.7</version>
- <configuration>
- <escapeString>\</escapeString>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-site-plugin</artifactId>
- <version>3.3</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.1.2</version>
- <configuration>
- <includePom>true</includePom>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>${surefire.version}</version>
- <configuration>
- <redirectTestOutputToFile>${test.hide}</redirectTestOutputToFile>
- <systemPropertyVariables>
- <java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-report-plugin</artifactId>
- <version>${surefire.version}</version>
- <configuration>
- <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
- <showSuccess>false</showSuccess>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <version>1.9.1</version>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>exec-maven-plugin</artifactId>
- <version>1.6.0</version>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>javacc-maven-plugin</artifactId>
- <version>2.6</version>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>properties-maven-plugin</artifactId>
- <version>1.0.0</version>
- </plugin>
- <plugin>
- <groupId>net.alchim31.maven</groupId>
- <artifactId>scala-maven-plugin</artifactId>
- <version>3.2.2</version>
- <configuration>
- <args>
- <arg>-unchecked</arg>
- <arg>-deprecation</arg>
- <arg>-feature</arg>
- <arg>-Xfatal-warnings</arg>
- </args>
- </configuration>
- </plugin>
- <plugin>
- <groupId>com.yahoo.vespa</groupId>
- <artifactId>bundle-plugin</artifactId>
- <version>${project.version}</version>
- <configuration>
- <configGenVersion>${project.version}</configGenVersion>
- <useCommonAssemblyIds>true</useCommonAssemblyIds>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- <profiles>
- <profile>
- <id>attach-sources</id>
- <activation>
- <property>
- <name>!skipSources</name>
- </property>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <executions>
- <execution>
- <id>attach-sources</id>
- <goals>
- <goal>jar-no-fork</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
- <id>generate-javadoc</id>
- <activation>
- <property>
- <name>!skipJavadoc</name>
- </property>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <executions>
- <execution>
- <id>generate-javadoc</id>
- <phase>package</phase>
- <goals>
- <goal>javadoc</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam>
- <failOnError>${javadoc.failOnError}</failOnError>
- <quiet>true</quiet>
- <show>private</show>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
- <id>coverage</id>
- <build>
- <plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>exec-maven-plugin</artifactId>
- <configuration>
- <includePluginDependencies>true</includePluginDependencies>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <executions>
- <execution>
- <phase>generate-sources</phase>
- <goals>
- <goal>add-source</goal>
- </goals>
- <configuration>
- <sources>
- <source>src/main/scala</source>
- </sources>
- </configuration>
- </execution>
- <execution>
- <id>add-test-source</id>
- <phase>generate-test-sources</phase>
- <goals>
- <goal>add-test-source</goal>
- </goals>
- <configuration>
- <sources>
- <source>src/test/scala</source>
- </sources>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
- <id>sign-artifacts</id>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-gpg-plugin</artifactId>
- <version>1.6</version>
- <executions>
- <execution>
- <id>sign-artifacts</id>
- <phase>verify</phase>
- <goals>
- <goal>sign</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- </profiles>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.apache.maven.wagon</groupId>
- <artifactId>wagon-ssh-external</artifactId>
- <version>2.7</version>
- </dependency>
- <dependency>
- <groupId>com.github.cverges.expect4j</groupId>
- <artifactId>expect4j</artifactId>
- <version>1.6</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-compress</artifactId>
- <version>1.11</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-exec</artifactId>
- <version>1.3</version>
- </dependency>
- <dependency>
- <groupId>io.airlift</groupId>
- <artifactId>airline</artifactId>
- <version>0.7</version>
- </dependency>
- <dependency>
- <groupId>aopalliance</groupId>
- <artifactId>aopalliance</artifactId>
- <version>1.0</version>
- </dependency>
- <dependency>
- <groupId>org.ow2.asm</groupId>
- <artifactId>asm</artifactId>
- <version>5.2</version>
- </dependency>
- <dependency>
- <groupId>com.google.code.findbugs</groupId>
- <artifactId>annotations</artifactId>
- <version>1.3.9</version>
- </dependency>
- <dependency>
- <groupId>com.google.code.findbugs</groupId>
- <artifactId>jsr305</artifactId>
- <version>1.3.9</version>
- </dependency>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>18.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava-testlib</artifactId>
- <version>18.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.inject</groupId>
- <artifactId>guice</artifactId>
- <version>3.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.inject</groupId>
- <artifactId>guice</artifactId>
- <version>3.0</version>
- <classifier>no_aop</classifier>
- </dependency>
- <dependency>
- <groupId>com.google.inject.extensions</groupId>
- <artifactId>guice-assistedinject</artifactId>
- <version>3.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.inject.extensions</groupId>
- <artifactId>guice-multibindings</artifactId>
- <version>3.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.protobuf</groupId>
- <artifactId>protobuf-java</artifactId>
- <version>2.4.1</version>
- </dependency>
- <dependency>
- <groupId>com.googlecode.jmockit</groupId>
- <artifactId>jmockit</artifactId>
- <version>1.2</version>
- </dependency>
- <dependency>
- <groupId>com.goldmansachs</groupId>
- <artifactId>gs-collections</artifactId>
- <version>6.1.0</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-annotations</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.jaxrs</groupId>
- <artifactId>jackson-jaxrs-json-provider</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.module</groupId>
- <artifactId>jackson-module-jaxb-annotations</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.jaxrs</groupId>
- <artifactId>jackson-jaxrs-base</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.jaxrs</groupId>
- <artifactId>jackson-jaxrs-xml-provider</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.dataformat</groupId>
- <artifactId>jackson-dataformat-xml</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.datatype</groupId>
- <artifactId>jackson-datatype-jdk8</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.datatype</groupId>
- <artifactId>jackson-datatype-jsr310</artifactId>
- <version>${jackson2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.infradna.tool</groupId>
- <artifactId>bridge-method-annotation</artifactId>
- <version>1.4</version>
- </dependency>
- <dependency>
- <groupId>commons-cli</groupId>
- <artifactId>commons-cli</artifactId>
- <version>1.3.1</version>
- </dependency>
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <version>1.4</version>
- </dependency>
- <dependency>
- <groupId>commons-collections</groupId>
- <artifactId>commons-collections</artifactId>
- <version>3.2.1</version>
- </dependency>
- <dependency>
- <groupId>commons-configuration</groupId>
- <artifactId>commons-configuration</artifactId>
- <version>1.6</version>
- </dependency>
- <dependency>
- <groupId>commons-daemon</groupId>
- <artifactId>commons-daemon</artifactId>
- <version>1.0.3</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.4</version>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>${commons-lang.version}</version>
- </dependency>
- <dependency>
- <!-- This version is exported by jdisc via jcl-over-slf4j. -->
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- <version>1.1.1</version>
- </dependency>
- <dependency>
- <groupId>commons-net</groupId>
- <artifactId>commons-net</artifactId>
- <version>2.0</version>
- </dependency>
- <dependency>
- <groupId>commons-pool</groupId>
- <artifactId>commons-pool</artifactId>
- <version>1.5.6</version>
- </dependency>
- <!-- Explicitly included to get Zookeeper version 3.4.10,
- can be excluded if you want the Zookeeper version
- used by curator by default
- -->
- <dependency>
- <groupId>org.apache.zookeeper</groupId>
- <artifactId>zookeeper</artifactId>
- <version>3.4.10</version>
- </dependency>
- <dependency>
- <groupId>org.apache.curator</groupId>
- <artifactId>curator-recipes</artifactId>
- <version>${curator.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.curator</groupId>
- <artifactId>curator-test</artifactId>
- <version>${curator.version}</version>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>3.1.0</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
- </dependency>
- <dependency>
- <groupId>org.antlr</groupId>
- <artifactId>antlr-runtime</artifactId>
- <version>${antlr.version}</version>
- </dependency>
- <dependency>
- <groupId>org.antlr</groupId>
- <artifactId>antlr4-runtime</artifactId>
- <version>${antlr4.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.aries.spifly</groupId>
- <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
- <version>${aries.spifly.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.framework</artifactId>
- <version>4.2.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.log</artifactId>
- <version>1.0.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.main</artifactId>
- <version>4.2.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>fluent-hc</artifactId>
- <version>4.3.6</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.3.6</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpcore</artifactId>
- <version>4.3.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpmime</artifactId>
- <version>4.3.6</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-artifact</artifactId>
- <version>3.5.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-core</artifactId>
- <version>3.5.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-model</artifactId>
- <version>3.5.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven.plugin-tools</groupId>
- <artifactId>maven-plugin-annotations</artifactId>
- <version>3.5</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-plugin-api</artifactId>
- <version>3.5.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-project</artifactId>
- <version>2.2.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <version>3.0.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven.surefire</groupId>
- <artifactId>surefire-junit4</artifactId>
- <version>${surefire.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.maven.surefire</groupId>
- <artifactId>surefire-providers</artifactId>
- <version>${surefire.version}</version>
- <type>pom</type>
- </dependency>
- <dependency>
- <groupId>org.codehaus.jettison</groupId>
- <artifactId>jettison</artifactId>
- <version>1.3.1</version>
- </dependency>
- <dependency>
- <groupId>org.cthul</groupId>
- <artifactId>cthul-matchers</artifactId>
- <version>1.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-continuation</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-server</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlet</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlets</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-util</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-http</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-jmx</artifactId>
- <version>${jetty.version}</version>
- </dependency>
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-all</artifactId>
- <version>1.3</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-core</artifactId>
- <version>1.3</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-library</artifactId>
- <version>1.3</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>uk.co.datumedge</groupId>
- <artifactId>hamcrest-json</artifactId>
- <version>0.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.hdrhistogram</groupId>
- <artifactId>HdrHistogram</artifactId>
- <version>2.1.8</version>
- </dependency>
- <dependency>
- <groupId>org.json</groupId>
- <artifactId>json</artifactId>
- <version>20090211</version>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.9.5</version>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>1.9.5</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.compendium</artifactId>
- <version>4.3.0</version>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.core</artifactId>
- <version>4.3.0</version>
- </dependency>
- <dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- <version>${scala.version}</version>
- </dependency>
- <dependency>
- <groupId>org.scala-lang.modules</groupId>
- <artifactId>scala-parser-combinators_${scala.major-version}</artifactId>
- <version>1.0.1</version>
- </dependency>
- <dependency>
- <groupId>org.scala-lang.modules</groupId>
- <artifactId>scala-xml_${scala.major-version}</artifactId>
- <version>1.0.2</version>
- </dependency>
- <dependency>
- <groupId>org.scalatest</groupId>
- <artifactId>scalatest_${scala.major-version}</artifactId>
- <version>2.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- <version>1.7.5</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>log4j-over-slf4j</artifactId>
- <version>1.7.5</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.7.5</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-jdk14</artifactId>
- <version>1.7.5</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>4.0.6.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.testng</groupId>
- <artifactId>testng</artifactId>
- <version>6.10</version>
- </dependency>
- <dependency>
- <groupId>org.twdata.maven</groupId>
- <artifactId>mojo-executor</artifactId>
- <version>2.3.0</version>
- </dependency>
- <dependency>
- <groupId>net.jcip</groupId>
- <artifactId>jcip-annotations</artifactId>
- <version>1.0</version>
- </dependency>
- <dependency>
- <groupId>net.jpountz.lz4</groupId>
- <artifactId>lz4</artifactId>
- <version>1.3.0</version>
- </dependency>
- <dependency>
- <groupId>net.spy</groupId>
- <artifactId>spymemcached</artifactId>
- <version>2.10.1</version>
- </dependency>
- <dependency>
- <groupId>xerces</groupId>
- <artifactId>xercesImpl</artifactId>
- <version>2.11.0</version>
- </dependency>
- <dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
- <version>${bouncycastle.version}</version>
- </dependency>
- <dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- <version>${bouncycastle.version}</version>
- </dependency>
- <!-- jersey 2 support -->
- <dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>javax.ws.rs-api</artifactId>
- <version>${javax.ws.rs-api.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.containers</groupId>
- <artifactId>jersey-container-servlet-core</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.containers</groupId>
- <artifactId>jersey-container-servlet</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.media</groupId>
- <artifactId>jersey-media-json-jackson</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.media</groupId>
- <artifactId>jersey-media-multipart</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.ext</groupId>
- <artifactId>jersey-proxy-client</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.core</groupId>
- <artifactId>jersey-client</artifactId>
- <version>${jersey2.version}</version>
- </dependency>
- <dependency>
- <groupId>com.ibm.icu</groupId>
- <artifactId>icu4j</artifactId>
- <version>57.1</version>
- </dependency>
- <dependency>
- <groupId>com.yahoo.athenz</groupId>
- <artifactId>athenz-zms-java-client</artifactId>
- <version>${athenz.version}</version>
- </dependency>
- <dependency>
- <groupId>com.yahoo.athenz</groupId>
- <artifactId>athenz-zts-java-client</artifactId>
- <version>${athenz.version}</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
- <properties>
- <javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version> <!-- must be kept in sync with version used by current jersey2.version -->
- <antlr.version>3.5.2</antlr.version>
- <antlr4.version>4.5</antlr4.version>
- <aries.spifly.version>1.0.8</aries.spifly.version>
- <aries.util.version>1.0.0</aries.util.version>
- <asm-debug-all.version>5.0.3</asm-debug-all.version>
- <!-- Athenz dependencies. Make sure these dependencies matches those in Vespa's internal repositories -->
- <athenz.version>1.7.28</athenz.version>
- <bouncycastle.version>1.58</bouncycastle.version>
- <commons-lang.version>2.6</commons-lang.version>
- <!-- WARNING: If you change curator version, you also need to update
- zkfacade/src/main/java/org/apache/curator/**/package-info.java
- using something like
- find zkfacade/src/main/java/org/apache/curator -name package-info.java | \
- xargs perl -pi -e 's/major = [0-9]+, minor = [0-9]+, micro = [0-9]+/major = 2, minor = 9, micro = 1/g'
- -->
- <curator.version>2.9.1</curator.version>
- <jackson2.version>2.8.3</jackson2.version>
- <jersey2.version>2.23.2</jersey2.version>
- <jetty.version>9.4.6.v20170531</jetty.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <test.hide>true</test.hide>
- <doclint>all</doclint>
- <scala.major-version>2.11</scala.major-version>
- <scala.version>${scala.major-version}.4</scala.version>
- <surefire.version>2.19.1</surefire.version> <!-- NOTE bjorncs 15.06.2017: Version 2.20 has OoM issues -->
- </properties>
-
<modules>
<module>application</module>
<module>application-deploy-plugin</module>
@@ -989,6 +53,7 @@
<module>container-core</module>
<module>container-accesslogging</module>
<module>container-dependencies-enforcer</module>
+ <module>container-dependency-versions</module>
<module>container-dev</module>
<module>container-di</module>
<module>container-disc</module>
@@ -1033,6 +98,7 @@
<module>node-maintainer</module>
<module>orchestrator-restapi</module>
<module>orchestrator</module>
+ <module>parent</module>
<module>predicate-search</module>
<module>predicate-search-core</module>
<module>processing</module>
diff --git a/predicate-search-core/pom.xml b/predicate-search-core/pom.xml
index ea2995c4770..8ced58b840e 100644
--- a/predicate-search-core/pom.xml
+++ b/predicate-search-core/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>predicate-search-core</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/predicate-search/pom.xml b/predicate-search/pom.xml
index ca79544c766..289250012a0 100644
--- a/predicate-search/pom.xml
+++ b/predicate-search/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>predicate-search</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/processing/pom.xml b/processing/pom.xml
index 15aa984cbd2..9bb9ee77037 100644
--- a/processing/pom.xml
+++ b/processing/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>processing</artifactId>
<packaging>jar</packaging>
diff --git a/provided-dependencies/pom.xml b/provided-dependencies/pom.xml
index 18cdd294b87..33f1977c7e2 100755
--- a/provided-dependencies/pom.xml
+++ b/provided-dependencies/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>provided-dependencies</artifactId>
<packaging>jar</packaging>
diff --git a/searchcore/pom.xml b/searchcore/pom.xml
index f31617cfe53..3b43bf1205e 100644
--- a/searchcore/pom.xml
+++ b/searchcore/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>searchcore</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp b/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp
index b8a3cceb153..6d69d9b225b 100644
--- a/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp
+++ b/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp
@@ -5,7 +5,7 @@
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/common/sequencedtaskexecutor.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
using namespace proton;
using namespace search;
diff --git a/searchcore/src/tests/proton/common/selectpruner_test.cpp b/searchcore/src/tests/proton/common/selectpruner_test.cpp
index cea72a556cc..4ec5bb928f6 100644
--- a/searchcore/src/tests/proton/common/selectpruner_test.cpp
+++ b/searchcore/src/tests/proton/common/selectpruner_test.cpp
@@ -618,8 +618,6 @@ TEST_F("Test that multiplication has higher priority than addition",
TEST_F("Test that toplevel functions are visited", TestFixture)
{
- f.testPrune("searchcolumn.15 == 4",
- "searchcolumn.15 == 4");
f.testPrune("id.scheme == \"doc\"",
"id.scheme == \"doc\"");
f.testPrune("test.aa < now() - 7200",
diff --git a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp
index 87efd74659d..d3567da3527 100644
--- a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp
@@ -666,6 +666,42 @@ TEST("require that maintenance controller should change if some config has chang
TEST_DO(assertMaintenanceControllerShouldChange(CCR().setMaintenanceChanged(true)));
}
+void
+assertSubDbsShouldNotChange(DocumentDBConfig::ComparisonResult result)
+{
+ ReconfigParams params(result);
+ EXPECT_FALSE(params.configHasChanged());
+ EXPECT_FALSE(params.shouldSubDbsChange());
+}
+
+void
+assertSubDbsShouldChange(DocumentDBConfig::ComparisonResult result)
+{
+ ReconfigParams params(result);
+ EXPECT_TRUE(params.configHasChanged());
+ EXPECT_TRUE(params.shouldSubDbsChange());
+}
+
+
+TEST("require that subdbs should change if relevant config changed")
+{
+ TEST_DO(assertSubDbsShouldNotChange(CCR()));
+ EXPECT_FALSE(ReconfigParams(CCR().setMaintenanceChanged(true)).shouldSubDbsChange());
+ TEST_DO(assertSubDbsShouldChange(CCR().setFlushChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setStoreChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setDocumenttypesChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setDocumentTypeRepoChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setSummaryChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setSummarymapChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setJuniperrcChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setAttributesChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setImportedFieldsChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setVisibilityDelayChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setRankProfilesChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setRankingConstantsChanged(true)));
+ TEST_DO(assertSubDbsShouldChange(CCR().setSchemaChanged(true)));
+}
+
TEST_MAIN()
{
TEST_RUN_ALL();
diff --git a/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp b/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp
index 0a4b83350ba..c1626e94809 100644
--- a/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp
@@ -7,7 +7,7 @@ LOG_SETUP("job_tracked_maintenance_test");
#include <vespa/searchcore/proton/test/simple_job_tracker.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/closuretask.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
using namespace proton;
diff --git a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
index b84aa1c1c6c..bf88a0c3003 100644
--- a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
@@ -32,8 +32,9 @@
#include <vespa/vespalib/data/slime/slime.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/closuretask.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
+#include <unistd.h>
#include <vespa/log/log.h>
LOG_SETUP("maintenancecontroller_test");
@@ -497,7 +498,8 @@ public:
_mcCfg->getLidSpaceCompactionConfig(),
_mcCfg->getAttributeUsageFilterConfig(),
_mcCfg->getAttributeUsageSampleInterval(),
- _mcCfg->getBlockableJobConfig()));
+ _mcCfg->getBlockableJobConfig(),
+ _mcCfg->getFlushConfig()));
_mcCfg = newCfg;
forwardMaintenanceConfig();
}
@@ -514,7 +516,8 @@ public:
_mcCfg->getLidSpaceCompactionConfig(),
_mcCfg->getAttributeUsageFilterConfig(),
_mcCfg->getAttributeUsageSampleInterval(),
- _mcCfg->getBlockableJobConfig()));
+ _mcCfg->getBlockableJobConfig(),
+ _mcCfg->getFlushConfig()));
_mcCfg = newCfg;
forwardMaintenanceConfig();
}
@@ -531,7 +534,8 @@ public:
_mcCfg->getLidSpaceCompactionConfig(),
_mcCfg->getAttributeUsageFilterConfig(),
_mcCfg->getAttributeUsageSampleInterval(),
- _mcCfg->getBlockableJobConfig()));
+ _mcCfg->getBlockableJobConfig(),
+ _mcCfg->getFlushConfig()));
_mcCfg = newCfg;
forwardMaintenanceConfig();
}
@@ -546,7 +550,8 @@ public:
cfg,
_mcCfg->getAttributeUsageFilterConfig(),
_mcCfg->getAttributeUsageSampleInterval(),
- _mcCfg->getBlockableJobConfig()));
+ _mcCfg->getBlockableJobConfig(),
+ _mcCfg->getFlushConfig()));
_mcCfg = newCfg;
forwardMaintenanceConfig();
}
diff --git a/searchcore/src/tests/proton/flushengine/flushengine.cpp b/searchcore/src/tests/proton/flushengine/flushengine.cpp
index 5e8537b657f..d1a98f1b7d3 100644
--- a/searchcore/src/tests/proton/flushengine/flushengine.cpp
+++ b/searchcore/src/tests/proton/flushengine/flushengine.cpp
@@ -11,8 +11,9 @@
#include <vespa/searchcore/proton/test/dummy_flush_target.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/data/slime/slime.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/test/insertion_operators.h>
+#include <mutex>
+#include <chrono>
#include <vespa/log/log.h>
LOG_SETUP("flushengine_test");
@@ -122,7 +123,7 @@ public:
search::SerialNum _oldestSerial;
search::SerialNum _currentSerial;
uint32_t _pendingDone;
- vespalib::Lock _lock;
+ std::mutex _lock;
vespalib::CountDownLatch _done;
FlushDoneHistory _flushDoneHistory;
@@ -167,7 +168,7 @@ public:
// Called once by flush engine slave thread for each task done
void taskDone()
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
++_pendingDone;
}
@@ -177,7 +178,7 @@ public:
void
flushDone(search::SerialNum oldestSerial) override
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
LOG(info, "SimpleHandler(%s)::flushDone(%" PRIu64 ")",
getName().c_str(), oldestSerial);
_oldestSerial = std::max(_oldestSerial, oldestSerial);
@@ -190,7 +191,7 @@ public:
FlushDoneHistory getFlushDoneHistory()
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _flushDoneHistory;
}
};
@@ -393,13 +394,12 @@ public:
}
};
-class ConstantFlushStrategy : public SimpleStrategy {
-public:
- uint64_t _millis;
-
-public:
- ConstantFlushStrategy(uint64_t millis) : SimpleStrategy(), _millis(millis) { }
- typedef std::shared_ptr<ConstantFlushStrategy> SP;
+class NoFlushStrategy : public SimpleStrategy
+{
+ virtual FlushContext::List getFlushTargets(const FlushContext::List &,
+ const flushengine::TlsStatsMap &) const override {
+ return FlushContext::List();
+ }
};
// --------------------------------------------------------------------------------
@@ -437,12 +437,38 @@ struct Fixture
SimpleStrategy::SP strategy;
FlushEngine engine;
- Fixture(uint32_t numThreads, uint32_t idleIntervalMS)
+ Fixture(uint32_t numThreads, uint32_t idleIntervalMS, SimpleStrategy::SP strategy_)
: tlsStatsFactory(std::make_shared<SimpleTlsStatsFactory>()),
- strategy(std::make_shared<SimpleStrategy>()),
+ strategy(strategy_),
engine(tlsStatsFactory, strategy, numThreads, idleIntervalMS)
{
}
+
+ Fixture(uint32_t numThreads, uint32_t idleIntervalMS)
+ : Fixture(numThreads, idleIntervalMS, std::make_shared<SimpleStrategy>())
+ {
+ }
+
+ std::shared_ptr<SimpleHandler>
+ addSimpleHandler(Targets targets)
+ {
+ auto handler = std::make_shared<SimpleHandler>(targets, "handler", 20);
+ engine.putFlushHandler(DocTypeName("handler"), handler);
+ engine.start();
+ return handler;
+ }
+
+ void assertOldestSerial(SimpleHandler &handler, search::SerialNum expOldestSerial)
+ {
+ using namespace std::chrono_literals;
+ for (int pass = 0; pass < 600; ++pass) {
+ std::this_thread::sleep_for(100ms);
+ if (handler._oldestSerial == expOldestSerial) {
+ break;
+ }
+ }
+ EXPECT_EQUAL(expOldestSerial, handler._oldestSerial);
+ }
};
@@ -717,6 +743,26 @@ TEST_F("require that state explorer can list flush targets", Fixture(1, 1))
target->_taskDone.await(LONG_TIMEOUT);
}
+TEST_F("require that oldest serial is updated when closing engine", Fixture(1, 100))
+{
+ auto target1 = std::make_shared<SimpleTarget>("target1", 10, false);
+ auto handler = f.addSimpleHandler({ target1 });
+ TEST_DO(f.assertOldestSerial(*handler, 10));
+ target1->_proceed.countDown();
+ f.engine.close();
+ EXPECT_EQUAL(20u, handler->_oldestSerial);
+}
+
+TEST_F("require that oldest serial is updated when finishing priority flush strategy", Fixture(1, 100, std::make_shared<NoFlushStrategy>()))
+{
+ auto target1 = std::make_shared<SimpleTarget>("target1", 10, true);
+ auto handler = f.addSimpleHandler({ target1 });
+ TEST_DO(f.assertOldestSerial(*handler, 10));
+ f.engine.setStrategy(std::make_shared<SimpleStrategy>());
+ EXPECT_EQUAL(20u, handler->_oldestSerial);
+}
+
+
TEST_MAIN()
{
TEST_RUN_ALL();
diff --git a/searchcore/src/tests/proton/index/indexmanager_test.cpp b/searchcore/src/tests/proton/index/indexmanager_test.cpp
index e8afd738e84..6a098667be8 100644
--- a/searchcore/src/tests/proton/index/indexmanager_test.cpp
+++ b/searchcore/src/tests/proton/index/indexmanager_test.cpp
@@ -20,7 +20,6 @@
#include <vespa/searchlib/common/serialnum.h>
#include <vespa/searchlib/util/dirtraverse.h>
#include <vespa/vespalib/testkit/testapp.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
#include <vespa/vespalib/io/fileutil.h>
@@ -50,9 +49,6 @@ using search::queryeval::Source;
using std::set;
using std::string;
using vespalib::BlockingThreadStackExecutor;
-using vespalib::Gate;
-using vespalib::Monitor;
-using vespalib::MonitorGuard;
using vespalib::ThreadStackExecutor;
using namespace proton;
diff --git a/searchcore/src/tests/proton/matchengine/matchengine.cpp b/searchcore/src/tests/proton/matchengine/matchengine.cpp
index d2ce4b14b9c..ccae9fa3d4f 100644
--- a/searchcore/src/tests/proton/matchengine/matchengine.cpp
+++ b/searchcore/src/tests/proton/matchengine/matchengine.cpp
@@ -3,6 +3,9 @@
#include <vespa/vespalib/data/slime/slime.h>
#include <vespa/searchlib/engine/docsumreply.h>
#include <vespa/vespalib/testkit/test_kit.h>
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
using namespace proton;
using namespace search::engine;
@@ -34,23 +37,26 @@ public:
class LocalSearchClient : public SearchClient {
private:
- vespalib::Monitor _monitor;
- SearchReply::UP _reply;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ SearchReply::UP _reply;
public:
LocalSearchClient();
~LocalSearchClient();
void searchDone(SearchReply::UP reply) override {
- vespalib::MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
_reply = std::move(reply);
- guard.broadcast();
+ _cond.notify_all();
}
SearchReply::UP getReply(uint32_t millis) {
- vespalib::MonitorGuard guard(_monitor);
- vespalib::TimedWaiter waiter(guard, millis);
- while (_reply.get() == NULL && waiter.hasTime()) {
- waiter.wait();
+ std::unique_lock<std::mutex> guard(_lock);
+ auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(millis);
+ while (!_reply) {
+ if (_cond.wait_until(guard, deadline) == std::cv_status::timeout) {
+ break;
+ }
}
return std::move(_reply);
}
diff --git a/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp b/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp
index a0dd3ee6214..4ed01f8caf7 100644
--- a/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp
+++ b/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp
@@ -9,7 +9,7 @@ LOG_SETUP("job_tracked_flush_test");
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/closuretask.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
using namespace proton;
using namespace searchcorespi;
@@ -17,7 +17,6 @@ using search::SerialNum;
using test::SimpleJobTracker;
using vespalib::makeTask;
using vespalib::makeClosure;
-using vespalib::CountDownLatch;
using vespalib::Gate;
using vespalib::ThreadStackExecutor;
diff --git a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp
index 4dd8c43d9b2..fb0c9b1f3a9 100644
--- a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp
+++ b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp
@@ -3,14 +3,12 @@
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/searchcore/proton/common/hw_info.h>
#include <vespa/searchcore/proton/server/disk_mem_usage_filter.h>
+#include <vespa/searchcore/proton/server/resource_usage_state.h>
-using proton::DiskMemUsageFilter;
-using proton::HwInfo;
+using namespace proton;
namespace fs = std::experimental::filesystem;
-namespace {
-
struct Fixture
{
DiskMemUsageFilter _filter;
@@ -18,13 +16,13 @@ struct Fixture
using Config = DiskMemUsageFilter::Config;
Fixture()
- : _filter(HwInfo(HwInfo::Disk(100, false, false), HwInfo::Memory(64 * 1024 * 1024), HwInfo::Cpu(0)))
+ : _filter(HwInfo(HwInfo::Disk(100, false, false), HwInfo::Memory(1000), HwInfo::Cpu(0)))
{
- _filter.setDiskUsedSize(0);
- _filter.setMemoryStats(vespalib::ProcessMemoryStats(10000000,
- 10000001,
- 10000002,
- 10000003,
+ _filter.setDiskUsedSize(20);
+ _filter.setMemoryStats(vespalib::ProcessMemoryStats(297,
+ 298,
+ 299,
+ 300,
42));
}
@@ -48,16 +46,14 @@ struct Fixture
void triggerMemoryLimit()
{
- _filter.setMemoryStats(vespalib::ProcessMemoryStats(58720259,
- 58720258,
- 58720257,
- 58720256,
+ _filter.setMemoryStats(vespalib::ProcessMemoryStats(897,
+ 898,
+ 899,
+ 900,
43));
}
};
-}
-
TEST_F("Check that default filter allows write", Fixture)
{
f.testWrite("");
@@ -70,28 +66,40 @@ TEST_F("Check that stats are wired through", Fixture)
EXPECT_EQUAL(43u, f._filter.getMemoryStats().getMappingsCount());
}
+void
+assertResourceUsage(double usage, double limit, double utilization, const ResourceUsageState &state)
+{
+ EXPECT_EQUAL(usage, state.usage());
+ EXPECT_EQUAL(limit, state.limit());
+ EXPECT_EQUAL(utilization, state.utilization());
+}
+
TEST_F("Check that disk limit can be reached", Fixture)
{
f._filter.setConfig(Fixture::Config(1.0, 0.8));
+ TEST_DO(assertResourceUsage(0.2, 0.8, 0.25, f._filter.usageState().diskState()));
f.triggerDiskLimit();
f.testWrite("diskLimitReached: { "
"action: \"add more content nodes\", "
"reason: \"disk used (0.9) > disk limit (0.8)\", "
"stats: { "
"capacity: 100, used: 90, diskUsed: 0.9, diskLimit: 0.8}}");
+ TEST_DO(assertResourceUsage(0.9, 0.8, 1.125, f._filter.usageState().diskState()));
}
TEST_F("Check that memory limit can be reached", Fixture)
{
f._filter.setConfig(Fixture::Config(0.8, 1.0));
+ TEST_DO(assertResourceUsage(0.3, 0.8, 0.375, f._filter.usageState().memoryState()));
f.triggerMemoryLimit();
f.testWrite("memoryLimitReached: { "
"action: \"add more content nodes\", "
- "reason: \"memory used (0.875) > memory limit (0.8)\", "
+ "reason: \"memory used (0.9) > memory limit (0.8)\", "
"stats: { "
- "mapped: { virt: 58720259, rss: 58720258}, "
- "anonymous: { virt: 58720257, rss: 58720256}, "
- "physicalMemory: 67108864, memoryUsed: 0.875, memoryLimit: 0.8}}");
+ "mapped: { virt: 897, rss: 898}, "
+ "anonymous: { virt: 899, rss: 900}, "
+ "physicalMemory: 1000, memoryUsed: 0.9, memoryLimit: 0.8}}");
+ TEST_DO(assertResourceUsage(0.9, 0.8, 1.125, f._filter.usageState().memoryState()));
}
TEST_F("Check that both disk limit and memory limit can be reached", Fixture)
@@ -101,11 +109,11 @@ TEST_F("Check that both disk limit and memory limit can be reached", Fixture)
f.triggerDiskLimit();
f.testWrite("memoryLimitReached: { "
"action: \"add more content nodes\", "
- "reason: \"memory used (0.875) > memory limit (0.8)\", "
+ "reason: \"memory used (0.9) > memory limit (0.8)\", "
"stats: { "
- "mapped: { virt: 58720259, rss: 58720258}, "
- "anonymous: { virt: 58720257, rss: 58720256}, "
- "physicalMemory: 67108864, memoryUsed: 0.875, memoryLimit: 0.8}}, "
+ "mapped: { virt: 897, rss: 898}, "
+ "anonymous: { virt: 899, rss: 900}, "
+ "physicalMemory: 1000, memoryUsed: 0.9, memoryLimit: 0.8}}, "
"diskLimitReached: { "
"action: \"add more content nodes\", "
"reason: \"disk used (0.9) > disk limit (0.8)\", "
diff --git a/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp b/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp
index a76596aa927..b3a7f5f525e 100644
--- a/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp
+++ b/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp
@@ -56,9 +56,16 @@ struct Fixture
EXPECT_EQUAL(expMaxEachMemory, strategy->getConfig().maxMemoryGain);
EXPECT_EQUAL(expMaxGlobalTlsSize, strategy->getConfig().maxGlobalTlsSize);
}
+ void assertStrategyDiskConfig(double expGlobalDiskBloatFactor, double expDiskBloatFactor) {
+ EXPECT_APPROX(expGlobalDiskBloatFactor, strategy->getConfig().globalDiskBloatFactor, 0.00001);
+ EXPECT_APPROX(expDiskBloatFactor, strategy->getConfig().diskBloatFactor, 0.00001);
+ }
void notifyDiskMemUsage(const ResourceUsageState &diskState, const ResourceUsageState &memoryState) {
updater.notifyDiskMemUsage(DiskMemUsageState(diskState, memoryState));
}
+ void setNodeRetired(bool nodeRetired) {
+ updater.setNodeRetired(nodeRetired);
+ }
};
TEST_F("require that strategy is updated when setting new config", Fixture)
@@ -133,4 +140,14 @@ TEST_F("require that we must go below low watermark for memory usage before usin
TEST_DO(f.assertStrategyConfig(4, 1, 20));
}
+TEST_F("require that more disk bloat is allowed while node state is retired", Fixture)
+{
+ f.notifyDiskMemUsage(ResourceUsageState(0.7, 0.3), belowLimit());
+ TEST_DO(f.assertStrategyDiskConfig(0.2, 0.2));
+ f.setNodeRetired(true);
+ TEST_DO(f.assertStrategyDiskConfig((0.8 - 0.3 / 0.7) * 0.8, 1.0));
+ f.notifyDiskMemUsage(belowLimit(), belowLimit());
+ TEST_DO(f.assertStrategyDiskConfig(0.2, 0.2));
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchcore/src/tests/proton/summaryengine/summaryengine.cpp b/searchcore/src/tests/proton/summaryengine/summaryengine.cpp
index db707e4aa97..4951b1cd569 100644
--- a/searchcore/src/tests/proton/summaryengine/summaryengine.cpp
+++ b/searchcore/src/tests/proton/summaryengine/summaryengine.cpp
@@ -9,8 +9,11 @@
#include <vespa/vespalib/util/compressor.h>
#include <vespa/searchlib/common/transport.h>
#include <vespa/fnet/frt/rpcrequest.h>
-#include <vespa/log/log.h>
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+#include <vespa/log/log.h>
LOG_SETUP("summaryengine_test");
using namespace search::engine;
@@ -81,8 +84,9 @@ public:
class MyDocsumClient : public DocsumClient {
private:
- vespalib::Monitor _monitor;
- DocsumReply::UP _reply;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ DocsumReply::UP _reply;
public:
MyDocsumClient();
@@ -90,16 +94,18 @@ public:
~MyDocsumClient();
void getDocsumsDone(DocsumReply::UP reply) override {
- vespalib::MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
_reply = std::move(reply);
- guard.broadcast();
+ _cond.notify_all();
}
DocsumReply::UP getReply(uint32_t millis) {
- vespalib::MonitorGuard guard(_monitor);
- vespalib::TimedWaiter waiter(guard, millis);
- while (_reply.get() == NULL && waiter.hasTime()) {
- waiter.wait();
+ std::unique_lock<std::mutex> guard(_lock);
+ auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(millis);
+ while (!_reply) {
+ if (_cond.wait_until(guard, deadline) == std::cv_status::timeout) {
+ break;
+ }
}
return std::move(_reply);
}
diff --git a/searchcore/src/vespa/searchcore/config/proton.def b/searchcore/src/vespa/searchcore/config/proton.def
index c6c810ae72f..f46f6c67fd4 100644
--- a/searchcore/src/vespa/searchcore/config/proton.def
+++ b/searchcore/src/vespa/searchcore/config/proton.def
@@ -143,9 +143,15 @@ index.warmup.time double default=0.0 restart
# Indicate if we also want warm up with full unpack, instead of only cheaper seek.
index.warmup.unpack bool default=false restart
-## How many flushed indexes there can be befor fusion is forced.
+## How many flushed indexes there can be befor fusion is forced while node is
+## not in retired state.
## Setting to 1 will force an immediate fusion.
-index.maxflushed int default=2 restart
+index.maxflushed int default=2
+
+## How many flushed indexes there can be befor fusion is forced while node is
+## in retired state.
+## Setting to 1 will force an immediate fusion.
+index.maxflushedretired int default=20
## How much memory is set aside for caching.
## Now only used for caching of dictionary lookups.
diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h b/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h
index a19dcff025d..ca0053e0261 100644
--- a/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h
+++ b/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h
@@ -329,6 +329,10 @@ public:
_util.CalcHitCount();
_util.AllocAlignedHitBuf();
}
+ void ST_AdjustNumHits(uint32_t numHits) {
+ _util.SetAlignedHitCount(numHits);
+ _util.CalcHitCount();
+ }
uint32_t ST_GetAlignedSearchOffset() const { return _util.GetAlignedSearchOffset(); }
uint32_t ST_GetAlignedMaxHits() const { return _util.GetAlignedMaxHits(); }
uint32_t ST_GetAlignedHitCount() const { return _util.GetAlignedHitCount(); }
diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp
index 846d95bd722..8c4a08a3bbb 100644
--- a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp
+++ b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp
@@ -5,6 +5,8 @@
#include "fnet_dataset.h"
#include "fnet_search.h"
#include <vespa/searchcore/util/stlishheap.h>
+#include <vespa/vespalib/stllike/hash_set.h>
+#include <vespa/vespalib/stllike/hash_set.hpp>
#include <vespa/log/log.h>
LOG_SETUP(".fdispatch.mergehits");
@@ -65,10 +67,17 @@ FastS_MergeCopyHit(typename T::HitType *src,
dst->setDistributionKey(src->getDistributionKey());
}
+struct GlobalIdHasher {
+ vespalib::hash_set<document::GlobalId, document::GlobalId::hash> seenSet;
+ bool insert(const document::GlobalId & g_id) {
+ return seenSet.insert(g_id).second;
+ }
+ GlobalIdHasher(size_t expected_size) : seenSet(expected_size * 3) {}
+};
template <typename T, typename F>
-void
+size_t
FastS_InternalMergeHits(FastS_HitMerger<T> *merger)
{
typename T::SearchType *search = merger->GetSearch();
@@ -89,18 +98,22 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger)
sortItr = sortRef;
}
+ GlobalIdHasher seenGids(end - beg);
+
FastS_make_heap(heap, heapSize, FastS_MergeCompare<T, F>);
- while (pt < end) {
+ while ((pt < end) && (heapSize > 0)) {
node = *heap;
- FastS_assert(heapSize > 0);
+ bool useHit = seenGids.insert(node->NT_GetHit()->HT_GetGlobalID());
if (F::UseSortData()) {
- if (!F::DropSortData()) {
+ if (!F::DropSortData() && useHit) {
FastS_MergeCopySortData<T>(node, sortItr++, sortDataLen);
}
node->NT_GetSortDataIterator()->Next();
}
- FastS_MergeCopyHit<T>(node->NT_GetHit(), pt++);
+ if (useHit) {
+ FastS_MergeCopyHit<T>(node->NT_GetHit(), pt++);
+ }
node->NT_NextHit();
if (node->NT_GetNumHitsLeft() > 0) {
FastS_pop_push_heap(heap, heapSize, node, FastS_MergeCompare<T, F>);
@@ -108,9 +121,13 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger)
FastS_pop_heap(heap, heapSize--, FastS_MergeCompare<T, F>);
}
}
+ if (pt != end) {
+ LOG(warning, "Duplicate removal lead to %zd missing hits (wanted %zd, got %zd)",
+ end - pt, end - beg, pt - beg);
+ }
merger->SetLastNode(node); // source of last hit
if (F::UseSortData()) {
- FastS_assert(F::DropSortData() || sortItr == sortRef + (end - beg));
+ FastS_assert(F::DropSortData() || sortItr == sortRef + (pt - beg));
}
// generate merged sort data
@@ -124,16 +141,17 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger)
char *sortData = search->ST_GetSortData();
sortItr = sortRef;
- for (uint32_t residue = (end - beg); residue > 0; residue--) {
+ for (uint32_t residue = (pt - beg); residue > 0; residue--) {
*sortIdx++ = offset;
memcpy(sortData + offset, sortItr->_buf, sortItr->_len);
offset += sortItr->_len;
sortItr++;
}
*sortIdx = offset;
- FastS_assert(sortItr == sortRef + (end - beg));
+ FastS_assert(sortItr == sortRef + (pt - beg));
FastS_assert(offset == sortDataLen);
}
+ return (pt - beg);
}
//-----------------------------------------------------------------------------
@@ -219,16 +237,17 @@ FastS_HitMerger<T>::MergeHits()
// do actual merging by invoking templated function
if (useSortData) {
if (dropSortData) {
- FastS_InternalMergeHits
+ numDocs = FastS_InternalMergeHits
<T, FastS_MergeFeatures<true, true> >(this);
} else {
- FastS_InternalMergeHits
+ numDocs = FastS_InternalMergeHits
<T, FastS_MergeFeatures<true, false> >(this);
}
} else {
- FastS_InternalMergeHits
+ numDocs = FastS_InternalMergeHits
<T, FastS_MergeFeatures<false, false> >(this);
}
+ _search->ST_AdjustNumHits(numDocs);
// detect incomplete/fuzzy results
if (_search->ST_ShouldLimitHitsPerNode()) {
diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h
index 79157acb175..306229b4730 100644
--- a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h
+++ b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h
@@ -64,6 +64,7 @@ struct FastS_MergeHits_DummySearch
bool ST_ShouldDropSortData() { return false; }
bool ST_ShouldLimitHitsPerNode() { return false; }
void ST_SetNumHits(uint32_t numHits) { (void) numHits; }
+ void ST_AdjustNumHits(uint32_t nH) { (void) nH; }
uint32_t ST_GetAlignedSearchOffset() { return 0; }
uint32_t ST_GetAlignedMaxHits() { return 0; }
uint32_t ST_GetAlignedHitCount() { return 0; }
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp b/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp
index c2c26e52a21..d9a0ff3d8dd 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "exclusive_attribute_read_accessor.h"
+#include <vespa/vespalib/util/gate.h>
#include <vespa/searchlib/attribute/attributevector.h>
#include <vespa/searchlib/common/isequencedtaskexecutor.h>
diff --git a/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp b/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp
index 49f70092d29..f3afab96cf5 100644
--- a/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp
+++ b/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp
@@ -303,4 +303,18 @@ EventLogger::loadDocumentStoreComplete(const vespalib::string &subDbName, int64_
loadComponentComplete(subDbName, "documentstore", elapsedTimeMs);
}
+void
+EventLogger::transactionLogPruneComplete(const string &domainName, SerialNum prunedSerial)
+{
+ JSONStringer jstr;
+ jstr.beginObject();
+ jstr.appendKey("domain").appendString(domainName);
+ jstr.appendKey("serialnum")
+ .beginObject()
+ .appendKey("pruned").appendInt64(prunedSerial)
+ .endObject();
+ jstr.endObject();
+ EV_STATE("transactionlog.prune.complete", jstr.toString().c_str());
+}
+
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/common/eventlogger.h b/searchcore/src/vespa/searchcore/proton/common/eventlogger.h
index ab3793eb677..6ba8852496e 100644
--- a/searchcore/src/vespa/searchcore/proton/common/eventlogger.h
+++ b/searchcore/src/vespa/searchcore/proton/common/eventlogger.h
@@ -50,6 +50,7 @@ public:
static void loadDocumentMetaStoreComplete(const vespalib::string &subDbName, int64_t elapsedTimeMs);
static void loadDocumentStoreStart(const vespalib::string &subDbName);
static void loadDocumentStoreComplete(const vespalib::string &subDbName, int64_t elapsedTimeMs);
+ static void transactionLogPruneComplete(const string &domainName, SerialNum prunedSerial);
};
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/common/feedtoken.h b/searchcore/src/vespa/searchcore/proton/common/feedtoken.h
index bc7e249ca3b..ab3fdea3345 100644
--- a/searchcore/src/vespa/searchcore/proton/common/feedtoken.h
+++ b/searchcore/src/vespa/searchcore/proton/common/feedtoken.h
@@ -3,7 +3,6 @@
#include <vespa/persistence/spi/persistenceprovider.h>
#include <vespa/searchlib/common/idestructorcallback.h>
-#include <vespa/vespalib/util/sync.h>
#include <atomic>
namespace proton {
diff --git a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
index 6b5d89cbd22..bddbfed371f 100644
--- a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
+++ b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
@@ -3,7 +3,6 @@
#include "doctypename.h"
#include <vespa/vespalib/util/exceptions.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/util/sequence.h>
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/stllike/hash_map.h>
diff --git a/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp b/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp
index 0915c11acfa..da45953e7b9 100644
--- a/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp
+++ b/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp
@@ -21,7 +21,6 @@ using document::select::Or;
using document::select::ArithmeticValueNode;
using document::select::FunctionValueNode;
using document::select::IdValueNode;
-using document::select::SearchColumnValueNode;
using document::select::FieldValueNode;
using document::select::FloatValueNode;
using document::select::VariableValueNode;
@@ -235,7 +234,7 @@ SelectPruner::visitDocumentType(const DocType &expr)
_inverted = true;
res = !res;
}
- _node.reset(new Constant(res ? "true" : "false"));
+ _node.reset(new Constant(res));
_resultSet.add(res ? Result::True : Result::False);
_priority = DocumentTypePriority;
}
@@ -411,7 +410,7 @@ SelectPruner::visitFieldValueNode(const FieldValueNode &expr)
if (!_hasFields) {
// If we're working on removed document sub db then we have no fields.
_constVal = true;
- _valueNode.reset(new NullValueNode("null"));
+ _valueNode.reset(new NullValueNode());
_priority = NullValPriority;
return;
}
@@ -511,7 +510,7 @@ SelectPruner::setTernaryConst(bool val)
{
_constVal = true;
_priority = ConstPriority;
- _node.reset(new Constant(val ? "true" : "false"));
+ _node.reset(new Constant(val));
}
void
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
index 026cbc00604..6667db02173 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
@@ -4,6 +4,8 @@
#include <vespa/searchlib/query/queryterm.h>
#include <vespa/searchlib/attribute/attributevector.h>
#include <vespa/searchlib/attribute/singlesmallnumericattribute.h>
+#include <mutex>
+
#include <vespa/log/log.h>
LOG_SETUP(".proton.documentmetastore.lid_allocator");
@@ -223,7 +225,7 @@ class BlackListBlueprint : public SimpleLeafBlueprint
{
private:
AttributeVector::SearchContext::UP _searchCtx;
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
mutable std::vector<search::fef::TermFieldMatchData *> _matchDataVector;
virtual SearchIterator::UP
@@ -235,7 +237,7 @@ private:
search::fef::TermFieldMatchData *tfmd =
new search::fef::TermFieldMatchData;
{
- vespalib::LockGuard lock(_lock);
+ std::lock_guard<std::mutex> lock(_lock);
_matchDataVector.push_back(tfmd);
}
return _searchCtx->createIterator(tfmd, strict);
diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
index fb25f8cf161..95b3008985b 100644
--- a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
+++ b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
@@ -12,10 +12,10 @@
#include <vespa/log/log.h>
LOG_SETUP(".proton.flushengine.flushengine");
-using vespalib::MonitorGuard;
typedef vespalib::Executor::Task Task;
using searchcorespi::IFlushTarget;
using searchcorespi::FlushStats;
+using namespace std::chrono_literals;
namespace proton {
@@ -71,11 +71,13 @@ FlushEngine::FlushEngine(std::shared_ptr<flushengine::ITlsStatsFactory>
_strategy(strategy),
_priorityStrategy(),
_executor(numThreads, 128 * 1024),
- _monitor(),
+ _lock(),
+ _cond(),
_handlers(),
_flushing(),
+ _setStrategyLock(),
_strategyLock(),
- _strategyMonitor(),
+ _strategyCond(),
_tlsStatsFactory(tlsStatsFactory),
_pendingPrune()
{
@@ -100,10 +102,10 @@ FlushEngine &
FlushEngine::close()
{
{
- MonitorGuard strategyGuard(_strategyMonitor);
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> strategyGuard(_strategyLock);
+ std::lock_guard<std::mutex> guard(_lock);
_closed = true;
- guard.broadcast();
+ _cond.notify_all();
}
_threadPool.Close();
_executor.shutdown();
@@ -120,13 +122,13 @@ FlushEngine::triggerFlush()
void
FlushEngine::kick()
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
LOG(debug, "Kicking flush engine");
- guard.broadcast();
+ _cond.notify_all();
}
bool
-FlushEngine::canFlushMore(const MonitorGuard & guard) const
+FlushEngine::canFlushMore(const std::unique_lock<std::mutex> &guard) const
{
(void) guard;
return _maxConcurrent > _flushing.size();
@@ -135,12 +137,12 @@ FlushEngine::canFlushMore(const MonitorGuard & guard) const
bool
FlushEngine::wait(size_t minimumWaitTimeIfReady)
{
- MonitorGuard guard(_monitor);
+ std::unique_lock<std::mutex> guard(_lock);
if ( (minimumWaitTimeIfReady > 0) && canFlushMore(guard) && _pendingPrune.empty()) {
- guard.wait(minimumWaitTimeIfReady);
+ _cond.wait_for(guard, std::chrono::milliseconds(minimumWaitTimeIfReady));
}
while ( ! canFlushMore(guard) && _pendingPrune.empty()) {
- guard.wait(1000); // broadcast when flush done
+ _cond.wait_for(guard, 1s); // broadcast when flush done
}
return !_closed;
}
@@ -167,6 +169,8 @@ FlushEngine::Run(FastOS_ThreadInterface *thread, void *arg)
}
LOG(debug, "Making another wait(idle=%s, timeMS=%d) last was '%s'", shouldIdle ? "true" : "false", shouldIdle ? _idleIntervalMS : 0, prevFlushName.c_str());
}
+ _executor.sync();
+ prune();
}
bool
@@ -174,7 +178,7 @@ FlushEngine::prune()
{
std::set<IFlushHandler::SP> toPrune;
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
if (_pendingPrune.empty()) {
return false;
}
@@ -187,7 +191,7 @@ FlushEngine::prune()
return true;
}
-bool FlushEngine::isFlushing(const MonitorGuard & guard, const vespalib::string & name) const
+bool FlushEngine::isFlushing(const std::lock_guard<std::mutex> & guard, const vespalib::string & name) const
{
(void) guard;
for(const auto & it : _flushing) {
@@ -203,7 +207,7 @@ FlushEngine::getTargetList(bool includeFlushingTargets) const
{
FlushContext::List ret;
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
for (const auto & it : _handlers) {
IFlushHandler & handler(*it.second);
search::SerialNum serial(handler.getCurrentSerialNumber());
@@ -227,12 +231,12 @@ FlushEngine::getTargetList(bool includeFlushingTargets) const
}
std::pair<FlushContext::List,bool>
-FlushEngine::getSortedTargetList(MonitorGuard &strategyGuard) const
+FlushEngine::getSortedTargetList()
{
- (void) strategyGuard;
FlushContext::List unsortedTargets = getTargetList(false);
- std::pair<FlushContext::List, bool> ret;
flushengine::TlsStatsMap tlsStatsMap(_tlsStatsFactory->create());
+ std::lock_guard<std::mutex> strategyGuard(_strategyLock);
+ std::pair<FlushContext::List, bool> ret;
if (_priorityStrategy) {
ret = std::make_pair(_priorityStrategy->getFlushTargets(unsortedTargets, tlsStatsMap), true);
} else {
@@ -291,14 +295,15 @@ FlushEngine::flushAll(const FlushContext::List &lst)
vespalib::string
FlushEngine::flushNextTarget(const vespalib::string & name)
{
- MonitorGuard strategyGuard(_strategyMonitor);
- std::pair<FlushContext::List,bool> lst = getSortedTargetList(strategyGuard);
+ std::pair<FlushContext::List,bool> lst = getSortedTargetList();
if (lst.second) {
// Everything returned from a priority strategy should be flushed
flushAll(lst.first);
_executor.sync();
+ prune();
+ std::lock_guard<std::mutex> strategyGuard(_strategyLock);
_priorityStrategy.reset();
- strategyGuard.broadcast();
+ _strategyCond.notify_all();
return "";
}
if (lst.first.empty()) {
@@ -340,7 +345,7 @@ FlushEngine::flushDone(const FlushContext &ctx, uint32_t taskId)
{
fastos::TimeStamp duration;
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
duration = fastos::TimeStamp(fastos::ClockSystem::now()) - _flushing[taskId].getStart();
}
if (LOG_WOULD_LOG(event)) {
@@ -351,20 +356,20 @@ FlushEngine::flushDone(const FlushContext &ctx, uint32_t taskId)
stats.getPathElementsToLog());
}
LOG(debug, "FlushEngine::flushDone(taskId='%d') took '%f' secs", taskId, duration.sec());
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
_flushing.erase(taskId);
assert(ctx.getHandler());
if (_handlers.hasHandler(ctx.getHandler())) {
_pendingPrune.insert(ctx.getHandler());
}
- guard.broadcast();
+ _cond.notify_all();
}
IFlushHandler::SP
FlushEngine::putFlushHandler(const DocTypeName &docTypeName,
const IFlushHandler::SP &flushHandler)
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
IFlushHandler::SP result(_handlers.putHandler(docTypeName, flushHandler));
if (result) {
_pendingPrune.erase(result);
@@ -376,14 +381,14 @@ FlushEngine::putFlushHandler(const DocTypeName &docTypeName,
IFlushHandler::SP
FlushEngine::getFlushHandler(const DocTypeName &docTypeName) const
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
return _handlers.getHandler(docTypeName);
}
IFlushHandler::SP
FlushEngine::removeFlushHandler(const DocTypeName &docTypeName)
{
- MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
IFlushHandler::SP result(_handlers.removeHandler(docTypeName));
_pendingPrune.erase(result);
return std::move(result);
@@ -393,7 +398,7 @@ FlushEngine::FlushMetaSet
FlushEngine::getCurrentlyFlushingSet() const
{
FlushMetaSet s;
- vespalib::LockGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
for (const auto & it : _flushing) {
s.insert(it.second);
}
@@ -405,7 +410,7 @@ FlushEngine::initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP
{
uint32_t taskId(0);
{
- vespalib::LockGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
taskId = _taskId++;
vespalib::string name(FlushContext::createName(*handler, *target));
FlushInfo flush(taskId, target, name);
@@ -419,19 +424,19 @@ FlushEngine::initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP
void
FlushEngine::setStrategy(IFlushStrategy::SP strategy)
{
- vespalib::LockGuard strategyLock(_strategyLock);
- MonitorGuard strategyGuard(_strategyMonitor);
+ std::lock_guard<std::mutex> setStrategyGuard(_setStrategyLock);
+ std::unique_lock<std::mutex> strategyGuard(_strategyLock);
if (_closed) {
return;
}
assert(!_priorityStrategy);
_priorityStrategy = strategy;
{
- MonitorGuard guard(_monitor);
- guard.broadcast();
+ std::lock_guard<std::mutex> guard(_lock);
+ _cond.notify_all();
}
while (_priorityStrategy) {
- strategyGuard.wait();
+ _strategyCond.wait(strategyGuard);
}
}
diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h
index 7f4698610c8..19175f9ce2a 100644
--- a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h
+++ b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h
@@ -5,10 +5,11 @@
#include "iflushstrategy.h"
#include <vespa/searchcore/proton/common/handlermap.hpp>
#include <vespa/searchcore/proton/common/doctypename.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/fastos/thread.h>
#include <set>
+#include <mutex>
+#include <condition_variable>
namespace proton {
@@ -53,16 +54,18 @@ private:
IFlushStrategy::SP _strategy;
mutable IFlushStrategy::SP _priorityStrategy;
vespalib::ThreadStackExecutor _executor;
- vespalib::Monitor _monitor;
+ mutable std::mutex _lock;
+ std::condition_variable _cond;
FlushHandlerMap _handlers;
FlushMap _flushing;
- vespalib::Lock _strategyLock; // serialize setStrategy calls
- vespalib::Monitor _strategyMonitor;
+ std::mutex _setStrategyLock; // serialize setStrategy calls
+ std::mutex _strategyLock;
+ std::condition_variable _strategyCond;
std::shared_ptr<flushengine::ITlsStatsFactory> _tlsStatsFactory;
std::set<IFlushHandler::SP> _pendingPrune;
FlushContext::List getTargetList(bool includeFlushingTargets) const;
- std::pair<FlushContext::List,bool> getSortedTargetList(vespalib::MonitorGuard &strategyGuard) const;
+ std::pair<FlushContext::List,bool> getSortedTargetList();
FlushContext::SP initNextFlush(const FlushContext::List &lst);
vespalib::string flushNextTarget(const vespalib::string & name);
void flushAll(const FlushContext::List &lst);
@@ -70,9 +73,9 @@ private:
uint32_t initFlush(const FlushContext &ctx);
uint32_t initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP &target);
void flushDone(const FlushContext &ctx, uint32_t taskId);
- bool canFlushMore(const vespalib::MonitorGuard & guard) const;
+ bool canFlushMore(const std::unique_lock<std::mutex> &guard) const;
bool wait(size_t minimumWaitTimeIfReady);
- bool isFlushing(const vespalib::MonitorGuard & guard, const vespalib::string & name) const;
+ bool isFlushing(const std::lock_guard<std::mutex> &guard, const vespalib::string & name) const;
friend class FlushTask;
friend class FlushEngineExplorer;
diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h b/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h
index 421b176bfec..3a24330f8ec 100644
--- a/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h
+++ b/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h
@@ -65,7 +65,7 @@ public:
/**
* This method is called after a flush has been completed. All transactions
* up to the given serial number can be pruned from the domain of this
- * handler. This method is called by an arbitrary worker thread.
+ * handler. This method is called by the flush scheduler thread.
*
* @param flushedSerial Serial number flushed for all flush
* targets belonging to this handler.
diff --git a/searchcore/src/vespa/searchcore/proton/index/indexmanager.h b/searchcore/src/vespa/searchcore/proton/index/indexmanager.h
index 00dd4befed7..1607c19611a 100644
--- a/searchcore/src/vespa/searchcore/proton/index/indexmanager.h
+++ b/searchcore/src/vespa/searchcore/proton/index/indexmanager.h
@@ -115,6 +115,9 @@ public:
virtual void setSchema(const Schema &schema, SerialNum serialNum) override {
_maintainer.setSchema(schema, serialNum);
}
+ virtual void setMaxFlushed(uint32_t maxFlushed) override {
+ _maintainer.setMaxFlushed(maxFlushed);
+ }
};
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
index adb14b11c71..05dc75146c7 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp
@@ -59,7 +59,7 @@ AttributeLimiter::toString(DiversityCutoffStrategy strategy)
SearchIterator::UP
AttributeLimiter::create_search(size_t want_hits, size_t max_group_size, bool strictSearch)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
const uint32_t my_field_id = 0;
search::fef::MatchDataLayout layout;
auto my_handle = layout.allocTermField(my_field_id);
diff --git a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h
index 67180da6a10..95f7bf4e42b 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h
@@ -4,11 +4,11 @@
#include <vespa/searchlib/queryeval/searchable.h>
#include <vespa/vespalib/stllike/string.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/searchlib/queryeval/searchiterator.h>
#include <vespa/searchlib/queryeval/blueprint.h>
#include <vespa/searchlib/fef/matchdata.h>
+#include <mutex>
namespace proton {
namespace matching {
@@ -43,7 +43,7 @@ private:
vespalib::string _attribute_name;
bool _descending;
vespalib::string _diversity_attribute;
- vespalib::Lock _lock;
+ std::mutex _lock;
std::vector<search::fef::MatchData::UP> _match_datas;
search::queryeval::Blueprint::UP _blueprint;
ssize_t _estimatedHits;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h
index e9e69c35bd4..a0aefd150f5 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h
@@ -4,7 +4,6 @@
#include "isearchcontext.h"
#include <vespa/vespalib/stllike/string.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/searchlib/queryeval/searchiterator.h>
#include <vespa/searchlib/queryeval/blueprint.h>
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h
index bc7a8038974..165762d5356 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h
@@ -7,7 +7,6 @@
#include <vespa/searchlib/queryeval/searchable.h>
#include <vespa/vespalib/stllike/string.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/searchlib/queryeval/searchiterator.h>
#include <vespa/searchlib/queryeval/blueprint.h>
#include <atomic>
diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
index 32775d7619a..bbe6f604e4e 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
@@ -152,7 +152,7 @@ Matcher::Matcher(const search::index::Schema &schema,
MatchingStats
Matcher::getStats()
{
- vespalib::LockGuard guard(_statsLock);
+ std::lock_guard<std::mutex> guard(_statsLock);
MatchingStats stats = std::move(_stats);
_stats = std::move(MatchingStats());
_stats.softDoomFactor(stats.softDoomFactor());
@@ -268,9 +268,9 @@ Matcher::match(const SearchRequest &request,
ResultProcessor::Result::UP result = master.match(params, limitedThreadBundle, *mtf, rp,
_distributionKey, numSearchPartitions);
my_stats = MatchMaster::getStats(std::move(master));
- size_t estimate = std::min(static_cast<size_t>(metaStore.getCommittedDocIdLimit()),
- mtf->match_limiter().getDocIdSpaceEstimate());
+
bool wasLimited = mtf->match_limiter().was_limited();
+ size_t spaceEstimate = mtf->match_limiter().getDocIdSpaceEstimate();
uint32_t estHits = mtf->estimate().estHits;
if (shouldCacheSearchSession && ((result->_numFs4Hits != 0) || shouldCacheGroupingSession)) {
SearchSession::SP session = std::make_shared<SearchSession>(sessionId, request.getTimeOfDoom(),
@@ -281,16 +281,32 @@ Matcher::match(const SearchRequest &request,
reply = std::move(result->_reply);
SearchReply::Coverage & coverage = reply->coverage;
if (wasLimited) {
+ LOG(debug, "was limited, degraded from match phase");
coverage.degradeMatchPhase();
}
if (my_stats.softDoomed()) {
+ LOG(debug, "soft doomed, degraded from timeout");
coverage.degradeTimeout();
}
- coverage.setActive(metaStore.getNumActiveLids());
+ uint32_t numActiveLids = metaStore.getNumActiveLids();
+ // note: this is actually totalSpace+1, since 0 is reserved
+ uint32_t totalSpace = metaStore.getCommittedDocIdLimit();
+ LOG(debug, "docid limit = %d", totalSpace);
+ LOG(debug, "num active lids = %d", numActiveLids);
+ LOG(debug, "space Estimate = %zd", spaceEstimate);
+ if (spaceEstimate >= totalSpace) {
+ // estimate is too high, clamp it
+ spaceEstimate = totalSpace;
+ } else {
+ // account for docid 0 reserved
+ spaceEstimate += 1;
+ }
+ size_t covered = (spaceEstimate * numActiveLids) / totalSpace;
+ LOG(debug, "covered = %zd", covered);
+ coverage.setActive(numActiveLids);
//TODO this should be calculated with ClusterState calculator.
- coverage.setSoonActive(metaStore.getNumActiveLids());
- coverage.setCovered(std::min(static_cast<size_t>(metaStore.getNumActiveLids()),
- (estimate * metaStore.getNumActiveLids())/metaStore.getCommittedDocIdLimit()));
+ coverage.setSoonActive(numActiveLids);
+ coverage.setCovered(covered);
LOG(debug, "numThreadsPerSearch = %zu. Configured = %d, estimated hits=%d, totalHits=%ld",
numThreadsPerSearch, _rankSetup->getNumThreadsPerSearch(), estHits, reply->totalHitCount);
}
@@ -299,7 +315,7 @@ Matcher::match(const SearchRequest &request,
{
fastos::TimeStamp softLimit = uint64_t((1.0 - _rankSetup->getSoftTimeoutTailCost()) * request.getTimeout());
fastos::TimeStamp duration = request.getTimeUsed();
- vespalib::LockGuard guard(_statsLock);
+ std::lock_guard<std::mutex> guard(_statsLock);
_stats.add(my_stats);
if (my_stats.softDoomed()) {
LOG(info, "Triggered softtimeout limit=%1.3f and duration=%1.3f", softLimit.sec(), duration.sec());
diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.h b/searchcore/src/vespa/searchcore/proton/matching/matcher.h
index 42a74e3c29b..8415f659473 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/matcher.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.h
@@ -15,8 +15,8 @@
#include <vespa/searchlib/query/base.h>
#include <vespa/vespalib/util/clock.h>
#include <vespa/vespalib/util/closure.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/util/thread_bundle.h>
+#include <mutex>
namespace search {
namespace grouping {
@@ -52,7 +52,7 @@ private:
search::fef::BlueprintFactory _blueprintFactory;
search::fef::RankSetup::SP _rankSetup;
ViewResolver _viewResolver;
- vespalib::Lock _statsLock;
+ std::mutex _statsLock;
MatchingStats _stats;
const vespalib::Clock &_clock;
QueryLimiter &_queryLimiter;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp
index 0184d3634de..0d985496d41 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp
@@ -1,5 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "querylimiter.h"
+#include <chrono>
namespace proton {
namespace matching {
@@ -18,11 +19,11 @@ QueryLimiter::LimitedToken::~LimitedToken()
void
QueryLimiter::grabToken(const Doom & doom)
{
- vespalib::MonitorGuard guard(_monitor);
+ std::unique_lock<std::mutex> guard(_lock);
while ((_maxThreads > 0) && (_activeThreads >= _maxThreads) && !doom.doom()) {
int left = doom.left().ms();
if (left > 0) {
- guard.wait(left);
+ _cond.wait_for(guard, std::chrono::milliseconds(left));
}
}
_activeThreads++;
@@ -31,13 +32,14 @@ QueryLimiter::grabToken(const Doom & doom)
void
QueryLimiter::releaseToken()
{
- vespalib::MonitorGuard guard(_monitor);
+ std::lock_guard<std::mutex> guard(_lock);
_activeThreads--;
- guard.signal();
+ _cond.notify_one();
}
QueryLimiter::QueryLimiter() :
- _monitor(),
+ _lock(),
+ _cond(),
_activeThreads(0),
_maxThreads(-1),
_coverage(1.0),
diff --git a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h
index e34ae303df2..fbe8526b051 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h
@@ -3,8 +3,9 @@
#pragma once
#include <memory>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/util/doom.h>
+#include <mutex>
+#include <condition_variable>
namespace proton {
namespace matching {
@@ -35,7 +36,8 @@ private:
};
void grabToken(const Doom & doom);
void releaseToken();
- vespalib::Monitor _monitor;
+ std::mutex _lock;
+ std::condition_variable _cond;
volatile int _activeThreads;
// These are updated asynchronously at reconfig.
diff --git a/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp b/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp
index b6c6c798506..afaf61cab5d 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp
@@ -3,7 +3,7 @@
#include "sessionmanager.h"
#include <vespa/vespalib/stllike/lrucache_map.hpp>
#include <vespa/vespalib/stllike/hash_map.hpp>
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
#include <algorithm>
#include <vespa/log/log.h>
@@ -18,7 +18,7 @@ using Stats = SessionManager::Stats;
struct SessionCacheBase {
protected:
Stats _stats;
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
void entryDropped(const SessionId &id);
~SessionCacheBase() {}
@@ -32,7 +32,7 @@ struct SessionCache : SessionCacheBase {
SessionCache(uint32_t max_size) : _cache(max_size) {}
void insert(EntryUP session) {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
const SessionId &id(session->getSessionId());
if (_cache.size() >= _cache.capacity()) {
entryDropped(id);
@@ -41,7 +41,7 @@ struct SessionCache : SessionCacheBase {
_stats.numInsert++;
}
EntryUP pick(const SessionId & id) {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
EntryUP ret;
if (_cache.hasKey(id)) {
_stats.numPick++;
@@ -56,7 +56,7 @@ struct SessionCache : SessionCacheBase {
}
std::vector<EntryUP> stealTimedOutSessions(fastos::TimeStamp currentTime) {
std::vector<EntryUP> toDestruct;
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
toDestruct.reserve(_cache.size());
for (auto it(_cache.begin()), mt(_cache.end()); it != mt;) {
auto &session = *it;
@@ -71,14 +71,14 @@ struct SessionCache : SessionCacheBase {
return toDestruct;
}
Stats getStats() {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
Stats stats = _stats;
stats.numCached = _cache.size();
_stats = Stats();
return stats;
}
bool empty() const {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _cache.empty();
}
};
@@ -89,13 +89,13 @@ struct SessionMap : SessionCacheBase {
vespalib::hash_map<SessionId, EntrySP> _map;
void insert(EntrySP session) {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
const SessionId &id(session->getSessionId());
_map.insert(std::make_pair(id, session));
_stats.numInsert++;
}
EntrySP pick(const SessionId & id) {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
auto it = _map.find(id);
if (it != _map.end()) {
_stats.numPick++;
@@ -110,7 +110,7 @@ struct SessionMap : SessionCacheBase {
std::vector<EntrySP> stealTimedOutSessions(fastos::TimeStamp currentTime) {
std::vector<EntrySP> toDestruct;
std::vector<SessionId> keys;
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
keys.reserve(_map.size());
toDestruct.reserve(_map.size());
for (auto & it : _map) {
@@ -128,23 +128,23 @@ struct SessionMap : SessionCacheBase {
return toDestruct;
}
Stats getStats() {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
Stats stats = _stats;
stats.numCached = _map.size();
_stats = Stats();
return stats;
}
size_t size() const {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _map.size();
}
bool empty() const {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _map.empty();
}
template <typename F>
void each(F f) const {
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
for (const auto &entry: _map) {
f(*entry.second);
}
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h b/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h
index 4dfcbe89d68..67f8e6dcacf 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h
+++ b/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h
@@ -4,7 +4,6 @@
#include "documentdb_tagged_metrics.h"
#include "job_tracker.h"
#include <vespa/searchcorespi/flush/iflushtarget.h>
-#include <vespa/vespalib/util/sync.h>
#include <chrono>
#include <mutex>
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp
index a699cf491e2..d944287243a 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp
+++ b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp
@@ -7,7 +7,9 @@ namespace proton {
ResourceUsageMetrics::ResourceUsageMetrics(metrics::MetricSet *parent)
: MetricSet("resource_usage", "", "Usage metrics for various resources in this search engine", parent),
disk("disk", "", "The relative amount of disk space used on this machine (value in the range [0, 1])", this),
+ diskUtilization("disk_utilization", "", "The relative amount of disk used compared to the disk resource limit", this),
memory("memory", "", "The relative amount of memory used by this process (value in the range [0, 1])", this),
+ memoryUtilization("memory_utilization", "", "The relative amount of memory used compared to the memory resource limit", this),
memoryMappings("memory_mappings", "", "The number of mapped memory areas", this),
openFileDescriptors("open_file_descriptors", "", "The number of open files", this),
feedingBlocked("feeding_blocked", "", "Whether feeding is blocked due to resource limits being reached (value is either 0 or 1)", this)
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h
index 698ba8029bd..497a0fe2ba0 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h
+++ b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h
@@ -12,7 +12,9 @@ namespace proton {
struct ResourceUsageMetrics : metrics::MetricSet
{
metrics::DoubleValueMetric disk;
+ metrics::DoubleValueMetric diskUtilization;
metrics::DoubleValueMetric memory;
+ metrics::DoubleValueMetric memoryUtilization;
metrics::LongValueMetric memoryMappings;
metrics::LongValueMetric openFileDescriptors;
metrics::LongValueMetric feedingBlocked;
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp
index 00d4532a44e..00a7e9b9140 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp
+++ b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp
@@ -12,7 +12,8 @@ TransLogServerMetrics::DomainMetrics::DomainMetrics(metrics::MetricSet *parent,
: metrics::MetricSet("transactionlog", {{"documenttype", documentType}},
"Transaction log metrics for a document type", parent),
entries("entries", "", "The current number of entries in the transaction log", this),
- diskUsage("disk_usage", "", "The disk usage (in bytes) of the transaction log", this)
+ diskUsage("disk_usage", "", "The disk usage (in bytes) of the transaction log", this),
+ replayTime("replay_time", "", "The replay time (in seconds) of the transaction log during start-up", this)
{
}
@@ -23,6 +24,7 @@ TransLogServerMetrics::DomainMetrics::update(const DomainInfo &stats)
{
entries.set(stats.numEntries);
diskUsage.set(stats.byteSize);
+ replayTime.set(stats.maxSessionRunTime.count());
}
void
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h
index c4620f46f4f..830d643e93e 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h
+++ b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h
@@ -17,6 +17,7 @@ public:
{
metrics::LongValueMetric entries;
metrics::LongValueMetric diskUsage;
+ metrics::DoubleValueMetric replayTime;
typedef std::unique_ptr<DomainMetrics> UP;
DomainMetrics(metrics::MetricSet *parent, const vespalib::string &documentType);
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
index 12ba5e7ab29..96025f5eaad 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
@@ -20,7 +20,7 @@ void
TransportLatch::send(ResultUP result, bool documentWasFound)
{
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (!_result) {
_result = std::move(result);
} else if (result->hasError()) {
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
index 10dae553a80..2fb55b14fdf 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
@@ -4,6 +4,8 @@
#include <vespa/persistence/spi/result.h>
#include <vespa/searchcore/proton/common/feedtoken.h>
#include <vespa/vespalib/util/sequence.h>
+#include <vespa/vespalib/util/count_down_latch.h>
+#include <mutex>
namespace proton {
@@ -17,7 +19,7 @@ private:
using UpdateResult = storage::spi::UpdateResult;
using RemoveResult = storage::spi::RemoveResult;
vespalib::CountDownLatch _latch;
- vespalib::Lock _lock;
+ std::mutex _lock;
ResultUP _result;
public:
diff --git a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt
index 508a0208e70..8ee6a4f73ae 100644
--- a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt
+++ b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt
@@ -14,6 +14,7 @@ vespa_add_library(searchcore_server STATIC
disk_mem_usage_forwarder.cpp
docstorevalidator.cpp
document_db_explorer.cpp
+ document_db_flush_config.cpp
document_db_maintenance_config.cpp
document_meta_store_read_guards.cpp
document_scan_iterator.cpp
diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp
index 32b2b6aaba6..d53adf19f76 100644
--- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp
@@ -209,18 +209,11 @@ DiskMemUsageFilter::getConfig() const
return _config;
}
-double
-DiskMemUsageFilter::getMemoryUsedRatio() const
-{
- Guard guard(_lock);
- return getMemoryUsedRatio(guard);
-}
-
-double
-DiskMemUsageFilter::getDiskUsedRatio() const
+DiskMemUsageState
+DiskMemUsageFilter::usageState() const
{
Guard guard(_lock);
- return getDiskUsedRatio(guard);
+ return _dmstate;
}
bool
diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h
index 4906348a54d..982e3bba6fd 100644
--- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h
+++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h
@@ -64,8 +64,7 @@ public:
uint64_t getDiskUsedSize() const;
Config getConfig() const;
const HwInfo &getHwInfo() const { return _hwInfo; }
- double getMemoryUsedRatio() const;
- double getDiskUsedRatio() const;
+ DiskMemUsageState usageState() const;
bool acceptWriteOperation() const override;
State getAcceptState() const override;
void addDiskMemUsageListener(IDiskMemUsageListener *listener) override;
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp
new file mode 100644
index 00000000000..d11b8ee397d
--- /dev/null
+++ b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "document_db_maintenance_config.h"
+
+namespace proton {
+
+DocumentDBFlushConfig::DocumentDBFlushConfig()
+ : DocumentDBFlushConfig(2, 20)
+{
+}
+
+DocumentDBFlushConfig::DocumentDBFlushConfig(uint32_t maxFlushed, uint32_t maxFlushedRetired)
+ : _maxFlushed(maxFlushed),
+ _maxFlushedRetired(maxFlushedRetired)
+{
+}
+
+bool
+DocumentDBFlushConfig::operator==(const DocumentDBFlushConfig &rhs) const
+{
+ return _maxFlushed == rhs._maxFlushed &&
+ _maxFlushedRetired == rhs._maxFlushedRetired;
+}
+
+} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h
new file mode 100644
index 00000000000..316d0716477
--- /dev/null
+++ b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+namespace proton {
+
+/*
+ * Flush config, used to adjust flush targets as needed.
+ */
+class DocumentDBFlushConfig {
+ uint32_t _maxFlushed;
+ uint32_t _maxFlushedRetired;
+
+public:
+ DocumentDBFlushConfig();
+ DocumentDBFlushConfig(uint32_t maxFlushed, uint32_t maxFlushedRetired);
+ bool operator==(const DocumentDBFlushConfig &rhs) const;
+ uint32_t getMaxFlushed() const { return _maxFlushed; }
+ uint32_t getMaxFlushedRetired() const { return _maxFlushedRetired; }
+};
+
+} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp
index cc491230784..848b1f27574 100644
--- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp
@@ -118,7 +118,8 @@ DocumentDBMaintenanceConfig::DocumentDBMaintenanceConfig()
_lidSpaceCompaction(),
_attributeUsageFilterConfig(),
_attributeUsageSampleInterval(60.0),
- _blockableJobConfig()
+ _blockableJobConfig(),
+ _flushConfig()
{
}
@@ -131,7 +132,8 @@ DocumentDBMaintenanceConfig(const DocumentDBPruneRemovedDocumentsConfig &
const DocumentDBLidSpaceCompactionConfig &lidSpaceCompaction,
const AttributeUsageFilterConfig &attributeUsageFilterConfig,
double attributeUsageSampleInterval,
- const BlockableMaintenanceJobConfig &blockableJobConfig)
+ const BlockableMaintenanceJobConfig &blockableJobConfig,
+ const DocumentDBFlushConfig &flushConfig)
: _pruneRemovedDocuments(pruneRemovedDocuments),
_heartBeat(heartBeat),
_sessionCachePruneInterval(groupingSessionPruneInterval),
@@ -139,7 +141,8 @@ DocumentDBMaintenanceConfig(const DocumentDBPruneRemovedDocumentsConfig &
_lidSpaceCompaction(lidSpaceCompaction),
_attributeUsageFilterConfig(attributeUsageFilterConfig),
_attributeUsageSampleInterval(attributeUsageSampleInterval),
- _blockableJobConfig(blockableJobConfig)
+ _blockableJobConfig(blockableJobConfig),
+ _flushConfig(flushConfig)
{
}
@@ -154,7 +157,8 @@ operator==(const DocumentDBMaintenanceConfig &rhs) const
_lidSpaceCompaction == rhs._lidSpaceCompaction &&
_attributeUsageFilterConfig == rhs._attributeUsageFilterConfig &&
_attributeUsageSampleInterval == rhs._attributeUsageSampleInterval &&
- _blockableJobConfig == rhs._blockableJobConfig;
+ _blockableJobConfig == rhs._blockableJobConfig &&
+ _flushConfig == rhs._flushConfig;
}
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h
index 2d4249531ec..acbbc442c7a 100644
--- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h
+++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h
@@ -4,6 +4,7 @@
#include <vespa/vespalib/stllike/string.h>
#include <vespa/searchcore/proton/attribute/attribute_usage_filter_config.h>
#include <vespa/fastos/timestamp.h>
+#include "document_db_flush_config.h"
namespace proton {
@@ -95,6 +96,7 @@ private:
AttributeUsageFilterConfig _attributeUsageFilterConfig;
double _attributeUsageSampleInterval;
BlockableMaintenanceJobConfig _blockableJobConfig;
+ DocumentDBFlushConfig _flushConfig;
public:
DocumentDBMaintenanceConfig();
@@ -106,7 +108,8 @@ public:
const DocumentDBLidSpaceCompactionConfig &lidSpaceCompaction,
const AttributeUsageFilterConfig &attributeUsageFilterConfig,
double attributeUsageSampleInterval,
- const BlockableMaintenanceJobConfig &blockableJobConfig);
+ const BlockableMaintenanceJobConfig &blockableJobConfig,
+ const DocumentDBFlushConfig &flushConfig);
bool
operator==(const DocumentDBMaintenanceConfig &rhs) const;
@@ -133,6 +136,7 @@ public:
const BlockableMaintenanceJobConfig &getBlockableJobConfig() const {
return _blockableJobConfig;
}
+ const DocumentDBFlushConfig &getFlushConfig() const { return _flushConfig; }
};
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp
index 2eee5dff09d..5693e56ccf2 100644
--- a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp
@@ -12,7 +12,8 @@ DocumentSubDbInitializerResult::DocumentSubDbInitializerResult()
_summaryManager(std::make_shared<SummaryManager::SP>()),
_attributeManager(std::make_shared<AttributeManager::SP>()),
_indexManager(std::make_shared<IIndexManager::SP>()),
- _lidReuseDelayerConfig()
+ _lidReuseDelayerConfig(),
+ _flushConfig()
{
}
@@ -23,5 +24,12 @@ setLidReuseDelayerConfig(LidReuseDelayerConfig lidReuseDelayerConfig_in)
_lidReuseDelayerConfig = lidReuseDelayerConfig_in;
}
+void
+DocumentSubDbInitializerResult::
+setFlushConfig(const DocumentDBFlushConfig &flushConfig)
+{
+ _flushConfig = flushConfig;
+}
+
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h
index 67b524332eb..da628c1ab5d 100644
--- a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h
+++ b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h
@@ -7,6 +7,7 @@
#include <vespa/searchcore/proton/documentmetastore/document_meta_store_initializer_result.h>
#include <vespa/searchcorespi/index/iindexmanager.h>
#include <vespa/searchcore/proton/documentmetastore/lid_reuse_delayer_config.h>
+#include "document_db_flush_config.h"
namespace proton {
@@ -24,6 +25,7 @@ private:
std::shared_ptr<searchcorespi::IIndexManager::SP> _indexManager;
using LidReuseDelayerConfig = documentmetastore::LidReuseDelayerConfig;
LidReuseDelayerConfig _lidReuseDelayerConfig;
+ DocumentDBFlushConfig _flushConfig;
public:
DocumentSubDbInitializerResult();
@@ -56,6 +58,8 @@ public:
const LidReuseDelayerConfig &lidReuseDelayerConfig() const {
return _lidReuseDelayerConfig;
}
+ void setFlushConfig(const DocumentDBFlushConfig &flushConfig);
+ const DocumentDBFlushConfig &getFlushConfig() const { return _flushConfig; }
};
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp
index 44f4260f7f8..b83040b04ff 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp
@@ -42,7 +42,8 @@ DocumentDBConfig::ComparisonResult::ComparisonResult()
schemaChanged(false),
maintenanceChanged(false),
storeChanged(false),
- visibilityDelayChanged(false)
+ visibilityDelayChanged(false),
+ flushChanged(false)
{ }
DocumentDBConfig::DocumentDBConfig(
@@ -152,6 +153,7 @@ DocumentDBConfig::compare(const DocumentDBConfig &rhs) const
retval.maintenanceChanged = !equals<DocumentDBMaintenanceConfig>(_maintenance.get(), rhs._maintenance.get());
retval.storeChanged = (_storeConfig != rhs._storeConfig);
retval.visibilityDelayChanged = (_maintenance->getVisibilityDelay() != rhs._maintenance->getVisibilityDelay());
+ retval.flushChanged = !equals<DocumentDBMaintenanceConfig>(_maintenance.get(), rhs._maintenance.get(), [](const auto &l, const auto &r) { return l.getFlushConfig() == r.getFlushConfig(); });
return retval;
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h
index 4250ce61175..cfca754a3d6 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h
+++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h
@@ -46,6 +46,7 @@ public:
bool maintenanceChanged;
bool storeChanged;
bool visibilityDelayChanged;
+ bool flushChanged;
ComparisonResult();
ComparisonResult &setRankProfilesChanged(bool val) { rankProfilesChanged = val; return *this; }
@@ -63,7 +64,20 @@ public:
ComparisonResult &setMaintenanceChanged(bool val) { maintenanceChanged = val; return *this; }
ComparisonResult &setStoreChanged(bool val) { storeChanged = val; return *this; }
- ComparisonResult &setVisibilityDelayChanged(bool val) { visibilityDelayChanged = val; return *this; }
+ ComparisonResult &setVisibilityDelayChanged(bool val) {
+ visibilityDelayChanged = val;
+ if (val) {
+ maintenanceChanged = true;
+ }
+ return *this;
+ }
+ ComparisonResult &setFlushChanged(bool val) {
+ flushChanged = val;
+ if (val) {
+ maintenanceChanged = true;
+ }
+ return *this;
+ }
};
using SP = std::shared_ptr<DocumentDBConfig>;
@@ -117,6 +131,14 @@ private:
}
return rhs != NULL && *lhs == *rhs;
}
+ template <typename T, typename Func>
+ bool equals(const T *lhs, const T *rhs, Func isEqual) const
+ {
+ if (lhs == NULL) {
+ return rhs == NULL;
+ }
+ return rhs != NULL && isEqual(*lhs, *rhs);
+ }
public:
DocumentDBConfig(int64_t generation,
const RankProfilesConfigSP &rankProfiles,
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
index 6aea5234fbe..b9f59c4c411 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
@@ -136,7 +136,10 @@ buildMaintenanceConfig(const BootstrapConfig::SP &bootstrapConfig,
proton.writefilter.sampleinterval,
BlockableMaintenanceJobConfig(
proton.maintenancejobs.resourcelimitfactor,
- proton.maintenancejobs.maxoutstandingmoveops));
+ proton.maintenancejobs.maxoutstandingmoveops),
+ DocumentDBFlushConfig(
+ proton.index.maxflushed,
+ proton.index.maxflushedretired));
}
template<typename T>
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp
index 0cea30385c3..d06319ae7f9 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp
@@ -72,7 +72,7 @@ CachedSelect::SP
DocumentRetrieverBase::parseSelect(const vespalib::string &selection) const
{
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (_selectCache.hasKey(selection))
return _selectCache[selection];
}
@@ -86,7 +86,7 @@ DocumentRetrieverBase::parseSelect(const vespalib::string &selection) const
getAttrMgr(),
_hasFields);
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (_selectCache.hasKey(selection))
return _selectCache[selection];
_selectCache.insert(selection, nselect);
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h
index 3a0752e4c95..53341e0d335 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h
+++ b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h
@@ -7,7 +7,7 @@
#include <vespa/document/fieldvalue/document.h>
#include <vespa/vespalib/stllike/lrucache_map.h>
#include <vespa/searchlib/attribute/iattributemanager.h>
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
namespace proton {
@@ -20,7 +20,7 @@ class DocumentRetrieverBase : public IDocumentRetriever
using SelectCache = vespalib::lrucache_map<vespalib::LruParam<vespalib::string, CachedSelect::SP>>;
mutable SelectCache _selectCache;
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
document::Document::UP _emptyDoc;
const bool _hasFields;
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp
index c766cc89bb3..4c1dd47569b 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp
@@ -309,6 +309,15 @@ DocumentSubDBCollection::close()
}
void
+DocumentSubDBCollection::setBucketStateCalculator(const IBucketStateCalculatorSP &calc)
+{
+ _calc = calc;
+ for (auto subDb : _subDBs) {
+ subDb->setBucketStateCalculator(calc);
+ }
+}
+
+void
DocumentSubDBCollection::tearDownReferences(IDocumentDBReferenceResolver &resolver)
{
for (auto subDb : _subDBs) {
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h
index 7290250c59e..ea07e391f69 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h
+++ b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h
@@ -94,9 +94,7 @@ public:
const HwInfo &hwInfo);
~DocumentSubDBCollection();
- void setBucketStateCalculator(const IBucketStateCalculatorSP &calc) {
- _calc = calc;
- }
+ void setBucketStateCalculator(const IBucketStateCalculatorSP &calc);
void createRetrievers();
void maintenanceSync(MaintenanceController &mc, ICommitable &commit);
diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
index 8091196cc4b..a5523083df7 100644
--- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
@@ -2,7 +2,6 @@
#include "executorthreadingservice.h"
#include <vespa/vespalib/util/executor.h>
-#include <vespa/vespalib/util/sync.h>
using vespalib::ThreadStackExecutorBase;
diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp
index bc1715e3c04..c9b3df5a6ed 100644
--- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp
@@ -11,6 +11,7 @@
#include <vespa/searchcore/proton/bucketdb/ibucketdbhandler.h>
#include <vespa/searchcore/proton/persistenceengine/i_resource_write_filter.h>
#include <vespa/searchcore/proton/persistenceengine/transport_latch.h>
+#include <vespa/searchcore/proton/common/eventlogger.h>
#include <vespa/searchcorespi/index/ithreadingservice.h>
#include <vespa/searchlib/common/gatecallback.h>
#include <vespa/vespalib/util/exceptions.h>
@@ -32,8 +33,6 @@ using vespalib::Executor;
using vespalib::IllegalStateException;
using vespalib::makeLambdaTask;
using vespalib::make_string;
-using vespalib::MonitorGuard;
-using vespalib::LockGuard;
using std::make_unique;
using std::make_shared;
@@ -80,7 +79,7 @@ void
FeedHandler::doHandleOperation(FeedToken token, FeedOperation::UP op)
{
assert(_writeService.master().isCurrentThread());
- LockGuard guard(_feedLock);
+ std::lock_guard<std::mutex> guard(_feedLock);
_feedState->handleOperation(std::move(token), std::move(op));
}
@@ -261,6 +260,7 @@ FeedHandler::performPrune(SerialNum flushedSerial)
tlsPrune(flushedSerial); // throws on error
LOG(debug, "Pruned TLS to token %" PRIu64 ".", flushedSerial);
_owner.onPerformPrune(flushedSerial);
+ EventLogger::transactionLogPruneComplete(_tlsMgr.getDomainName(), flushedSerial);
} catch (const IllegalStateException & e) {
LOG(warning, "FeedHandler::performPrune failed due to '%s'.", e.what());
}
@@ -282,7 +282,7 @@ FeedHandler::getFeedState() const
{
FeedState::SP state;
{
- LockGuard guard(_feedLock);
+ std::lock_guard<std::mutex> guard(_feedLock);
state = _feedState;
}
return state;
@@ -292,13 +292,13 @@ FeedHandler::getFeedState() const
void
FeedHandler::changeFeedState(FeedState::SP newState)
{
- LockGuard guard(_feedLock);
+ std::lock_guard<std::mutex> guard(_feedLock);
changeFeedState(std::move(newState), guard);
}
void
-FeedHandler::changeFeedState(FeedState::SP newState, const LockGuard &)
+FeedHandler::changeFeedState(FeedState::SP newState, const std::lock_guard<std::mutex> &)
{
LOG(debug, "Change feed state from '%s' -> '%s'", _feedState->getName().c_str(), newState->getName().c_str());
_feedState = newState;
@@ -390,8 +390,9 @@ FeedHandler::replayTransactionLog(SerialNum flushedIndexMgrSerial, SerialNum flu
void
FeedHandler::flushDone(SerialNum flushedSerial)
{
- // Called by flush worker thread after performing a flush task
+ // Called by flush scheduler thread after flush worker thread has completed a flush task
_writeService.master().execute(makeLambdaTask([this, flushedSerial]() { performFlushDone(flushedSerial); }));
+ _writeService.master().sync();
}
@@ -588,7 +589,7 @@ void
FeedHandler::syncTls(SerialNum syncTo)
{
{
- LockGuard guard(_syncLock);
+ std::lock_guard<std::mutex> guard(_syncLock);
if (_syncedSerialNum >= syncTo)
return;
}
@@ -597,7 +598,7 @@ FeedHandler::syncTls(SerialNum syncTo)
}
SerialNum syncedTo(_tlsWriter.sync(syncTo));
{
- LockGuard guard(_syncLock);
+ std::lock_guard<std::mutex> guard(_syncLock);
if (_syncedSerialNum < syncedTo)
_syncedSerialNum = syncedTo;
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.h b/searchcore/src/vespa/searchcore/proton/server/feedhandler.h
index 8c28fcdc1ea..171462a6c13 100644
--- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.h
+++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.h
@@ -13,6 +13,7 @@
#include <vespa/searchcore/proton/common/doctypename.h>
#include <vespa/searchcore/proton/common/feedtoken.h>
#include <vespa/searchlib/transactionlog/translogclient.h>
+#include <mutex>
namespace searchcorespi { namespace index { class IThreadingService; } }
@@ -85,12 +86,12 @@ private:
SerialNum _serialNum;
SerialNum _prunedSerialNum;
bool _delayedPrune;
- vespalib::Lock _feedLock;
+ mutable std::mutex _feedLock;
FeedStateSP _feedState;
// used by master write thread tasks
IFeedView *_activeFeedView;
bucketdb::IBucketDBHandler *_bucketDBHandler;
- vespalib::Lock _syncLock;
+ std::mutex _syncLock;
SerialNum _syncedSerialNum;
bool _allowSync; // Sanity check
@@ -129,7 +130,7 @@ private:
FeedStateSP getFeedState() const;
void changeFeedState(FeedStateSP newState);
- void changeFeedState(FeedStateSP newState, const vespalib::LockGuard &feedGuard);
+ void changeFeedState(FeedStateSP newState, const std::lock_guard<std::mutex> &feedGuard);
public:
FeedHandler(const FeedHandler &) = delete;
FeedHandler & operator = (const FeedHandler &) = delete;
diff --git a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp
index c021d786216..d5deb4482c4 100644
--- a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp
@@ -9,13 +9,13 @@
using document::BucketId;
using vespalib::makeClosure;
using vespalib::makeTask;
-using vespalib::MonitorGuard;
namespace proton {
-FrozenBucketsMap::FrozenBucketsMap() :
- _lock(),
- _map()
+FrozenBucketsMap::FrozenBucketsMap()
+ : _lock(),
+ _cond(),
+ _map()
{ }
FrozenBucketsMap::~FrozenBucketsMap() {
@@ -25,12 +25,12 @@ FrozenBucketsMap::~FrozenBucketsMap() {
void
FrozenBucketsMap::freezeBucket(BucketId bucket) {
- MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
std::pair<BucketId, FrozenBucket> tryVal(std::make_pair(bucket, FrozenBucket(FrozenBucket::Reader)));
std::pair<Map::iterator, bool> res;
for (res = _map.insert(tryVal); !res.second && (res.first->second.isExclusive()); res = _map.insert(tryVal)) {
- guard.wait();
+ _cond.wait(guard);
}
if (!res.second) {
@@ -42,7 +42,7 @@ FrozenBucketsMap::freezeBucket(BucketId bucket) {
bool
FrozenBucketsMap::thawBucket(BucketId bucket)
{
- MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
Map::iterator it(_map.find(bucket));
assert(it != _map.end());
assert(it->second.hasReaders());
@@ -52,7 +52,7 @@ FrozenBucketsMap::thawBucket(BucketId bucket)
isLastAndContended = true;
}
_map.erase(it);
- guard.broadcast();
+ _cond.notify_all();
} else {
it->second.removeReader();
}
@@ -63,7 +63,7 @@ FrozenBucketsMap::thawBucket(BucketId bucket)
IFrozenBucketHandler::ExclusiveBucketGuard::UP
FrozenBucketsMap::acquireExclusiveBucket(document::BucketId bucket)
{
- MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
Map::iterator it(_map.find(bucket));
if (it != _map.end()) {
assert(it->second.hasReaders());
@@ -77,11 +77,11 @@ FrozenBucketsMap::acquireExclusiveBucket(document::BucketId bucket)
void
FrozenBucketsMap::releaseExclusiveBucket(document::BucketId bucket)
{
- MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
Map::const_iterator it(_map.find(bucket));
assert ((it != _map.end()) && (it->second.isExclusive()));
_map.erase(it);
- guard.broadcast();
+ _cond.notify_all();
}
FrozenBuckets::FrozenBuckets(IThreadService &masterThread) :
diff --git a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h
index 9f389b89adf..be4d28f1e9e 100644
--- a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h
+++ b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h
@@ -4,7 +4,9 @@
#include "ifrozenbuckethandler.h"
#include "ibucketfreezer.h"
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
+#include <condition_variable>
+#include <cassert>
#include <map>
#include <vector>
@@ -66,8 +68,9 @@ private:
bool _notifyWriter;
};
typedef std::map<document::BucketId, FrozenBucket> Map;
- vespalib::Monitor _lock;
- Map _map;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ Map _map;
};
/**
diff --git a/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h b/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h
index 2251a24c58a..d41c2088518 100644
--- a/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h
+++ b/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h
@@ -25,6 +25,7 @@ class DocumentSubDbInitializerResult;
class FeedHandler;
class FileConfigManager;
class IAttributeManager;
+class IBucketStateCalculator;
class IDcoumentRetriever;
class IDocumentDBReferenceResolver;
class IDocumentDBReference;
@@ -72,6 +73,7 @@ public:
virtual IReprocessingTask::List
applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot,
SerialNum serialNum, const ReconfigParams &params, IDocumentDBReferenceResolver &resolver) = 0;
+ virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) = 0;
virtual std::shared_ptr<ISearchHandler> getSearchView() const = 0;
virtual std::shared_ptr<IFeedView> getFeedView() const = 0;
diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp
index 2ae8d826ebc..fad00fa00e6 100644
--- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp
@@ -6,7 +6,7 @@
#include "lid_space_compaction_job.h"
#include <vespa/searchcore/proton/common/eventlogger.h>
#include <vespa/searchlib/common/gatecallback.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <cassert>
#include <vespa/log/log.h>
diff --git a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp
index a5b0df67b77..35d6973e34b 100644
--- a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp
@@ -20,7 +20,7 @@ shouldUseConservativeMode(const ResourceUsageState &resourceState,
}
void
-MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &,
+MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &guard,
MemoryFlush::Config &newConfig)
{
if (shouldUseConservativeMode(_currState.diskState(), _useConservativeDiskMode,
@@ -30,6 +30,9 @@ MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &,
_useConservativeDiskMode = true;
} else {
_useConservativeDiskMode = false;
+ if (_nodeRetired) {
+ considerUseRelaxedDiskMode(guard, newConfig);
+ }
}
}
@@ -49,6 +52,18 @@ MemoryFlushConfigUpdater::considerUseConservativeMemoryMode(const LockGuard &,
}
void
+MemoryFlushConfigUpdater::considerUseRelaxedDiskMode(const LockGuard &, MemoryFlush::Config &newConfig)
+{
+ double utilization = _currState.diskState().utilization();
+ double bloatMargin = _currConfig.conservative.lowwatermarkfactor - utilization;
+ if (bloatMargin > 0.0) {
+ // Node retired and disk utiliation is below low mater mark factor.
+ newConfig.diskBloatFactor = 1.0;
+ newConfig.globalDiskBloatFactor = std::max(bloatMargin * 0.8, _currConfig.diskbloatfactor);
+ }
+}
+
+void
MemoryFlushConfigUpdater::updateFlushStrategy(const LockGuard &guard)
{
MemoryFlush::Config newConfig = convertConfig(_currConfig, _memory);
@@ -66,7 +81,8 @@ MemoryFlushConfigUpdater::MemoryFlushConfigUpdater(const MemoryFlush::SP &flushS
_memory(memory),
_currState(),
_useConservativeDiskMode(false),
- _useConservativeMemoryMode(false)
+ _useConservativeMemoryMode(false),
+ _nodeRetired(false)
{
}
@@ -86,6 +102,14 @@ MemoryFlushConfigUpdater::notifyDiskMemUsage(DiskMemUsageState newState)
updateFlushStrategy(guard);
}
+void
+MemoryFlushConfigUpdater::setNodeRetired(bool nodeRetired)
+{
+ LockGuard guard(_mutex);
+ _nodeRetired = nodeRetired;
+ updateFlushStrategy(guard);
+}
+
namespace {
size_t
diff --git a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h
index e6d8f7610f8..28ee330689d 100644
--- a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h
+++ b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h
@@ -28,12 +28,15 @@ private:
DiskMemUsageState _currState;
bool _useConservativeDiskMode;
bool _useConservativeMemoryMode;
+ bool _nodeRetired;
void considerUseConservativeDiskMode(const LockGuard &guard,
MemoryFlush::Config &newConfig);
void considerUseConservativeMemoryMode(const LockGuard &guard,
MemoryFlush::Config &newConfig);
+ void considerUseRelaxedDiskMode(const LockGuard &guard,
+ MemoryFlush::Config &newConfig);
void updateFlushStrategy(const LockGuard &guard);
public:
@@ -43,6 +46,7 @@ public:
const ProtonConfig::Flush::Memory &config,
const HwInfo::Memory &memory);
void setConfig(const ProtonConfig::Flush::Memory &newConfig);
+ void setNodeRetired(bool nodeRetired);
virtual void notifyDiskMemUsage(DiskMemUsageState newState) override;
static MemoryFlush::Config convertConfig(const ProtonConfig::Flush::Memory &config,
diff --git a/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp b/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp
index e80f2645fcf..7d97cc4030a 100644
--- a/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp
@@ -90,14 +90,14 @@ MemoryFlush::~MemoryFlush() { }
MemoryFlush::Config
MemoryFlush::getConfig() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _config;
}
void
MemoryFlush::setConfig(const Config &config)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_config = config;
}
@@ -120,13 +120,6 @@ size_t
computeGain(const IFlushTarget::DiskGain & gain) {
return std::max(100000000l, std::max(gain.getBefore(), gain.getAfter()));
}
-bool isDiskBloatToHigh(const IFlushTarget::DiskGain & totalDisk,
- const MemoryFlush::Config & config,
- const IFlushTarget::DiskGain & dgain)
-{
- return (totalDisk.gain() > config.globalDiskBloatFactor * computeGain(totalDisk))
- || (dgain.gain() > config.diskBloatFactor * computeGain(dgain));
-}
}
@@ -167,9 +160,9 @@ MemoryFlush::getFlushTargets(const FlushContext::List &targetList,
order = TLSSIZE;
}
}
- if (((totalMemory >= config.maxGlobalMemory) || (mgain >= config.maxMemoryGain)) && (order < MEMORY)) {
+ if ((mgain >= config.maxMemoryGain) && (order < MEMORY)) {
order = MEMORY;
- } else if (isDiskBloatToHigh(totalDisk, config, dgain) && (order < DISKBLOAT)) {
+ } else if ((dgain.gain() > config.diskBloatFactor * computeGain(dgain)) && (order < DISKBLOAT)) {
order = DISKBLOAT;
} else if ((timeDiff >= config.maxTimeGain) && (order < MAXAGE)) {
order = MAXAGE;
@@ -195,6 +188,14 @@ MemoryFlush::getFlushTargets(const FlushContext::List &targetList,
timeDiff.sec(),
getOrderName(order).c_str());
}
+ if (!targetList.empty()) {
+ if ((totalMemory >= config.maxGlobalMemory) && (order < MEMORY)) {
+ order = MEMORY;
+ }
+ if ((totalDisk.gain() > config.globalDiskBloatFactor * computeGain(totalDisk)) && (order < DISKBLOAT)) {
+ order = DISKBLOAT;
+ }
+ }
FlushContext::List fv(targetList);
std::sort(fv.begin(), fv.end(), CompareTarget(order, tlsStatsMap));
// No desired order and no urgent needs; no flush required at this moment.
diff --git a/searchcore/src/vespa/searchcore/proton/server/memoryflush.h b/searchcore/src/vespa/searchcore/proton/server/memoryflush.h
index 552a406aad9..7e9bba59b25 100644
--- a/searchcore/src/vespa/searchcore/proton/server/memoryflush.h
+++ b/searchcore/src/vespa/searchcore/proton/server/memoryflush.h
@@ -2,7 +2,7 @@
#pragma once
#include <vespa/searchcore/proton/flushengine/iflushstrategy.h>
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
namespace proton {
@@ -39,7 +39,7 @@ public:
private:
/// Needed as flushDone is called in different context from the rest
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
Config _config;
/// The time when the strategy was started.
fastos::TimeStamp _startTime;
diff --git a/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h b/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h
index 118f9601abd..6224b3b693a 100644
--- a/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h
+++ b/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h
@@ -4,7 +4,7 @@
#include "tls_replay_progress.h"
#include <vespa/searchlib/transactionlog/common.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
namespace proton {
/**
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.cpp b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
index 6de5410470f..8ce7e0c79fe 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
@@ -36,7 +36,6 @@ LOG_SETUP(".proton.server.proton");
using document::DocumentTypeRepo;
using vespalib::FileHeader;
using vespalib::IllegalStateException;
-using vespalib::MonitorGuard;
using vespalib::Slime;
using vespalib::slime::ArrayInserter;
using vespalib::slime::Cursor;
@@ -195,7 +194,6 @@ Proton::Proton(const config::ConfigUri & configUri,
_queryLimiter(),
_clock(0.010),
_threadPool(128 * 1024),
- _configGenMonitor(),
_configGen(0),
_distributionKey(-1),
_isInitializing(true),
@@ -704,8 +702,11 @@ Proton::updateMetrics(const vespalib::MonitorGuard &)
ContentProtonMetrics &metrics = _metricsEngine->root();
metrics.transactionLog.update(_tls->getTransLogServer()->getDomainStats());
const DiskMemUsageFilter &usageFilter = _diskMemUsageSampler->writeFilter();
- metrics.resourceUsage.disk.set(usageFilter.getDiskUsedRatio());
- metrics.resourceUsage.memory.set(usageFilter.getMemoryUsedRatio());
+ DiskMemUsageState usageState = usageFilter.usageState();
+ metrics.resourceUsage.disk.set(usageState.diskState().usage());
+ metrics.resourceUsage.diskUtilization.set(usageState.diskState().utilization());
+ metrics.resourceUsage.memory.set(usageState.memoryState().usage());
+ metrics.resourceUsage.memoryUtilization.set(usageState.memoryState().utilization());
metrics.resourceUsage.memoryMappings.set(usageFilter.getMemoryStats().getMappingsCount());
metrics.resourceUsage.openFileDescriptors.set(countOpenFiles());
metrics.resourceUsage.feedingBlocked.set((usageFilter.acceptWriteOperation() ? 0.0 : 1.0));
@@ -774,7 +775,11 @@ Proton::setClusterState(const storage::spi::ClusterState &calc)
// about whether node is supposed to be up or not. Match engine
// needs to know this in order to stop serving queries.
bool nodeUp(calc.nodeUp());
+ bool nodeRetired(calc.nodeRetired());
_matchEngine->setNodeUp(nodeUp);
+ if (_memoryFlushConfigUpdater) {
+ _memoryFlushConfigUpdater->setNodeRetired(nodeRetired);
+ }
}
namespace {
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.h b/searchcore/src/vespa/searchcore/proton/server/proton.h
index 532c6651cfa..8d1026340ca 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.h
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.h
@@ -116,7 +116,6 @@ private:
matching::QueryLimiter _queryLimiter;
vespalib::Clock _clock;
FastOS_ThreadPool _threadPool;
- vespalib::Monitor _configGenMonitor;
int64_t _configGen;
uint32_t _distributionKey;
bool _isInitializing;
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp
index 710b0ad228c..adb52583a58 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp
@@ -24,7 +24,9 @@ ProtonConfigFetcher::ProtonConfigFetcher(const config::ConfigUri & configUri, IP
_owner(owner),
_mutex(),
_dbManagerMap(),
- _threadPool(128 * 1024, 1)
+ _threadPool(128 * 1024, 1),
+ _oldDocumentTypeRepos(),
+ _currentDocumentTypeRepo()
{
}
@@ -100,10 +102,11 @@ ProtonConfigFetcher::reconfigure()
assert(insres.first->second->getGeneration() == generation);
}
}
- auto configSnapshot = std::make_shared<ProtonConfigSnapshot>(std::move(bootstrapConfig), std::move(dbConfigs));
+ auto configSnapshot = std::make_shared<ProtonConfigSnapshot>(bootstrapConfig, std::move(dbConfigs));
LOG(debug, "Reconfiguring proton with gen %" PRId64, generation);
_owner.reconfigure(std::move(configSnapshot));
LOG(debug, "Reconfigured proton with gen %" PRId64, generation);
+ rememberDocumentTypeRepo(bootstrapConfig->getDocumentTypeRepoSP());
}
void
@@ -189,4 +192,22 @@ ProtonConfigFetcher::getDocumentDBConfig(const DocTypeName & docTypeName) const
return it->second->getConfig();
}
+void
+ProtonConfigFetcher::rememberDocumentTypeRepo(std::shared_ptr<document::DocumentTypeRepo> repo)
+{
+ // Ensure that previous document type repo is kept alive, and also
+ // any document type repo that was current within last 10 minutes.
+ using namespace std::chrono_literals;
+ if (repo == _currentDocumentTypeRepo) {
+ return; // no change
+ }
+ auto &repos = _oldDocumentTypeRepos;
+ TimePoint now = Clock::now();
+ while (!repos.empty() && repos.front().first < now) {
+ repos.pop_front();
+ }
+ repos.emplace_back(now + 10min, _currentDocumentTypeRepo);
+ _currentDocumentTypeRepo = repo;
+}
+
} // namespace proton
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h
index 9adbabbc174..fa1f75ebe91 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h
+++ b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h
@@ -9,6 +9,9 @@
#include "bootstrapconfigmanager.h"
#include "documentdbconfigmanager.h"
#include "i_document_db_config_owner.h"
+#include <chrono>
+
+namespace document { class DocumentTypeRepo; }
namespace proton {
@@ -47,6 +50,9 @@ public:
private:
typedef std::map<DocTypeName, DocumentDBConfigManager::SP> DBManagerMap;
+ using Clock = std::chrono::steady_clock;
+ using TimePoint = std::chrono::time_point<Clock>;
+ using OldDocumentTypeRepo = std::pair<TimePoint, std::shared_ptr<document::DocumentTypeRepo>>;
BootstrapConfigManager _bootstrapConfigManager;
config::ConfigRetriever _retriever;
@@ -57,11 +63,14 @@ private:
DBManagerMap _dbManagerMap;
FastOS_ThreadPool _threadPool;
+ std::deque<OldDocumentTypeRepo> _oldDocumentTypeRepos;
+ std::shared_ptr<document::DocumentTypeRepo> _currentDocumentTypeRepo;
void fetchConfigs();
void updateDocumentDBConfigs(const BootstrapConfigSP & config, const config::ConfigSnapshot & snapshot);
void reconfigure();
const config::ConfigKeySet pruneManagerMap(const BootstrapConfigSP & config);
+ void rememberDocumentTypeRepo(std::shared_ptr<document::DocumentTypeRepo> repo);
};
diff --git a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp
index 377b012de99..8ec41ae3e3c 100644
--- a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp
@@ -64,7 +64,7 @@ bool
ReconfigParams::shouldSubDbsChange() const
{
return shouldMatchersChange() || shouldAttributeManagerChange() || shouldSummaryManagerChange()
- || _res.documentTypeRepoChanged || _res.documenttypesChanged || _res.storeChanged;
+ || _res.documentTypeRepoChanged || _res.documenttypesChanged || _res.storeChanged || _res.flushChanged;
}
bool
diff --git a/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp b/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp
index 194e190bb7b..3d770fad313 100644
--- a/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp
@@ -3,6 +3,7 @@
#include "removedonecontext.h"
#include "removedonetask.h"
#include <vespa/searchcore/proton/reference/i_gid_to_lid_change_handler.h>
+#include <cassert>
namespace proton {
diff --git a/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp b/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp
index 33b37649ed2..6f1fd8b90e0 100644
--- a/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp
@@ -33,21 +33,23 @@ void
ResourceUsageExplorer::get_state(const vespalib::slime::Inserter &inserter, bool full) const
{
Cursor &object = inserter.insertObject();
+ DiskMemUsageState usageState = _usageFilter.usageState();
if (full) {
- DiskMemUsageFilter::Config config = _usageFilter.getConfig();
Cursor &disk = object.setObject("disk");
- disk.setDouble("usedRatio", _usageFilter.getDiskUsedRatio());
- disk.setDouble("usedLimit", config._diskLimit);
+ disk.setDouble("usage", usageState.diskState().usage());
+ disk.setDouble("limit", usageState.diskState().limit());
+ disk.setDouble("utilization", usageState.diskState().utilization());
convertDiskStatsToSlime(_usageFilter.getHwInfo(), _usageFilter.getDiskUsedSize(), disk.setObject("stats"));
Cursor &memory = object.setObject("memory");
- memory.setDouble("usedRatio", _usageFilter.getMemoryUsedRatio());
- memory.setDouble("usedLimit", config._memoryLimit);
+ memory.setDouble("usage", usageState.memoryState().usage());
+ memory.setDouble("limit", usageState.memoryState().limit());
+ memory.setDouble("utilization", usageState.memoryState().utilization());
memory.setLong("physicalMemory", _usageFilter.getHwInfo().memory().sizeBytes());
convertMemoryStatsToSlime(_usageFilter.getMemoryStats(), memory.setObject("stats"));
} else {
- object.setDouble("disk", _usageFilter.getDiskUsedRatio());
- object.setDouble("memory", _usageFilter.getMemoryUsedRatio());
+ object.setDouble("disk", usageState.diskState().usage());
+ object.setDouble("memory", usageState.memoryState().usage());
}
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h b/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h
index 7ac127c6b25..29f9959aea9 100644
--- a/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h
+++ b/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h
@@ -5,7 +5,10 @@
namespace proton {
/**
- * Class representing the state of an resource (e.g. disk or memory) with its limit and current usage.
+ * Class representing the state of an resource (e.g. disk or memory) with its limit and current usage:
+ * - usage: How much of this resource is currently used (number between 0 and 1).
+ * - limit: How much of this resource is allowed to use (number between 0 and 1).
+ * - utilization: How much of the allowed part of this resource is used (usage/limit).
*/
class ResourceUsageState
{
@@ -33,6 +36,7 @@ public:
}
double limit() const { return _limit; }
double usage() const { return _usage; }
+ double utilization() const { return _usage/_limit; }
bool aboveLimit() const {
return aboveLimit(1.0);
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp
index 7f6fdf0e06f..913efa469d3 100644
--- a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp
@@ -4,6 +4,7 @@
#include "proton.h"
#include <vespa/vespalib/util/closuretask.h>
#include <vespa/fnet/frt/supervisor.h>
+#include <chrono>
#include <vespa/log/log.h>
LOG_SETUP(".proton.server.rtchooks");
@@ -35,8 +36,8 @@ RPCHooksBase::checkState(StateArg::UP arg)
{
fastos::TimeStamp now(fastos::ClockSystem::now());
if (now < arg->_dueTime) {
- MonitorGuard guard(_stateMonitor);
- if ( guard.wait(std::min(1000L, (arg->_dueTime - now)/fastos::TimeStamp::MS)) ) {
+ std::unique_lock<std::mutex> guard(_stateLock);
+ if (_stateCond.wait_for(guard, std::chrono::milliseconds(std::min(1000L, (arg->_dueTime - now)/fastos::TimeStamp::MS))) == std::cv_status::no_timeout) {
LOG(debug, "state has changed");
reportState(*arg->_session, arg->_req);
arg->_req->Return();
@@ -203,6 +204,8 @@ RPCHooksBase::RPCHooksBase(Params &params)
_docsumByRPC(new DocsumByRPC(_proton.getDocsumBySlime())),
_orb(std::make_unique<FRT_Supervisor>()),
_regAPI(*_orb, params.slobrok_config),
+ _stateLock(),
+ _stateCond(),
_executor(48, 128 * 1024)
{ }
@@ -225,8 +228,8 @@ RPCHooksBase::close()
_orb->ShutDown(true);
_executor.shutdown();
{
- MonitorGuard guard(_stateMonitor);
- guard.broadcast();
+ std::lock_guard<std::mutex> guard(_stateLock);
+ _stateCond.notify_all();
}
_executor.sync();
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h
index ab5980a39fa..994699ac480 100644
--- a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h
+++ b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h
@@ -8,6 +8,8 @@
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/searchlib/common/packets.h>
+#include <mutex>
+#include <condition_variable>
namespace proton {
@@ -61,7 +63,8 @@ private:
std::unique_ptr<DocsumByRPC> _docsumByRPC;
std::unique_ptr<FRT_Supervisor> _orb;
slobrok::api::RegisterAPI _regAPI;
- vespalib::Monitor _stateMonitor;
+ std::mutex _stateLock;
+ std::condition_variable _stateCond;
vespalib::ThreadStackExecutor _executor;
void initRPC();
diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp
index 4069a871210..e05ab4b798f 100644
--- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp
@@ -5,6 +5,7 @@
#include "document_subdb_initializer.h"
#include "reconfig_params.h"
#include "i_document_subdb_owner.h"
+#include "ibucketstatecalculator.h"
#include <vespa/searchcore/proton/attribute/attribute_writer.h>
#include <vespa/searchcore/proton/flushengine/threadedflushtarget.h>
#include <vespa/searchcore/proton/index/index_manager_initializer.h>
@@ -49,7 +50,9 @@ SearchableDocSubDB::SearchableDocSubDB(const Config &cfg, const Context &ctx)
getSubDbName(), ctx._fastUpdCtx._storeOnlyCtx._owner.getDistributionKey()),
_numSearcherThreads(cfg._numSearcherThreads),
_warmupExecutor(ctx._warmupExecutor),
- _realGidToLidChangeHandler(std::make_shared<GidToLidChangeHandler>())
+ _realGidToLidChangeHandler(std::make_shared<GidToLidChangeHandler>()),
+ _flushConfig(),
+ _nodeRetired(false)
{
_gidToLidChangeHandler = _realGidToLidChangeHandler;
}
@@ -136,6 +139,7 @@ SearchableDocSubDB::setup(const DocumentSubDbInitializerResult &initResult)
Parent::setup(initResult);
setupIndexManager(initResult.indexManager());
_docIdLimit.set(_dms->getCommittedDocIdLimit());
+ applyFlushConfig(initResult.getFlushConfig());
}
void
@@ -160,6 +164,7 @@ SearchableDocSubDB::applyConfig(const DocumentDBConfig &newConfigSnapshot, const
StoreOnlyDocSubDB::reconfigure(newConfigSnapshot.getStoreConfig());
IReprocessingTask::List tasks;
updateLidReuseDelayer(&newConfigSnapshot);
+ applyFlushConfig(newConfigSnapshot.getMaintenanceConfigSP()->getFlushConfig());
if (params.shouldMatchersChange() && _addMetrics) {
reconfigureMatchingMetrics(newConfigSnapshot.getRankProfilesConfig());
}
@@ -185,6 +190,27 @@ SearchableDocSubDB::applyConfig(const DocumentDBConfig &newConfigSnapshot, const
}
void
+SearchableDocSubDB::applyFlushConfig(const DocumentDBFlushConfig &flushConfig)
+{
+ _flushConfig = flushConfig;
+ propagateFlushConfig();
+}
+
+void
+SearchableDocSubDB::propagateFlushConfig()
+{
+ uint32_t maxFlushed = _nodeRetired ? _flushConfig.getMaxFlushedRetired() : _flushConfig.getMaxFlushed();
+ _indexMgr->setMaxFlushed(maxFlushed);
+}
+
+void
+SearchableDocSubDB::setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc)
+{
+ _nodeRetired = calc->nodeRetired();
+ propagateFlushConfig();
+}
+
+void
SearchableDocSubDB::initViews(const DocumentDBConfig &configSnapshot, const SessionManager::SP &sessionManager)
{
assert(_writeService.master().isCurrentThread());
diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h
index 6da1a337cda..0852e028c25 100644
--- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h
+++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h
@@ -85,6 +85,8 @@ private:
const size_t _numSearcherThreads;
vespalib::ThreadExecutor &_warmupExecutor;
std::shared_ptr<GidToLidChangeHandler> _realGidToLidChangeHandler;
+ DocumentDBFlushConfig _flushConfig;
+ bool _nodeRetired;
// Note: lifetime of indexManager must be handled by caller.
std::shared_ptr<initializer::InitializerTask>
@@ -100,6 +102,8 @@ private:
bool reconfigure(vespalib::Closure0<bool>::UP closure) override;
void reconfigureIndexSearchable();
void syncViews();
+ void applyFlushConfig(const DocumentDBFlushConfig &flushConfig);
+ void propagateFlushConfig();
protected:
IFlushTargetList getFlushTargetsInternal() override;
@@ -120,6 +124,7 @@ public:
IReprocessingTask::List
applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot,
SerialNum serialNum, const ReconfigParams &params, IDocumentDBReferenceResolver &resolver) override;
+ virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) override;
void clearViews() override;
diff --git a/searchcore/src/vespa/searchcore/proton/server/simpleflush.h b/searchcore/src/vespa/searchcore/proton/server/simpleflush.h
index 71dd629bdd1..c1f6e689eaa 100644
--- a/searchcore/src/vespa/searchcore/proton/server/simpleflush.h
+++ b/searchcore/src/vespa/searchcore/proton/server/simpleflush.h
@@ -2,7 +2,6 @@
#pragma once
#include <vespa/searchcore/proton/flushengine/iflushstrategy.h>
-#include <vespa/vespalib/util/sync.h>
namespace proton {
diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp
index e78f8136d26..c5a8706a0ce 100644
--- a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp
@@ -329,6 +329,7 @@ StoreOnlyDocSubDB::createInitializer(const DocumentDBConfig &configSnapshot, Ser
LidReuseDelayerConfig lidReuseDelayerConfig(configSnapshot);
result->writableResult().setLidReuseDelayerConfig(lidReuseDelayerConfig);
+ result->writableResult().setFlushConfig(configSnapshot.getMaintenanceConfigSP()->getFlushConfig());
return result;
}
@@ -454,6 +455,11 @@ StoreOnlyDocSubDB::reconfigure(const search::LogDocumentStore::Config & config)
_rSummaryMgr->reconfigure(config);
}
+void
+StoreOnlyDocSubDB::setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &)
+{
+}
+
proton::IAttributeManager::SP
StoreOnlyDocSubDB::getAttributeManager() const
{
diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h
index f5038c252a7..d7b242f27ff 100644
--- a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h
+++ b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h
@@ -215,6 +215,7 @@ public:
IReprocessingTask::List
applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot,
SerialNum serialNum, const ReconfigParams &params, IDocumentDBReferenceResolver &resolver) override;
+ virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) override;
ISearchHandler::SP getSearchView() const override { return _iSearchView.get(); }
IFeedView::SP getFeedView() const override { return _iFeedView.get(); }
diff --git a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp
index 95f31f141d7..a271e34b6b7 100644
--- a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp
@@ -16,7 +16,8 @@ TransactionLogManagerBase::TransactionLogManagerBase(
_tlc(tlsSpec),
_tlcSession(),
_domainName(domainName),
- _replayMonitor(),
+ _replayLock(),
+ _replayCond(),
_replayDone(false),
_replayStarted(false),
_replayStartTime(0)
@@ -65,7 +66,7 @@ TransactionLogManagerBase::init()
void
TransactionLogManagerBase::internalStartReplay()
{
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::lock_guard<std::mutex> guard(_replayLock);
_replayStarted = true;
_replayDone = false;
FastOS_Time timer;
@@ -76,23 +77,23 @@ TransactionLogManagerBase::internalStartReplay()
void
TransactionLogManagerBase::markReplayStarted()
{
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::lock_guard<std::mutex> guard(_replayLock);
_replayStarted = true;
}
void TransactionLogManagerBase::changeReplayDone()
{
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::lock_guard<std::mutex> guard(_replayLock);
_replayDone = true;
- guard.broadcast();
+ _replayCond.notify_all();
}
void
TransactionLogManagerBase::waitForReplayDone() const
{
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::unique_lock<std::mutex> guard(_replayLock);
while (_replayStarted && !_replayDone) {
- guard.wait();
+ _replayCond.wait(guard);
}
}
@@ -115,12 +116,12 @@ TransLogClient::Visitor::UP TransactionLogManagerBase::createTlcVisitor(
}
bool TransactionLogManagerBase::getReplayDone() const {
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::lock_guard<std::mutex> guard(_replayLock);
return _replayDone;
}
bool TransactionLogManagerBase::isDoingReplay() const {
- vespalib::MonitorGuard guard(_replayMonitor);
+ std::lock_guard<std::mutex> guard(_replayLock);
return _replayStarted && !_replayDone;
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h
index 1b109d8d9e1..9f4e63842cd 100644
--- a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h
+++ b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h
@@ -3,6 +3,8 @@
#pragma once
#include <vespa/searchlib/transactionlog/translogclient.h>
+#include <mutex>
+#include <condition_variable>
namespace proton {
@@ -14,7 +16,8 @@ class TransactionLogManagerBase {
search::transactionlog::TransLogClient _tlc;
search::transactionlog::TransLogClient::Session::UP _tlcSession;
vespalib::string _domainName;
- vespalib::Monitor _replayMonitor;
+ mutable std::mutex _replayLock;
+ mutable std::condition_variable _replayCond;
volatile bool _replayDone;
bool _replayStarted;
double _replayStartTime;
diff --git a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp
index 430f61eed1d..dd12a5cfd3d 100644
--- a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp
@@ -26,7 +26,7 @@ void VisibilityHandler::commit()
if (_writeService.master().isCurrentThread()) {
performCommit(true);
} else {
- LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
startCommit(guard, true);
}
}
@@ -38,7 +38,7 @@ void VisibilityHandler::commitAndWait()
if (_writeService.master().isCurrentThread()) {
performCommit(false);
} else {
- LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (startCommit(guard, false)) {
_writeService.master().sync();
}
@@ -50,7 +50,7 @@ void VisibilityHandler::commitAndWait()
_writeService.summary().sync();
}
-bool VisibilityHandler::startCommit(const LockGuard &unused, bool force)
+bool VisibilityHandler::startCommit(const std::lock_guard<std::mutex> &unused, bool force)
{
(void) unused;
SerialNum current = _serial.getSerialNum();
diff --git a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h
index 6de6b0721d6..6ba2b640fce 100644
--- a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h
+++ b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h
@@ -7,6 +7,7 @@
#include <vespa/searchcore/proton/server/igetserialnum.h>
#include <vespa/searchcorespi/index/ithreadingservice.h>
#include <vespa/vespalib/util/varholder.h>
+#include <mutex>
namespace proton {
@@ -16,11 +17,9 @@ namespace proton {
**/
class VisibilityHandler : public ICommitable
{
- typedef vespalib::LockGuard LockGuard;
typedef fastos::TimeStamp TimeStamp;
using IThreadingService = searchcorespi::index::IThreadingService;
typedef vespalib::ThreadExecutor ThreadExecutor;
- typedef vespalib::Lock Lock;
typedef vespalib::VarHolder<IFeedView::SP> FeedViewHolder;
public:
typedef search::SerialNum SerialNum;
@@ -32,14 +31,14 @@ public:
void commit() override;
virtual void commitAndWait() override;
private:
- bool startCommit(const LockGuard &unused, bool force);
+ bool startCommit(const std::lock_guard<std::mutex> &unused, bool force);
void performCommit(bool force);
const IGetSerialNum & _serial;
IThreadingService & _writeService;
const FeedViewHolder & _feedView;
TimeStamp _visibilityDelay;
SerialNum _lastCommitSerialNum;
- Lock _lock;
+ std::mutex _lock;
};
}
diff --git a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp
index cda1d0103c7..30e9382ae14 100644
--- a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp
+++ b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp
@@ -51,7 +51,7 @@ SummaryEngine::close()
{
LOG(debug, "Closing summary engine");
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_closed = true;
}
LOG(debug, "Handshaking with task manager");
@@ -61,21 +61,21 @@ SummaryEngine::close()
ISearchHandler::SP
SummaryEngine::putSearchHandler(const DocTypeName &docTypeName, const ISearchHandler::SP & searchHandler)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _handlers.putHandler(docTypeName, searchHandler);
}
ISearchHandler::SP
SummaryEngine::getSearchHandler(const DocTypeName &docTypeName)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _handlers.getHandler(docTypeName);
}
ISearchHandler::SP
SummaryEngine::removeSearchHandler(const DocTypeName &docTypeName)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _handlers.removeHandler(docTypeName);
}
@@ -107,7 +107,7 @@ SummaryEngine::getDocsums(DocsumRequest::UP req)
} else {
vespalib::Sequence<ISearchHandler*>::UP snapshot;
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
snapshot = _handlers.snapshot();
}
if (snapshot->valid()) {
diff --git a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h
index d599f1521ec..2420a656909 100644
--- a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h
+++ b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h
@@ -6,6 +6,7 @@
#include <vespa/searchlib/engine/docsumapi.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/searchcore/proton/common/doctypename.h>
+#include <mutex>
namespace proton {
@@ -16,7 +17,7 @@ private:
using DocsumRequest = search::engine::DocsumRequest;
using DocsumClient = search::engine::DocsumClient;
- vespalib::Lock _lock;
+ std::mutex _lock;
bool _closed;
HandlerMap<ISearchHandler> _handlers;
vespalib::ThreadStackExecutor _executor;
diff --git a/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h b/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h
index 6620795483d..66ae296566f 100644
--- a/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h
+++ b/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h
@@ -55,6 +55,7 @@ struct DummyDocumentSubDb : public IDocumentSubDB
{
return IReprocessingTask::List();
}
+ void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &) override { }
ISearchHandler::SP getSearchView() const override { return ISearchHandler::SP(); }
IFeedView::SP getFeedView() const override { return IFeedView::SP(); }
void clearViews() override {}
diff --git a/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h b/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h
index 9e26b1250ff..9e105ef8a7f 100644
--- a/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h
+++ b/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h
@@ -28,6 +28,7 @@ struct MockIndexManager : public searchcorespi::IIndexManager
}
virtual void setSchema(const Schema &, SerialNum) override {}
virtual void heartBeat(SerialNum) override {}
+ virtual void setMaxFlushed(uint32_t) override { }
};
} // namespace test
diff --git a/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h b/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h
index a80eb7b670e..648ca0aa8ad 100644
--- a/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h
+++ b/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h
@@ -2,7 +2,7 @@
#pragma once
#include <vespa/searchcore/proton/metrics/i_job_tracker.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/count_down_latch.h>
namespace proton {
namespace test {
diff --git a/searchcorespi/src/tests/plugin/plugin.cpp b/searchcorespi/src/tests/plugin/plugin.cpp
index bea732f3db6..5614ea1da3a 100644
--- a/searchcorespi/src/tests/plugin/plugin.cpp
+++ b/searchcorespi/src/tests/plugin/plugin.cpp
@@ -35,6 +35,7 @@ public:
return l;
}
virtual void setSchema(const Schema &, SerialNum) override { }
+ virtual void setMaxFlushed(uint32_t) override { }
};
class IndexManagerFactory : public searchcorespi::IIndexManagerFactory
diff --git a/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h b/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h
index b6538bd1ade..c529fe01dee 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h
+++ b/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h
@@ -157,6 +157,13 @@ public:
* @param schema The new schema to start using.
**/
virtual void setSchema(const Schema &schema, SerialNum serialNum) = 0;
+
+ /*
+ * Sets the max number of flushed indexes before fusion is urgent.
+ *
+ * @param maxFlushed The max number of flushed indexes before fusion is urgent.
+ */
+ virtual void setMaxFlushed(uint32_t maxFlushed) = 0;
};
} // namespace searchcorespi
diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp
index d18ff417074..a18d24931cb 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp
+++ b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp
@@ -1085,9 +1085,9 @@ IndexMaintainer::getFusionStats() const
{
LockGuard lock(_new_search_lock);
source_list = _source_list;
+ stats.maxFlushed = _maxFlushed;
}
stats.diskUsage = source_list->getSearchableStats().sizeOnDisk();
- stats.maxFlushed = _maxFlushed;
{
LockGuard guard(_fusion_lock);
stats.numUnfused = _fusion_spec.flush_ids.size() + ((_fusion_spec.last_fusion_id != 0) ? 1 : 0);
@@ -1228,5 +1228,12 @@ IndexMaintainer::pruneRemovedFields(const Schema &schema, SerialNum serialNum)
}
}
+void
+IndexMaintainer::setMaxFlushed(uint32_t maxFlushed)
+{
+ LockGuard lock(_new_search_lock);
+ _maxFlushed = maxFlushed;
+}
+
} // namespace index
} // namespace searchcorespi
diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h
index 133784f3b57..fb0bc2e5e78 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h
+++ b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h
@@ -383,6 +383,7 @@ public:
IFlushTarget::List getFlushTargets() override;
void setSchema(const Schema & schema, SerialNum serialNum) override ;
+ void setMaxFlushed(uint32_t maxFlushed) override;
};
} // namespace index
diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp
index 3d3231a5e09..c8b2e81a9c0 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp
+++ b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp
@@ -7,6 +7,7 @@
#include <vespa/fastlib/io/bufferedfile.h>
#include <vespa/vespalib/util/exceptions.h>
#include <sstream>
+#include <unistd.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchcorespi.index.indexwriteutilities");
diff --git a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp
index cd0a4caf33b..75412dcf8e9 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp
+++ b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp
@@ -117,7 +117,7 @@ WarmupIndexCollection::fireWarmup(Task::UP task)
if (now < _warmupEndTime) {
_executor.execute(std::move(task));
} else {
- vespalib::LockGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (_warmupEndTime != 0) {
_warmupEndTime = 0;
guard.unlock();
@@ -133,7 +133,7 @@ WarmupIndexCollection::handledBefore(uint32_t fieldId, const Node &term)
const StringBase * sb(dynamic_cast<const StringBase *>(&term));
if (sb != NULL) {
const vespalib::string & s = sb->getTerm();
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
TermMap::insert_result found = (*_handledTerms)[fieldId].insert(s);
return ! found.second;
}
diff --git a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h
index 96f46610a29..f6d6bc89fc4 100644
--- a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h
+++ b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h
@@ -99,7 +99,7 @@ private:
vespalib::ThreadExecutor & _executor;
IWarmupDone & _warmupDone;
fastos::TimeStamp _warmupEndTime;
- vespalib::Lock _lock;
+ std::mutex _lock;
std::unique_ptr<FieldTermMap> _handledTerms;
};
diff --git a/searchlib/pom.xml b/searchlib/pom.xml
index c669903c3da..5f6717d9516 100644
--- a/searchlib/pom.xml
+++ b/searchlib/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>searchlib</artifactId>
<packaging>container-plugin</packaging>
diff --git a/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp b/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp
index 8eef498d85c..5722b7c90ca 100644
--- a/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp
+++ b/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp
@@ -8,11 +8,12 @@
#include <vespa/searchlib/attribute/singlestringattribute.h>
#include <vespa/searchlib/attribute/multistringattribute.h>
#include <vespa/searchlib/attribute/attrvector.h>
+#include <vespa/fastos/thread.h>
#include <vespa/fastos/app.h>
#include <iostream>
#include <fstream>
-#include "../attributesearcher.h"
-#include "../attributeupdater.h"
+#include "attributesearcher.h"
+#include "attributeupdater.h"
#include <sys/resource.h>
#include <vespa/log/log.h>
@@ -21,8 +22,6 @@ LOG_SETUP("attributebenchmark");
#include <vespa/searchlib/attribute/attributevector.hpp>
-using vespalib::Monitor;
-using vespalib::MonitorGuard;
using std::shared_ptr;
typedef std::vector<uint32_t> NumVector;
@@ -268,11 +267,11 @@ AttributeBenchmark::benchmarkSearch(const AttributePtr & ptr, const std::vector<
for (uint32_t i = 0; i < _config._numSearchers; ++i) {
if (_config._rangeSearch) {
RangeSpec spec(_config._rangeStart, _config._rangeEnd, _config._rangeDelta);
- searchers.push_back(new AttributeRangeSearcher(i, ptr, spec, _config._numQueries));
+ searchers.push_back(new AttributeRangeSearcher(ptr, spec, _config._numQueries));
} else if (_config._prefixSearch) {
- searchers.push_back(new AttributePrefixSearcher(i, ptr, prefixStrings, _config._numQueries));
+ searchers.push_back(new AttributePrefixSearcher(ptr, prefixStrings, _config._numQueries));
} else {
- searchers.push_back(new AttributeFindSearcher<T>(i, ptr, values, _config._numQueries));
+ searchers.push_back(new AttributeFindSearcher<T>(ptr, values, _config._numQueries));
}
_threadPool->NewThread(searchers.back());
}
diff --git a/searchlib/src/tests/attribute/attributesearcher.h b/searchlib/src/tests/attribute/benchmark/attributesearcher.h
index bd86f291f7b..f8cd614c48c 100644
--- a/searchlib/src/tests/attribute/attributesearcher.h
+++ b/searchlib/src/tests/attribute/benchmark/attributesearcher.h
@@ -2,7 +2,7 @@
#pragma once
-#include "runnable.h"
+#include <vespa/searchlib/util/runnable.h>
#include <vespa/searchlib/attribute/attribute.h>
#include <vespa/searchlib/attribute/attributeguard.h>
#include <vespa/searchlib/queryeval/hitcollector.h>
@@ -67,8 +67,8 @@ protected:
AttributeSearcherStatus _status;
public:
- AttributeSearcher(uint32_t id, const AttributePtr & attrPtr) :
- Runnable(id), _attrPtr(attrPtr), _timer(), _status()
+ AttributeSearcher(const AttributePtr & attrPtr) :
+ Runnable(), _attrPtr(attrPtr), _timer(), _status()
{
_status._numClients = 1;
}
@@ -108,9 +108,9 @@ private:
std::vector<char> _query;
public:
- AttributeFindSearcher(uint32_t id, const AttributePtr & attrPtr, const std::vector<T> & values,
+ AttributeFindSearcher(const AttributePtr & attrPtr, const std::vector<T> & values,
uint32_t numQueries) :
- AttributeSearcher(id, attrPtr), _values(values), _query()
+ AttributeSearcher(attrPtr), _values(values), _query()
{
_status._numQueries = numQueries;
}
@@ -186,9 +186,9 @@ private:
std::vector<char> _query;
public:
- AttributeRangeSearcher(uint32_t id, const AttributePtr & attrPtr, const RangeSpec & spec,
+ AttributeRangeSearcher(const AttributePtr & attrPtr, const RangeSpec & spec,
uint32_t numQueries) :
- AttributeSearcher(id, attrPtr), _spec(spec), _query()
+ AttributeSearcher(attrPtr), _spec(spec), _query()
{
_status._numQueries = numQueries;
}
@@ -228,9 +228,9 @@ private:
std::vector<char> _query;
public:
- AttributePrefixSearcher(uint32_t id, const AttributePtr & attrPtr,
+ AttributePrefixSearcher(const AttributePtr & attrPtr,
const std::vector<vespalib::string> & values, uint32_t numQueries) :
- AttributeSearcher(id, attrPtr), _values(values), _query()
+ AttributeSearcher(attrPtr), _values(values), _query()
{
_status._numQueries = numQueries;
}
diff --git a/searchlib/src/tests/attribute/attributeupdater.h b/searchlib/src/tests/attribute/benchmark/attributeupdater.h
index 3f41b79f331..13360e58b2d 100644
--- a/searchlib/src/tests/attribute/attributeupdater.h
+++ b/searchlib/src/tests/attribute/benchmark/attributeupdater.h
@@ -3,7 +3,7 @@
#pragma once
#include <vespa/searchlib/util/randomgenerator.h>
-#include "runnable.h"
+#include <vespa/searchlib/util/runnable.h>
#include <vespa/searchlib/attribute/attribute.h>
#define VALIDATOR_STR(str) #str
@@ -169,7 +169,7 @@ AttributeUpdaterThread<Vector, T, BT>::AttributeUpdaterThread(const AttributePtr
RandomGenerator & rndGen, bool validate, uint32_t commitFreq,
uint32_t minValueCount, uint32_t maxValueCount)
: AttributeUpdater<Vector, T, BT>(attrPtr, values, rndGen, validate, commitFreq, minValueCount, maxValueCount),
- Runnable(0)
+ Runnable()
{}
template <typename Vector, typename T, typename BT>
AttributeUpdaterThread<Vector, T, BT>::~AttributeUpdaterThread() { }
diff --git a/searchlib/src/tests/attribute/runnable.h b/searchlib/src/tests/attribute/runnable.h
deleted file mode 100644
index b95d7d3843f..00000000000
--- a/searchlib/src/tests/attribute/runnable.h
+++ /dev/null
@@ -1,43 +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/util/sync.h>
-#include <vespa/fastos/thread.h>
-
-namespace search {
-
-class Runnable : public FastOS_Runnable
-{
-protected:
- uint32_t _id;
- vespalib::Monitor _cond;
- bool _done;
- bool _stopped;
-
-public:
- Runnable(uint32_t id) :
- _id(id), _cond(), _done(false), _stopped(false)
- { }
- void Run(FastOS_ThreadInterface *, void *) override {
- doRun();
-
- vespalib::MonitorGuard guard(_cond);
- _stopped = true;
- guard.broadcast();
- }
- virtual void doRun() = 0;
- void stop() {
- vespalib::MonitorGuard guard(_cond);
- _done = true;
- }
- void join() {
- vespalib::MonitorGuard guard(_cond);
- while (!_stopped) {
- guard.wait();
- }
- }
-};
-
-} // search
-
diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
index 8fc47725a1d..d041dde52a5 100644
--- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
+++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
@@ -363,10 +363,10 @@ Fixture::testEmptyTensor()
Tensor::UP emptyTensor = tensorAttr.getEmptyTensor();
if (_denseTensors) {
vespalib::string expSpec = expEmptyDenseTensorSpec();
- EXPECT_EQUAL(emptyTensor->getType(), ValueType::from_spec(expSpec));
+ EXPECT_EQUAL(emptyTensor->type(), ValueType::from_spec(expSpec));
} else {
- EXPECT_EQUAL(emptyTensor->getType(), tensorAttr.getConfig().tensorType());
- EXPECT_EQUAL(emptyTensor->getType(), ValueType::from_spec(_typeSpec));
+ EXPECT_EQUAL(emptyTensor->type(), tensorAttr.getConfig().tensorType());
+ EXPECT_EQUAL(emptyTensor->type(), ValueType::from_spec(_typeSpec));
}
}
diff --git a/searchlib/src/tests/features/constant/constant_test.cpp b/searchlib/src/tests/features/constant/constant_test.cpp
index 4a88fde58ce..f9d1736ba9e 100644
--- a/searchlib/src/tests/features/constant/constant_test.cpp
+++ b/searchlib/src/tests/features/constant/constant_test.cpp
@@ -76,7 +76,7 @@ struct ExecFixture
const TensorDimensions &dimensions)
{
Tensor::UP tensor = createTensor(cells, dimensions);
- ValueType type(tensor->getType());
+ ValueType type(tensor->type());
test.getIndexEnv().addConstantValue(name,
std::move(type),
std::move(tensor));
diff --git a/searchlib/src/tests/transactionlog/translogclient_test.cpp b/searchlib/src/tests/transactionlog/translogclient_test.cpp
index 9f83db9b23a..861023b79b7 100644
--- a/searchlib/src/tests/transactionlog/translogclient_test.cpp
+++ b/searchlib/src/tests/transactionlog/translogclient_test.cpp
@@ -43,12 +43,12 @@ private:
void checkFilledDomainTest(const TransLogClient::Session::UP &s1, size_t numEntries);
bool visitDomainTest(TransLogClient & tls, TransLogClient::Session * s1, const vespalib::string & name);
bool partialUpdateTest();
- bool test1();
+ bool testVisitOverGeneratedDomain();
bool testRemove();
void createAndFillDomain(const vespalib::string & name, DomainPart::Crc crcMethod, size_t preExistingDomains);
void verifyDomain(const vespalib::string & name);
void testCrcVersions();
- bool test2();
+ bool testVisitOverPreExistingDomain();
void testMany();
void testErase();
void testSync();
@@ -480,7 +480,13 @@ bool Test::visitDomainTest(TransLogClient & tls, TransLogClient::Session * s1, c
return retval;
}
-bool Test::test1()
+double
+getMaxSessionRunTime(TransLogServer &tls, const vespalib::string &domain)
+{
+ return tls.getDomainStats()[domain].maxSessionRunTime.count();
+}
+
+bool Test::testVisitOverGeneratedDomain()
{
DummyFileHeaderContext fileHeaderContext;
TransLogServer tlss("test7", 18377, ".", fileHeaderContext, 0x10000);
@@ -490,7 +496,11 @@ bool Test::test1()
createDomainTest(tls, name);
TransLogClient::Session::UP s1 = openDomainTest(tls, name);
fillDomainTest(s1.get(), name);
+ EXPECT_EQUAL(0, getMaxSessionRunTime(tlss, "test1"));
visitDomainTest(tls, s1.get(), name);
+ double maxSessionRunTime = getMaxSessionRunTime(tlss, "test1");
+ LOG(info, "testVisitOverGeneratedDomain(): maxSessionRunTime=%f", maxSessionRunTime);
+ EXPECT_GREATER(maxSessionRunTime, 0);
return true;
}
@@ -539,8 +549,9 @@ bool Test::testRemove()
return true;
}
-bool Test::test2()
+bool Test::testVisitOverPreExistingDomain()
{
+ // Depends on Test::testVisitOverGeneratedDomain()
DummyFileHeaderContext fileHeaderContext;
TransLogServer tlss("test7", 18377, ".", fileHeaderContext, 0x10000);
TransLogClient tls("tcp/localhost:18377");
@@ -868,8 +879,8 @@ int Test::Main()
if (_argc > 0) {
DummyFileHeaderContext::setCreator(_argv[0]);
}
- test1();
- test2();
+ testVisitOverGeneratedDomain();
+ testVisitOverPreExistingDomain();
testMany();
testErase();
partialUpdateTest();
diff --git a/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp b/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp
index 770d5c653a3..0a3fe963aae 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp
@@ -43,19 +43,19 @@ AttributeContext::~AttributeContext() { }
const IAttributeVector *
AttributeContext::getAttribute(const string & name) const
{
- vespalib::LockGuard guard(_cacheLock);
+ std::lock_guard<std::mutex> guard(_cacheLock);
return getAttribute(_attributes, name, false);
}
const IAttributeVector *
AttributeContext::getAttributeStableEnum(const string & name) const
{
- vespalib::LockGuard guard(_cacheLock);
+ std::lock_guard<std::mutex> guard(_cacheLock);
return getAttribute(_enumAttributes, name, true);
}
void AttributeContext::releaseEnumGuards() {
- vespalib::LockGuard guard(_cacheLock);
+ std::lock_guard<std::mutex> guard(_cacheLock);
_enumAttributes.clear();
}
diff --git a/searchlib/src/vespa/searchlib/attribute/attributecontext.h b/searchlib/src/vespa/searchlib/attribute/attributecontext.h
index b7373f343ec..80abe84f8ef 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributecontext.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributecontext.h
@@ -3,9 +3,9 @@
#pragma once
#include <vespa/searchcommon/attribute/iattributecontext.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/stllike/hash_map.h>
#include "iattributemanager.h"
+#include <mutex>
namespace search {
@@ -21,7 +21,7 @@ private:
const search::IAttributeManager & _manager;
mutable AttributeMap _attributes;
mutable AttributeMap _enumAttributes;
- mutable vespalib::Lock _cacheLock;
+ mutable std::mutex _cacheLock;
const attribute::IAttributeVector *
getAttribute(AttributeMap & map, const string & name, bool stableEnum) const;
diff --git a/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp b/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp
index 0122516b767..f10f3491ac0 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp
@@ -12,14 +12,14 @@
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.attributemanager");
-using vespalib::LockGuard;
using vespalib::string;
using vespalib::IllegalStateException;
using search::attribute::IAttributeContext;
namespace {
-vespalib::Monitor baseDirMonitor;
+std::mutex baseDirLock;
+std::condition_variable baseDirCond;
typedef std::set<string> BaseDirSet;
BaseDirSet baseDirSet;
@@ -27,7 +27,7 @@ static void
waitBaseDir(const string &baseDir)
{
if (baseDir.empty()) { return; }
- vespalib::MonitorGuard guard(baseDirMonitor);
+ std::unique_lock<std::mutex> guard(baseDirLock);
bool waited = false;
BaseDirSet::iterator it = baseDirSet.find(baseDir);
@@ -36,7 +36,7 @@ waitBaseDir(const string &baseDir)
waited = true;
LOG(debug, "AttributeManager: Waiting for basedir %s to be available", baseDir.c_str());
}
- guard.wait();
+ baseDirCond.wait(guard);
it = baseDirSet.find(baseDir);
}
@@ -52,7 +52,7 @@ dropBaseDir(const string &baseDir)
{
if (baseDir.empty())
return;
- vespalib::MonitorGuard guard(baseDirMonitor);
+ std::lock_guard<std::mutex> guard(baseDirLock);
BaseDirSet::iterator it = baseDirSet.find(baseDir);
if (it == baseDirSet.end()) {
@@ -60,7 +60,7 @@ dropBaseDir(const string &baseDir)
} else {
baseDirSet.erase(it);
}
- guard.broadcast();
+ baseDirCond.notify_all();
}
}
@@ -147,7 +147,7 @@ AttributeManager::findAndLoadAttribute(const string & name) const
if (found != _attributes.end()) {
AttributeVector & vec = *found->second;
if ( ! vec.isLoaded() ) {
- vespalib::LockGuard loadGuard(_loadLock);
+ std::lock_guard<std::mutex> loadGuard(_loadLock);
if ( ! vec.isLoaded() ) {
vec.load();
} else {
diff --git a/searchlib/src/vespa/searchlib/attribute/attributemanager.h b/searchlib/src/vespa/searchlib/attribute/attributemanager.h
index f69d4cb9295..3b23881e3bc 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributemanager.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributemanager.h
@@ -7,7 +7,7 @@
#include <vespa/searchlib/common/indexmetainfo.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/vespalib/stllike/hash_map.h>
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
namespace search {
@@ -56,7 +56,7 @@ public:
protected:
typedef vespalib::hash_map<string, VectorHolder> AttributeMap;
AttributeMap _attributes;
- vespalib::Lock _loadLock;
+ mutable std::mutex _loadLock;
private:
const VectorHolder * findAndLoadAttribute(const string & name) const;
string createBaseFileName(const string & name, bool useSnapshot) const;
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.h b/searchlib/src/vespa/searchlib/attribute/attributevector.h
index 13ba3f801b7..b514a5830ba 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.h
@@ -19,8 +19,7 @@
#include <vespa/searchlib/queryeval/searchiterator.h>
#include <vespa/vespalib/objects/identifiable.h>
#include <vespa/vespalib/stllike/asciistream.h>
-#include <vespa/vespalib/util/rwlock.h>
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/fastos/time.h>
#include <cmath>
#include <mutex>
#include <shared_mutex>
diff --git a/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp b/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp
index 04909da8ea7..a063ed347a4 100644
--- a/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp
+++ b/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp
@@ -29,7 +29,7 @@ BitVectorCache::computeCountVector(KeySet & keys, CountVector & v) const
std::vector<CondensedBitVector::KeySet> keySets;
ChunkV chunks;
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
keySets.resize(_chunks.size());
Key2Index::const_iterator end(_keys.end());
for (Key k : keys) {
@@ -61,7 +61,7 @@ BitVectorCache::KeySet
BitVectorCache::lookupCachedSet(const KeyAndCountSet & keys)
{
KeySet cached(keys.size()*3);
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_lookupCount++;
if (_lookupCount == 2000) {
_needPopulation = true;
@@ -101,7 +101,7 @@ BitVectorCache::getSorted(Key2Index & keys)
}
bool
-BitVectorCache::hasCostChanged(const vespalib::LockGuard & guard)
+BitVectorCache::hasCostChanged(const std::lock_guard<std::mutex> & guard)
{
(void) guard;
if ( ! _chunks.empty()) {
@@ -163,17 +163,17 @@ BitVectorCache::populate(Key2Index & newKeys, CondensedBitVector & chunk, const
void
BitVectorCache::populate(uint32_t sz, const PopulateInterface & lookup)
{
- vespalib::LockGuard guard1(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (! _needPopulation) {
return;
}
Key2Index newKeys(_keys);
- guard1.unlock();
+ guard.unlock();
CondensedBitVector::UP chunk(CondensedBitVector::create(sz, _genHolder));
populate(newKeys, *chunk, lookup);
- vespalib::LockGuard guard2(_lock);
+ guard.lock();
_chunks.push_back(std::move(chunk));
_keys.swap(newKeys);
_needPopulation = false;
@@ -182,7 +182,7 @@ BitVectorCache::populate(uint32_t sz, const PopulateInterface & lookup)
void
BitVectorCache::set(Key key, uint32_t index, bool v)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
auto found = _keys.find(key);
if (found != _keys.end()) {
const KeyMeta & m(found->second);
@@ -202,7 +202,7 @@ BitVectorCache::get(Key key, uint32_t index) const
void
BitVectorCache::removeIndex(uint32_t index)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
for (auto & chunk : _chunks) {
chunk->clearIndex(index);
}
diff --git a/searchlib/src/vespa/searchlib/common/bitvectorcache.h b/searchlib/src/vespa/searchlib/common/bitvectorcache.h
index 0d27701e1e1..c1415d9130f 100644
--- a/searchlib/src/vespa/searchlib/common/bitvectorcache.h
+++ b/searchlib/src/vespa/searchlib/common/bitvectorcache.h
@@ -2,9 +2,9 @@
#pragma once
#include "condensedbitvectors.h"
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/stllike/hash_set.h>
#include <vespa/fastos/dynamiclibrary.h>
+#include <mutex>
namespace search {
@@ -74,11 +74,11 @@ private:
VESPA_DLL_LOCAL static SortedKeyMeta getSorted(Key2Index & keys);
VESPA_DLL_LOCAL static void populate(Key2Index & newKeys, CondensedBitVector & chunk, const PopulateInterface & lookup);
- VESPA_DLL_LOCAL bool hasCostChanged(const vespalib::LockGuard &);
+ VESPA_DLL_LOCAL bool hasCostChanged(const std::lock_guard<std::mutex> &);
uint64_t _lookupCount;
bool _needPopulation;
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
Key2Index _keys;
ChunkV _chunks;
GenerationHolder &_genHolder;
diff --git a/searchlib/src/vespa/searchlib/common/gatecallback.cpp b/searchlib/src/vespa/searchlib/common/gatecallback.cpp
index a853909be71..29346d7ad9c 100644
--- a/searchlib/src/vespa/searchlib/common/gatecallback.cpp
+++ b/searchlib/src/vespa/searchlib/common/gatecallback.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 "gatecallback.h"
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
namespace search {
diff --git a/searchlib/src/vespa/searchlib/common/sortspec.cpp b/searchlib/src/vespa/searchlib/common/sortspec.cpp
index f43596f9c7f..694443b00ba 100644
--- a/searchlib/src/vespa/searchlib/common/sortspec.cpp
+++ b/searchlib/src/vespa/searchlib/common/sortspec.cpp
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "sortspec.h"
#include <vespa/vespalib/util/stringfmt.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/fastlib/text/normwordfolder.h>
#include <vespa/vespalib/text/utf8.h>
diff --git a/searchlib/src/vespa/searchlib/features/queryfeature.cpp b/searchlib/src/vespa/searchlib/features/queryfeature.cpp
index 271861d472c..3e52546e841 100644
--- a/searchlib/src/vespa/searchlib/features/queryfeature.cpp
+++ b/searchlib/src/vespa/searchlib/features/queryfeature.cpp
@@ -122,7 +122,7 @@ createTensorExecutor(const search::fef::IQueryEnvironment &env,
const vespalib::string &value = prop.get();
vespalib::nbostream stream(value.data(), value.size());
vespalib::tensor::Tensor::UP tensor = vespalib::tensor::TypedBinaryFormat::deserialize(stream);
- if (tensor->getType() != valueType) {
+ if (tensor->type() != valueType) {
vespalib::tensor::TensorMapper mapper(valueType);
vespalib::tensor::Tensor::UP mappedTensor = mapper.map(*tensor);
tensor = std::move(mappedTensor);
diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
index 74ca3e6527d..03785258f28 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
+++ b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
@@ -36,6 +36,7 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & comm
_lock(),
_sessionLock(),
_sessions(),
+ _maxSessionRunTime(),
_baseDir(baseDir),
_fileHeaderContext(fileHeaderContext),
_markedDeleted(false)
@@ -105,7 +106,7 @@ DomainInfo
Domain::getDomainInfo() const
{
LockGuard guard(_lock);
- DomainInfo info(SerialNumRange(begin(guard), end(guard)), size(guard), byteSize(guard));
+ DomainInfo info(SerialNumRange(begin(guard), end(guard)), size(guard), byteSize(guard), _maxSessionRunTime);
for (const auto &entry: _parts) {
const DomainPart &part = *entry.second;
info.parts.emplace_back(PartInfo(part.range(), part.size(), part.byteSize(), part.fileName()));
@@ -332,6 +333,7 @@ int Domain::startSession(int sessionId)
LockGuard guard(_sessionLock);
SessionList::iterator found = _sessions.find(sessionId);
if (found != _sessions.end()) {
+ found->second->setStartTime(std::chrono::steady_clock::now());
if ( execute(Session::createTask(found->second)).get() == nullptr ) {
retval = 0;
} else {
@@ -345,10 +347,12 @@ int Domain::closeSession(int sessionId)
{
_commitExecutor.sync();
int retval(-1);
+ DurationSeconds sessionRunTime(0);
{
LockGuard guard(_sessionLock);
SessionList::iterator found = _sessions.find(sessionId);
if (found != _sessions.end()) {
+ sessionRunTime = (std::chrono::steady_clock::now() - found->second->getStartTime());
retval = 1;
_sessionExecutor.sync();
}
@@ -364,6 +368,12 @@ int Domain::closeSession(int sessionId)
retval = 0;
}
}
+ {
+ LockGuard guard(_lock);
+ if (sessionRunTime > _maxSessionRunTime) {
+ _maxSessionRunTime = sessionRunTime;
+ }
+ }
return retval;
}
diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.h b/searchlib/src/vespa/searchlib/transactionlog/domain.h
index ab7ded91e5b..c1ff9157a6f 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/domain.h
+++ b/searchlib/src/vespa/searchlib/transactionlog/domain.h
@@ -4,6 +4,7 @@
#include "domainpart.h"
#include "session.h"
#include <vespa/vespalib/util/threadexecutor.h>
+#include <chrono>
namespace search::transactionlog {
@@ -20,14 +21,16 @@ struct PartInfo {
};
struct DomainInfo {
+ using DurationSeconds = std::chrono::duration<double>;
SerialNumRange range;
size_t numEntries;
size_t byteSize;
+ DurationSeconds maxSessionRunTime;
std::vector<PartInfo> parts;
- DomainInfo(SerialNumRange range_in, size_t numEntries_in, size_t byteSize_in)
- : range(range_in), numEntries(numEntries_in), byteSize(byteSize_in), parts() {}
+ DomainInfo(SerialNumRange range_in, size_t numEntries_in, size_t byteSize_in, DurationSeconds maxSessionRunTime_in)
+ : range(range_in), numEntries(numEntries_in), byteSize(byteSize_in), maxSessionRunTime(maxSessionRunTime_in), parts() {}
DomainInfo()
- : range(), numEntries(0), byteSize(0), parts() {}
+ : range(), numEntries(0), byteSize(0), maxSessionRunTime(), parts() {}
};
typedef std::map<vespalib::string, DomainInfo> DomainStats;
@@ -74,6 +77,7 @@ public:
return _sessionExecutor.execute(std::move(task));
}
uint64_t size() const;
+
private:
SerialNum begin(const vespalib::LockGuard & guard) const;
SerialNum end(const vespalib::LockGuard & guard) const;
@@ -89,6 +93,7 @@ private:
using SessionList = std::map<int, Session::SP>;
using DomainPartList = std::map<int64_t, DomainPart::SP>;
+ using DurationSeconds = std::chrono::duration<double>;
DomainPart::Crc _defaultCrcType;
Executor & _commitExecutor;
@@ -102,6 +107,7 @@ private:
vespalib::Lock _lock;
vespalib::Lock _sessionLock;
SessionList _sessions;
+ DurationSeconds _maxSessionRunTime;
vespalib::string _baseDir;
const common::FileHeaderContext &_fileHeaderContext;
bool _markedDeleted;
diff --git a/searchlib/src/vespa/searchlib/transactionlog/session.cpp b/searchlib/src/vespa/searchlib/transactionlog/session.cpp
index 3ea656be9a2..6f0ab998e26 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/session.cpp
+++ b/searchlib/src/vespa/searchlib/transactionlog/session.cpp
@@ -197,7 +197,9 @@ Session::Session(int sId, const SerialNumRange & r, const Domain::SP & d,
_inSync(false),
_ok(true),
_finished(false),
- _packetQ()
+ _packetQ(),
+ _startTime(),
+ _lock()
{
_connection->AddRef();
}
diff --git a/searchlib/src/vespa/searchlib/transactionlog/session.h b/searchlib/src/vespa/searchlib/transactionlog/session.h
index ac6f496e151..c42d6839dfa 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/session.h
+++ b/searchlib/src/vespa/searchlib/transactionlog/session.h
@@ -5,6 +5,7 @@
#include <vespa/vespalib/util/executor.h>
#include <vespa/vespalib/util/sync.h>
#include <vespa/fnet/frt/invoker.h>
+#include <chrono>
#include <deque>
class FastOS_FileInterface;
@@ -18,7 +19,8 @@ using DomainSP = std::shared_ptr<Domain>;
class Session : public FRT_IRequestWait
{
private:
- typedef vespalib::Executor::Task Task;
+ using Task = vespalib::Executor::Task;
+ using time_point = std::chrono::time_point<std::chrono::steady_clock>;
public:
typedef std::shared_ptr<Session> SP;
@@ -33,6 +35,8 @@ public:
bool finished() const;
static void enQ(const SP & session, SerialNum serial, const Packet & packet);
static Task::UP createTask(const Session::SP & session);
+ void setStartTime(time_point startTime) { _startTime = startTime; }
+ time_point getStartTime() const { return _startTime; }
private:
struct QPacket {
QPacket() : _serial(0), _packet() {}
@@ -79,6 +83,7 @@ private:
bool _ok;
bool _finished;
std::deque<QPacket> _packetQ;
+ time_point _startTime;
vespalib::Lock _lock;
};
diff --git a/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp b/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp
index db16d165cec..47d66a94d9e 100644
--- a/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp
+++ b/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp
@@ -2,8 +2,8 @@
#include "ucaconverter.h"
#include <vespa/vespalib/util/stringfmt.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/text/utf8.h>
+#include <mutex>
#include <vespa/log/log.h>
LOG_SETUP(".search.common.sortspec");
@@ -14,7 +14,7 @@ using vespalib::ConstBufferRef;
using vespalib::make_string;
namespace {
- vespalib::Lock _GlobalDirtyICUThreadSafeLock;
+std::mutex _GlobalDirtyICUThreadSafeLock;
}
BlobConverter::UP
@@ -30,7 +30,7 @@ UcaConverter::UcaConverter(vespalib::stringref locale, vespalib::stringref stren
UErrorCode status = U_ZERO_ERROR;
Collator *coll(NULL);
{
- vespalib::LockGuard guard(_GlobalDirtyICUThreadSafeLock);
+ std::lock_guard<std::mutex> guard(_GlobalDirtyICUThreadSafeLock);
coll = Collator::createInstance(icu::Locale(locale.c_str()), status);
}
if(U_SUCCESS(status)) {
diff --git a/searchlib/src/vespa/searchlib/util/runnable.h b/searchlib/src/vespa/searchlib/util/runnable.h
index a8dc8e56856..a95d4d68368 100644
--- a/searchlib/src/vespa/searchlib/util/runnable.h
+++ b/searchlib/src/vespa/searchlib/util/runnable.h
@@ -2,37 +2,39 @@
#pragma once
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
+#include <condition_variable>
namespace search {
class Runnable : public FastOS_Runnable
{
protected:
- vespalib::Monitor _cond;
- bool _done;
- bool _stopped;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ bool _done;
+ bool _stopped;
public:
Runnable() :
- _cond(), _done(false), _stopped(false)
+ _lock(), _cond(), _done(false), _stopped(false)
{ }
- void Run(FastOS_ThreadInterface *, void *) override{
+ void Run(FastOS_ThreadInterface *, void *) override {
doRun();
- vespalib::MonitorGuard guard(_cond);
+ std::lock_guard<std::mutex> guard(_lock);
_stopped = true;
- guard.broadcast();
+ _cond.notify_all();
}
virtual void doRun() = 0;
void stop() {
- vespalib::MonitorGuard guard(_cond);
+ std::lock_guard<std::mutex> guard(_lock);
_done = true;
}
void join() {
- vespalib::MonitorGuard guard(_cond);
+ std::unique_lock<std::mutex> guard(_lock);
while (!_stopped) {
- guard.wait();
+ _cond.wait(guard);
}
}
};
diff --git a/searchsummary/pom.xml b/searchsummary/pom.xml
index 97a29655ad1..417e25ff944 100644
--- a/searchsummary/pom.xml
+++ b/searchsummary/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>searchsummary</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/service-monitor/pom.xml b/service-monitor/pom.xml
index c63bdf35574..70f9d4aa655 100644
--- a/service-monitor/pom.xml
+++ b/service-monitor/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>service-monitor</artifactId>
<packaging>container-plugin</packaging>
@@ -66,6 +67,7 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>provided</scope>
+ <classifier>no_aop</classifier>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
diff --git a/serviceview/pom.xml b/serviceview/pom.xml
index c3620ecfc9d..4357cc697b8 100644
--- a/serviceview/pom.xml
+++ b/serviceview/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>serviceview</artifactId>
<packaging>container-plugin</packaging>
diff --git a/simplemetrics/pom.xml b/simplemetrics/pom.xml
index d027fda82ce..02758b182e3 100644
--- a/simplemetrics/pom.xml
+++ b/simplemetrics/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>simplemetrics</artifactId>
<packaging>container-plugin</packaging>
diff --git a/socket_test/pom.xml b/socket_test/pom.xml
index 60d8b3713e9..0c6d5cd77f4 100644
--- a/socket_test/pom.xml
+++ b/socket_test/pom.xml
@@ -12,6 +12,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<build>
diff --git a/staging_vespalib/CMakeLists.txt b/staging_vespalib/CMakeLists.txt
index d59529df828..095caf17a56 100644
--- a/staging_vespalib/CMakeLists.txt
+++ b/staging_vespalib/CMakeLists.txt
@@ -21,6 +21,7 @@ vespa_define_module(
src/tests/json
src/tests/librarypool
src/tests/memorydatastore
+ src/tests/metrics
src/tests/objectdump
src/tests/objects
src/tests/objectselection
@@ -38,6 +39,7 @@ vespa_define_module(
src/vespa/vespalib
src/vespa/vespalib/data
src/vespa/vespalib/encoding
+ src/vespa/vespalib/metrics
src/vespa/vespalib/net
src/vespa/vespalib/objects
src/vespa/vespalib/stllike
diff --git a/staging_vespalib/src/testlist.txt b/staging_vespalib/src/testlist.txt
deleted file mode 100644
index f29e51218ec..00000000000
--- a/staging_vespalib/src/testlist.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-tests/array
-tests/benchmark
-tests/bits
-tests/clock
-tests/crc
-tests/databuffer
-tests/directio
-tests/dotproduct
-tests/encoding/base64
-tests/fileheader
-tests/floatingpointtype
-tests/growablebytebuffer
-tests/json
-tests/librarypool
-tests/memorydatastore
-tests/objectdump
-tests/objects
-tests/objectselection
-tests/programoptions
-tests/polymorphicarray
-tests/rusage
-tests/shutdownguard
-tests/state_server
-tests/stllike
-tests/timer
-tests/util/process_memory_stats
-tests/xmlserializable
diff --git a/staging_vespalib/src/tests/metrics/CMakeLists.txt b/staging_vespalib/src/tests/metrics/CMakeLists.txt
new file mode 100644
index 00000000000..a441631fd15
--- /dev/null
+++ b/staging_vespalib/src/tests/metrics/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(staging_vespalib_metrics_test_app TEST
+ SOURCES
+ simple_metrics_test.cpp
+ mock_tick.cpp
+ DEPENDS
+ staging_vespalib
+)
+vespa_add_test(NAME staging_vespalib_metrics_test_app COMMAND staging_vespalib_metrics_test_app)
+
+vespa_add_executable(staging_vespalib_stablestore_test_app TEST
+ SOURCES
+ stable_store_test.cpp
+ DEPENDS
+ staging_vespalib
+)
+vespa_add_test(NAME staging_vespalib_stablestore_test_app COMMAND staging_vespalib_stablestore_test_app)
diff --git a/staging_vespalib/src/tests/metrics/mock_tick.cpp b/staging_vespalib/src/tests/metrics/mock_tick.cpp
new file mode 100644
index 00000000000..68e8d7a7daa
--- /dev/null
+++ b/staging_vespalib/src/tests/metrics/mock_tick.cpp
@@ -0,0 +1,6 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "mock_tick.h"
+
+namespace vespalib::metrics {
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/tests/metrics/mock_tick.h b/staging_vespalib/src/tests/metrics/mock_tick.h
new file mode 100644
index 00000000000..3f244ea6c9f
--- /dev/null
+++ b/staging_vespalib/src/tests/metrics/mock_tick.h
@@ -0,0 +1,91 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <vespa/vespalib/metrics/clock.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+
+namespace vespalib::metrics {
+
+// used to test clients of the Tick interface
+// values shared between threads are bounded queues with max size 1
+class MockTick : public Tick {
+private:
+ using Guard = std::unique_lock<std::mutex>;
+ struct Value {
+ TimeStamp value{0.0};
+ bool valid{false};
+ };
+
+ TimeStamp _first_value;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ bool _alive;
+ Value _prev;
+ Value _next;
+
+ void push(Value &dst, TimeStamp value) {
+ Guard guard(_lock);
+ while (_alive && dst.valid) {
+ _cond.wait(guard);
+ }
+ dst.value = value;
+ dst.valid = true;
+ _cond.notify_one();
+ }
+
+ TimeStamp pop(Value &src) {
+ Guard guard(_lock);
+ while (_alive && !src.valid) {
+ _cond.wait(guard);
+ }
+ src.valid = false;
+ _cond.notify_one();
+ return src.value;
+ }
+
+ TimeStamp peek(const Value &src) {
+ Guard guard(_lock);
+ while (_alive && !src.valid) {
+ _cond.wait(guard);
+ }
+ return src.value;
+ }
+
+public:
+ MockTick(TimeStamp first_value)
+ : _first_value(first_value), _lock(), _cond(), _alive(true), _prev(), _next() {}
+ TimeStamp first() override { return _first_value; }
+ TimeStamp next(TimeStamp prev) override {
+ push(_prev, prev);
+ return pop(_next);
+ }
+ TimeStamp give(TimeStamp next_value) {
+ TimeStamp prev_value = pop(_prev);
+ push(_next, next_value);
+ EXPECT_EQUAL(peek(_prev).count(), next_value.count());
+ return prev_value;
+ }
+ bool alive() const override { return _alive; }
+ void kill() override {
+ Guard guard(_lock);
+ _alive = false;
+ _cond.notify_all();
+ }
+};
+
+// share the MockTick between the tested and the tester.
+class TickProxy : public Tick {
+private:
+ std::shared_ptr<Tick> _tick;
+public:
+ TickProxy(std::shared_ptr<Tick> tick) : _tick(std::move(tick)) {}
+ TimeStamp first() override { return _tick->first(); }
+ TimeStamp next(TimeStamp prev) override { return _tick->next(prev); }
+ bool alive() const override { return _tick->alive(); }
+ void kill() override { _tick->kill(); }
+};
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp b/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp
new file mode 100644
index 00000000000..da2502f7ed7
--- /dev/null
+++ b/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp
@@ -0,0 +1,215 @@
+// 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/data/slime/slime.h>
+#include <vespa/vespalib/data/slime/json_format.h>
+#include <vespa/vespalib/metrics/simple_metrics.h>
+#include <vespa/vespalib/metrics/simple_metrics_manager.h>
+#include <vespa/vespalib/metrics/stable_store.h>
+#include <vespa/vespalib/metrics/json_formatter.h>
+#include "mock_tick.h"
+#include <stdio.h>
+#include <unistd.h>
+
+using namespace vespalib;
+using namespace vespalib::metrics;
+
+TEST("require that simple metrics gauge merge works")
+{
+ MetricIdentifier id(MetricName(42), Point(17));
+ Gauge::Measurement a1(id, 0.0);
+ Gauge::Measurement b1(id, 7.0);
+ Gauge::Measurement b2(id, 9.0);
+ Gauge::Measurement b3(id, 8.0);
+ Gauge::Measurement c1(id, 10.0);
+ Gauge::Measurement c2(id, 1.0);
+
+ GaugeAggregator a(a1), b(b1), c(c1);
+ b.merge(b2);
+ b.merge(b3);
+ c.merge(c2);
+
+ EXPECT_EQUAL(a.observedCount, 1u);
+ EXPECT_EQUAL(a.sumValue, 0.0);
+ EXPECT_EQUAL(a.minValue, 0.0);
+ EXPECT_EQUAL(a.maxValue, 0.0);
+ EXPECT_EQUAL(a.lastValue, 0.0);
+
+ EXPECT_EQUAL(b.observedCount, 3u);
+ EXPECT_EQUAL(b.sumValue, 24.0);
+ EXPECT_EQUAL(b.minValue, 7.0);
+ EXPECT_EQUAL(b.maxValue, 9.0);
+ EXPECT_EQUAL(b.lastValue, 8.0);
+
+ EXPECT_EQUAL(c.observedCount, 2u);
+ EXPECT_EQUAL(c.sumValue, 11.0);
+ EXPECT_EQUAL(c.minValue, 1.0);
+ EXPECT_EQUAL(c.maxValue, 10.0);
+ EXPECT_EQUAL(c.lastValue, 1.0);
+
+ a.minValue = 8;
+
+ a.merge(b);
+ EXPECT_EQUAL(a.observedCount, 4u);
+ EXPECT_EQUAL(a.sumValue, 24.0);
+ EXPECT_EQUAL(a.minValue, 7.0);
+ EXPECT_EQUAL(a.maxValue, 9.0);
+ EXPECT_EQUAL(a.lastValue, 8.0);
+
+ a.merge(b);
+ EXPECT_EQUAL(a.observedCount, 7u);
+ EXPECT_EQUAL(a.sumValue, 48.0);
+ EXPECT_EQUAL(a.minValue, 7.0);
+ EXPECT_EQUAL(a.maxValue, 9.0);
+ EXPECT_EQUAL(a.lastValue, 8.0);
+
+ a.merge(c);
+ EXPECT_EQUAL(a.observedCount, 9u);
+ EXPECT_EQUAL(a.sumValue, 59.0);
+ EXPECT_EQUAL(a.minValue, 1.0);
+ EXPECT_EQUAL(a.maxValue, 10.0);
+ EXPECT_EQUAL(a.lastValue, 1.0);
+}
+
+bool compare_json(const vespalib::string &a, const vespalib::string &b)
+{
+ using vespalib::Memory;
+ using vespalib::slime::JsonFormat;
+
+ Slime slimeA, slimeB;
+ if (! JsonFormat::decode(a, slimeA)) {
+fprintf(stderr, "bad json a:\n>>>%s\n<<<\n", a.c_str());
+ return false;
+ }
+ if (! JsonFormat::decode(b, slimeB)) {
+fprintf(stderr, "bad json b\n");
+ return false;
+ }
+ return slimeA == slimeB;
+}
+
+void check_json(const vespalib::string &actual)
+{
+ vespalib::string expect = "{"
+ " snapshot: { from: 1, to: 4 },"
+ " values: [ { name: 'foo',"
+ " values: { count: 17, rate: 4.85714 }"
+ " }, {"
+ " name: 'foo',"
+ " dimensions: { chain: 'default', documenttype: 'music', thread: '0' },"
+ " values: { count: 4, rate: 1.14286 }"
+ " }, {"
+ " name: 'bar',"
+ " values: { count: 4, rate: 1.14286, average: 42, min: 41, max: 43, last: 42 }"
+ " }, {"
+ " name: 'bar',"
+ " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '1' },"
+ " values: { count: 1, rate: 0.285714, average: 14, min: 14, max: 14, last: 14 }"
+ " }, {"
+ " name: 'bar',"
+ " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '2' },"
+ " values: { count: 1, rate: 0.285714, average: 11, min: 11, max: 11, last: 11 }"
+ " } ]"
+ "}";
+ EXPECT_TRUE(compare_json(expect, actual));
+}
+
+
+TEST("use simple_metrics_collector")
+{
+ using namespace vespalib::metrics;
+ SimpleManagerConfig cf;
+ cf.sliding_window_seconds = 5;
+ std::shared_ptr<MockTick> ticker = std::make_shared<MockTick>(TimeStamp(1.0));
+ auto manager = SimpleMetricsManager::createForTest(cf, std::make_unique<TickProxy>(ticker));
+
+ Counter myCounter = manager->counter("foo", "no description");
+ myCounter.add();
+ myCounter.add(16);
+
+ Gauge myGauge = manager->gauge("bar", "dummy description");
+ myGauge.sample(42.0);
+ myGauge.sample(41.0);
+ myGauge.sample(43.0);
+ myGauge.sample(42.0);
+
+ EXPECT_EQUAL(1.0, ticker->give(TimeStamp(2.0)).count());
+
+ Snapshot snap1 = manager->snapshot();
+ EXPECT_EQUAL(1.0, snap1.startTime());
+ EXPECT_EQUAL(2.0, snap1.endTime());
+
+ EXPECT_EQUAL(1u, snap1.counters().size());
+ EXPECT_EQUAL("foo", snap1.counters()[0].name());
+ EXPECT_EQUAL(17u, snap1.counters()[0].count());
+
+ EXPECT_EQUAL(1u, snap1.gauges().size());
+ EXPECT_EQUAL("bar", snap1.gauges()[0].name());
+ EXPECT_EQUAL(4u, snap1.gauges()[0].observedCount());
+ EXPECT_EQUAL(41.0, snap1.gauges()[0].minValue());
+ EXPECT_EQUAL(43.0, snap1.gauges()[0].maxValue());
+ EXPECT_EQUAL(42.0, snap1.gauges()[0].lastValue());
+
+ Point one = manager->pointBuilder()
+ .bind("chain", "default")
+ .bind("documenttype", "music")
+ .bind("thread", "0").build();
+ Point two = manager->pointBuilder()
+ .bind("chain", "vespa")
+ .bind("documenttype", "blogpost")
+ .bind("thread", "1");
+ EXPECT_EQUAL(one.id(), 1u);
+ EXPECT_EQUAL(two.id(), 2u);
+
+ Point anotherOne = manager->pointBuilder()
+ .bind("chain", "default")
+ .bind("documenttype", "music")
+ .bind("thread", "0");
+ EXPECT_EQUAL(anotherOne.id(), 1u);
+
+ Point three = manager->pointBuilder(two).bind("thread", "2");
+ EXPECT_EQUAL(three.id(), 3u);
+
+ myCounter.add(3, one);
+ myCounter.add(one);
+ myGauge.sample(14.0, two);
+ myGauge.sample(11.0, three);
+
+ EXPECT_EQUAL(2.0, ticker->give(TimeStamp(4.5)).count());
+
+ Snapshot snap2 = manager->snapshot();
+ EXPECT_EQUAL(1.0, snap2.startTime());
+ EXPECT_EQUAL(4.5, snap2.endTime());
+ EXPECT_EQUAL(2u, snap2.counters().size());
+ EXPECT_EQUAL(3u, snap2.gauges().size());
+
+ JsonFormatter fmt2(snap2);
+ check_json(fmt2.asString());
+
+ // flush sliding window
+ for (int i = 5; i <= 10; ++i) {
+ ticker->give(TimeStamp(i));
+ }
+ Snapshot snap3 = manager->snapshot();
+ EXPECT_EQUAL(5.0, snap3.startTime());
+ EXPECT_EQUAL(10.0, snap3.endTime());
+ EXPECT_EQUAL(2u, snap3.counters().size());
+ EXPECT_EQUAL(0u, snap3.counters()[0].count());
+ EXPECT_EQUAL(0u, snap3.counters()[1].count());
+ EXPECT_EQUAL(3u, snap3.gauges().size());
+ EXPECT_EQUAL(0u, snap3.gauges()[0].observedCount());
+ EXPECT_EQUAL(0u, snap3.gauges()[1].observedCount());
+ EXPECT_EQUAL(0u, snap3.gauges()[2].observedCount());
+
+ Snapshot snap4 = manager->totalSnapshot();
+ EXPECT_EQUAL(1.0, snap4.startTime());
+ EXPECT_EQUAL(10.0, snap4.endTime());
+ EXPECT_EQUAL(2u, snap4.counters().size());
+ EXPECT_NOT_EQUAL(0u, snap4.counters()[0].count());
+ EXPECT_NOT_EQUAL(0u, snap4.counters()[1].count());
+ EXPECT_EQUAL(3u, snap4.gauges().size());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[0].observedCount());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[1].observedCount());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[2].observedCount());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/staging_vespalib/src/tests/metrics/stable_store_test.cpp b/staging_vespalib/src/tests/metrics/stable_store_test.cpp
new file mode 100644
index 00000000000..03b6663cd64
--- /dev/null
+++ b/staging_vespalib/src/tests/metrics/stable_store_test.cpp
@@ -0,0 +1,65 @@
+// 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/metrics/simple_metrics.h>
+#include <vespa/vespalib/metrics/simple_metrics_manager.h>
+#include <vespa/vespalib/metrics/stable_store.h>
+#include <vespa/vespalib/metrics/json_formatter.h>
+#include <stdio.h>
+#include <unistd.h>
+
+using namespace vespalib;
+using namespace vespalib::metrics;
+
+struct Foo {
+ int a;
+ char *p;
+ explicit Foo(int v) : a(v), p(nullptr) {}
+ bool operator==(const Foo &other) const {
+ return a == other.a;
+ }
+};
+
+TEST("require that stable_store works")
+{
+ vespalib::StableStore<Foo> bunch;
+ bunch.add(Foo(1));
+ bunch.add(Foo(2));
+ bunch.add(Foo(3));
+ bunch.add(Foo(5));
+ bunch.add(Foo(8));
+ bunch.add(Foo(13));
+ bunch.add(Foo(21));
+ bunch.add(Foo(34));
+ bunch.add(Foo(55));
+ bunch.add(Foo(89));
+
+ EXPECT_EQUAL(bunch.size(), 10u);
+
+ int sum = 0;
+
+ bunch.for_each([&sum](const Foo& value) { sum += value.a; });
+ EXPECT_EQUAL(231, sum);
+
+ std::vector<const Foo *> pointers;
+ bunch.for_each([&pointers](const Foo& value)
+ { pointers.push_back(&value); });
+ EXPECT_EQUAL(1, pointers[0]->a);
+ EXPECT_EQUAL(2, pointers[1]->a);
+ EXPECT_EQUAL(55, pointers[8]->a);
+ EXPECT_EQUAL(89, pointers[9]->a);
+
+ for (int i = 0; i < 20000; ++i) {
+ bunch.add(Foo(i));
+ }
+ bunch.for_each([&sum](const Foo& value) { sum -= value.a; });
+ EXPECT_EQUAL(-199990000, sum);
+
+ std::vector<const Foo *> after;
+ bunch.for_each([&after](const Foo& value)
+ { if (after.size() < 10) after.push_back(&value); });
+
+ EXPECT_EQUAL(pointers[0], after[0]);
+ EXPECT_EQUAL(pointers[9], after[9]);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/staging_vespalib/src/vespa/vespalib/CMakeLists.txt b/staging_vespalib/src/vespa/vespalib/CMakeLists.txt
index 24d576eb775..76c69284839 100644
--- a/staging_vespalib/src/vespa/vespalib/CMakeLists.txt
+++ b/staging_vespalib/src/vespa/vespalib/CMakeLists.txt
@@ -4,6 +4,7 @@ vespa_add_library(staging_vespalib
$<TARGET_OBJECTS:staging_vespalib_vespalib_encoding>
$<TARGET_OBJECTS:staging_vespalib_vespalib_util>
$<TARGET_OBJECTS:staging_vespalib_vespalib_data>
+ $<TARGET_OBJECTS:staging_vespalib_vespalib_metrics>
$<TARGET_OBJECTS:staging_vespalib_vespalib_objects>
$<TARGET_OBJECTS:staging_vespalib_vespalib_stllike>
$<TARGET_OBJECTS:staging_vespalib_vespalib_net>
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt b/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt
new file mode 100644
index 00000000000..34cc5453304
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(staging_vespalib_vespalib_metrics OBJECT
+ SOURCES
+ bucket.cpp
+ clock.cpp
+ counter_aggregator.cpp
+ counter.cpp
+ current_samples.cpp
+ dimension.cpp
+ dummy_metrics_manager.cpp
+ gauge_aggregator.cpp
+ gauge.cpp
+ handle.cpp
+ json_formatter.cpp
+ label.cpp
+ metric_identifier.cpp
+ metric_name.cpp
+ metrics_manager.cpp
+ metric_types.cpp
+ name_collection.cpp
+ point_builder.cpp
+ point.cpp
+ point_map_collection.cpp
+ point_map.cpp
+ producer.cpp
+ simple_metrics.cpp
+ simple_metrics_manager.cpp
+ simple_tick.cpp
+ snapshots.cpp
+ stable_store.cpp
+
+ DEPENDS
+)
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp b/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp
new file mode 100644
index 00000000000..52e95e1ac3f
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp
@@ -0,0 +1,143 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "bucket.h"
+#include <assert.h>
+#include <map>
+
+namespace vespalib {
+namespace metrics {
+
+namespace {
+
+template<typename T>
+std::vector<typename T::aggregator_type>
+mergeFromSamples(const StableStore<typename T::sample_type> &source)
+{
+ using Aggregator = typename T::aggregator_type;
+ using Sample = typename T::sample_type;
+ using Map = std::map<MetricIdentifier, Aggregator>;
+ using MapValue = typename Map::value_type;
+
+ Map map;
+ source.for_each([&map] (const Sample &sample) {
+ MetricIdentifier id = sample.idx;
+ auto iter_check = map.emplace(id, sample);
+ if (!iter_check.second) {
+ iter_check.first->second.merge(sample);
+ }
+ });
+ std::vector<typename T::aggregator_type> result;
+ for (const MapValue &entry : map) {
+ result.push_back(entry.second);
+ }
+ return result;
+}
+
+template<typename T>
+std::vector<T>
+mergeVectors(const std::vector<T> &a,
+ const std::vector<T> &b)
+{
+ std::vector<T> result;
+ auto a_iter = a.begin();
+ auto b_iter = b.begin();
+ while (a_iter != a.end() &&
+ b_iter != b.end())
+ {
+ if (a_iter->idx < b_iter->idx) {
+ result.push_back(*a_iter);
+ ++a_iter;
+ } else if (b_iter->idx < a_iter->idx) {
+ result.push_back(*b_iter);
+ ++b_iter;
+ } else {
+ result.push_back(*a_iter);
+ result.back().merge(*b_iter);
+ ++a_iter;
+ ++b_iter;
+ }
+ }
+ while (a_iter != a.end()) {
+ result.push_back(*a_iter);
+ ++a_iter;
+ }
+ while (b_iter != b.end()) {
+ result.push_back(*b_iter);
+ ++b_iter;
+ }
+ return result;
+}
+
+template<typename T>
+std::vector<T>
+findMissing(const std::vector<T> &already,
+ const std::vector<T> &complete)
+{
+ std::vector<T> result;
+ auto a_iter = already.begin();
+ auto c_iter = complete.begin();
+ while (a_iter != already.end() &&
+ c_iter != complete.end())
+ {
+ if (a_iter->idx < c_iter->idx) {
+ // missing from "complete", should not happen
+ ++a_iter;
+ } else if (c_iter->idx < a_iter->idx) {
+ // missing this
+ result.push_back(*c_iter);
+ ++c_iter;
+ } else {
+ // already have this
+ ++a_iter;
+ ++c_iter;
+ }
+ }
+ while (c_iter != complete.end()) {
+ // missing this
+ result.push_back(*c_iter);
+ ++c_iter;
+ }
+ return result;
+}
+
+
+} // namespace <unnamed>
+
+void Bucket::merge(const CurrentSamples &samples)
+{
+ counters = mergeFromSamples<Counter>(samples.counterIncrements);
+ gauges = mergeFromSamples<Gauge>(samples.gaugeMeasurements);
+}
+
+void Bucket::merge(const Bucket &other)
+{
+ assert(genCnt < other.genCnt);
+ genCnt = other.genCnt;
+ startTime = std::min(startTime, other.startTime);
+ endTime = std::max(endTime, other.endTime);
+
+ std::vector<CounterAggregator> nextCounters = mergeVectors(counters, other.counters);
+ counters = std::move(nextCounters);
+
+ std::vector<GaugeAggregator> nextGauges = mergeVectors(gauges, other.gauges);
+ gauges = std::move(nextGauges);
+}
+
+void Bucket::padMetrics(const Bucket &source)
+{
+ std::vector<CounterAggregator> missingC = findMissing(counters, source.counters);
+ for (CounterAggregator aggr : missingC) {
+ aggr.count = 0;
+ counters.push_back(aggr);
+ }
+ std::vector<GaugeAggregator> missingG = findMissing(gauges, source.gauges);
+ for (GaugeAggregator aggr : missingG) {
+ aggr.observedCount = 0;
+ aggr.sumValue = 0;
+ aggr.minValue = 0;
+ aggr.maxValue = 0;
+ gauges.push_back(aggr);
+ }
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/bucket.h b/staging_vespalib/src/vespa/vespalib/metrics/bucket.h
new file mode 100644
index 00000000000..5a88e435502
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/bucket.h
@@ -0,0 +1,43 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <mutex>
+#include "stable_store.h"
+#include "metric_identifier.h"
+#include "counter.h"
+#include "gauge.h"
+#include "clock.h"
+#include "counter_aggregator.h"
+#include "gauge_aggregator.h"
+#include "current_samples.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+struct Bucket {
+ size_t genCnt;
+ TimeStamp startTime;
+ TimeStamp endTime;
+ std::vector<CounterAggregator> counters;
+ std::vector<GaugeAggregator> gauges;
+
+ void merge(const CurrentSamples &other);
+ void merge(const Bucket &other);
+ void padMetrics(const Bucket &source);
+
+ Bucket(size_t generation, TimeStamp started, TimeStamp ended)
+ : genCnt(generation),
+ startTime(started),
+ endTime(ended),
+ counters(),
+ gauges()
+ {}
+ ~Bucket() {}
+ Bucket(Bucket &&) = default;
+ Bucket(const Bucket &) = default;
+ Bucket& operator= (Bucket &&) = default;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp b/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp
new file mode 100644
index 00000000000..82144da895d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "clock.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/clock.h b/staging_vespalib/src/vespa/vespalib/metrics/clock.h
new file mode 100644
index 00000000000..2af2ad89427
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/clock.h
@@ -0,0 +1,30 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+namespace vespalib::metrics {
+
+using TimeStamp = std::chrono::duration<double, std::ratio<1,1>>;
+
+/**
+ * Simple interface abstracting both timing and time measurement for
+ * threads wanting to do stuff at regular intervals and also knowing
+ * at what time stuff was done. The 'next' function blocks until the
+ * next tick is due and returns the current number of seconds since
+ * epoch. The parameter passed to the 'next' function should be its
+ * previous return value, except the first time it is called, then 0
+ * should be used. A convenience function called 'first' is added for
+ * this purpose.
+ **/
+struct Tick {
+ using UP = std::unique_ptr<Tick>;
+ virtual TimeStamp next(TimeStamp prev) = 0;
+ virtual TimeStamp first() = 0;
+ virtual void kill() = 0;
+ virtual bool alive() const = 0;
+ virtual ~Tick() {}
+};
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp b/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp
new file mode 100644
index 00000000000..08f72e6aa34
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp
@@ -0,0 +1,19 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "counter.h"
+#include "metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+
+void
+Counter::add(size_t count, Point point) const
+{
+ if (_manager) {
+ MetricIdentifier fullId(_id, point);
+ _manager->add(Increment(fullId, count));
+ }
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter.h b/staging_vespalib/src/vespa/vespalib/metrics/counter.h
new file mode 100644
index 00000000000..36a25adda2d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/counter.h
@@ -0,0 +1,56 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include "metric_identifier.h"
+#include "point.h"
+
+namespace vespalib {
+namespace metrics {
+
+class MetricsManager;
+class CounterAggregator;
+
+
+/**
+ * Represents a counter metric that can be incremented.
+ **/
+class Counter {
+ std::shared_ptr<MetricsManager> _manager;
+ MetricName _id;
+public:
+ Counter() : _manager(), _id(0) {}
+ Counter(const Counter&) = delete;
+ Counter(Counter &&other) = default;
+ Counter& operator= (const Counter &) = delete;
+ Counter& operator= (Counter &&other) = default;
+ Counter(std::shared_ptr<MetricsManager> m, MetricName id)
+ : _manager(std::move(m)), _id(id)
+ {}
+
+ // convenience methods:
+ void add() const { add(1, Point::empty); }
+ void add(Point p) { add(1, p); }
+ void add(size_t count) const { add(count, Point::empty); }
+
+ /**
+ * Increment the counter.
+ * @param count the amount to increment by (default 1)
+ * @param p the point representing labels for this increment (default empty)
+ **/
+ void add(size_t count, Point p) const;
+
+ // internal
+ struct Increment {
+ MetricIdentifier idx;
+ size_t value;
+ Increment() = delete;
+ Increment(MetricIdentifier id, size_t v) : idx(id), value(v) {}
+ };
+
+ typedef CounterAggregator aggregator_type;
+ typedef Increment sample_type;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp
new file mode 100644
index 00000000000..a02cc1e7a7e
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "counter_aggregator.h"
+#include <assert.h>
+#include <map>
+
+namespace vespalib {
+namespace metrics {
+
+CounterAggregator::CounterAggregator(const Counter::Increment &increment)
+ : idx(increment.idx), count(increment.value)
+{}
+
+void
+CounterAggregator::merge(const CounterAggregator &other)
+{
+ assert(idx == other.idx);
+ count += other.count;
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h
new file mode 100644
index 00000000000..995ba07deb3
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "metric_identifier.h"
+#include "counter.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+struct CounterAggregator {
+ MetricIdentifier idx;
+ size_t count;
+
+ CounterAggregator(const Counter::Increment &other);
+ void merge(const CounterAggregator &other);
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp
new file mode 100644
index 00000000000..55211717973
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp
@@ -0,0 +1,32 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "current_samples.h"
+
+namespace vespalib {
+namespace metrics {
+
+using Guard = std::lock_guard<std::mutex>;
+
+void
+CurrentSamples::add(Counter::Increment inc)
+{
+ Guard guard(lock);
+ counterIncrements.add(inc);
+}
+
+void
+CurrentSamples::sample(Gauge::Measurement value)
+{
+ Guard guard(lock);
+ gaugeMeasurements.add(value);
+}
+
+void
+CurrentSamples::extract(CurrentSamples &into)
+{
+ Guard guard(lock);
+ swap(into.counterIncrements, counterIncrements);
+ swap(into.gaugeMeasurements, gaugeMeasurements);
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h
new file mode 100644
index 00000000000..1b87b135aa2
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <mutex>
+#include "stable_store.h"
+#include "counter.h"
+#include "gauge.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+struct CurrentSamples {
+ std::mutex lock;
+ StableStore<Counter::Increment> counterIncrements;
+ StableStore<Gauge::Measurement> gaugeMeasurements;
+
+ ~CurrentSamples() {}
+
+ void add(Counter::Increment inc);
+ void sample(Gauge::Measurement value);
+ void extract(CurrentSamples &into);
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp b/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp
new file mode 100644
index 00000000000..05bdd42f34a
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "dimension.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dimension.h b/staging_vespalib/src/vespa/vespalib/metrics/dimension.h
new file mode 100644
index 00000000000..ee16bc6f98a
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/dimension.h
@@ -0,0 +1,18 @@
+// 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>
+#include "handle.h"
+
+namespace vespalib::metrics {
+
+using DimensionName = vespalib::string;
+
+struct DimensionTag {};
+
+/**
+ * Opaque handle representing an uniquely named dimension.
+ **/
+using Dimension = Handle<DimensionTag>;
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp
new file mode 100644
index 00000000000..cd7a3abb1eb
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "dummy_metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+DummyMetricsManager::~DummyMetricsManager() {}
+
+Snapshot
+DummyMetricsManager::snapshot()
+{
+ Snapshot snap(0, 0);
+ return snap;
+}
+
+Snapshot
+DummyMetricsManager::totalSnapshot()
+{
+ Snapshot snap(0, 0);
+ return snap;
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h
new file mode 100644
index 00000000000..52e9c6a608c
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h
@@ -0,0 +1,62 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <thread>
+#include <vespa/vespalib/stllike/string.h>
+#include "name_collection.h"
+#include "current_samples.h"
+#include "snapshots.h"
+#include "metrics_manager.h"
+#include "clock.h"
+
+namespace vespalib {
+namespace metrics {
+
+/**
+ * Dummy manager that discards everything, use
+ * for unit tests where you don't care about
+ * metrics.
+ **/
+class DummyMetricsManager : public MetricsManager
+{
+private:
+ DummyMetricsManager() {}
+public:
+ ~DummyMetricsManager();
+
+ static std::shared_ptr<MetricsManager> create() {
+ return std::shared_ptr<MetricsManager>(new DummyMetricsManager());
+ }
+
+ Counter counter(const vespalib::string &, const vespalib::string &) override {
+ return Counter(shared_from_this(), MetricName(0));
+ }
+ Gauge gauge(const vespalib::string &, const vespalib::string &) override {
+ return Gauge(shared_from_this(), MetricName(0));
+ }
+
+ Dimension dimension(const vespalib::string &) override {
+ return Dimension(0);
+ }
+ Label label(const vespalib::string &) override {
+ return Label(0);
+ }
+ PointBuilder pointBuilder(Point) override {
+ return PointBuilder(shared_from_this());
+ }
+ Point pointFrom(PointMap::BackingMap) override {
+ return Point(0);
+ }
+
+ Snapshot snapshot() override;
+ Snapshot totalSnapshot() override;
+
+ // for use from Counter only
+ void add(Counter::Increment) override {}
+ // for use from Gauge only
+ void sample(Gauge::Measurement) override {}
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp b/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp
new file mode 100644
index 00000000000..af98ba2de18
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "gauge.h"
+#include "metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+void
+Gauge::sample(double value, Point point) const
+{
+ if (_manager) {
+ MetricIdentifier fullId(_id, point);
+ _manager->sample(Measurement(fullId, value));
+ }
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge.h b/staging_vespalib/src/vespa/vespalib/metrics/gauge.h
new file mode 100644
index 00000000000..fa3f826f2b8
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge.h
@@ -0,0 +1,46 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include "metric_identifier.h"
+#include "point.h"
+
+namespace vespalib {
+namespace metrics {
+
+class MetricsManager;
+class GaugeAggregator;
+
+/**
+ * Represents a gauge metric that can be measured.
+ **/
+class Gauge {
+private:
+ std::shared_ptr<MetricsManager> _manager;
+ MetricName _id;
+public:
+ Gauge(std::shared_ptr<MetricsManager> m, MetricName id)
+ : _manager(std::move(m)), _id(id)
+ {}
+
+ /**
+ * Provide a sample for the gauge.
+ * @param value the measurement for this sample
+ * @param p the point representing labels for this sample (default empty)
+ **/
+ void sample(double value, Point p = Point::empty) const;
+
+ // internal
+ struct Measurement {
+ MetricIdentifier idx;
+ double value;
+ Measurement() = delete;
+ Measurement(MetricIdentifier id, double v) : idx(id), value(v) {}
+ };
+
+ typedef GaugeAggregator aggregator_type;
+ typedef Measurement sample_type;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp
new file mode 100644
index 00000000000..84ce8aef28d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp
@@ -0,0 +1,30 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "gauge_aggregator.h"
+#include <assert.h>
+#include <map>
+
+namespace vespalib {
+namespace metrics {
+
+GaugeAggregator::GaugeAggregator(const Gauge::Measurement &sample)
+ : idx(sample.idx),
+ observedCount(1),
+ sumValue(sample.value),
+ minValue(sample.value),
+ maxValue(sample.value),
+ lastValue(sample.value)
+{}
+
+void
+GaugeAggregator::merge(const GaugeAggregator &other)
+{
+ assert(idx == other.idx);
+ minValue = std::min(minValue, other.minValue);
+ maxValue = std::max(maxValue, other.maxValue);
+ sumValue += other.sumValue;
+ lastValue = other.lastValue;
+ observedCount += other.observedCount;
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h
new file mode 100644
index 00000000000..84772d7fff4
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "metric_identifier.h"
+#include "gauge.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+struct GaugeAggregator {
+ MetricIdentifier idx;
+ size_t observedCount;
+ double sumValue;
+ double minValue;
+ double maxValue;
+ double lastValue;
+
+ GaugeAggregator(const Gauge::Measurement &other);
+ void merge(const GaugeAggregator &other);
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp b/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp
new file mode 100644
index 00000000000..2b806caeaea
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "handle.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/handle.h b/staging_vespalib/src/vespa/vespalib/metrics/handle.h
new file mode 100644
index 00000000000..94a9ee33830
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/handle.h
@@ -0,0 +1,52 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <cstddef>
+
+namespace vespalib {
+namespace metrics {
+
+/**
+ * Common implementation of an opaque handle identified only
+ * by a (64-bit) integer. Templated to avoid different concepts
+ * sharing a superclass.
+ **/
+template <typename T>
+class Handle {
+private:
+ const size_t _id;
+public:
+ explicit Handle(size_t id) : _id(id) {}
+ size_t id() const { return _id; }
+};
+
+template <typename T>
+bool
+operator< (const Handle<T> &a, const Handle<T> &b) noexcept
+{
+ return a.id() < b.id();
+}
+
+template <typename T>
+bool
+operator> (const Handle<T> &a, const Handle<T> &b) noexcept
+{
+ return a.id() > b.id();
+}
+
+template <typename T>
+bool
+operator== (const Handle<T> &a, const Handle<T> &b) noexcept
+{
+ return a.id() == b.id();
+}
+
+template <typename T>
+bool
+operator!= (const Handle<T> &a, const Handle<T> &b) noexcept
+{
+ return a.id() != b.id();
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp
new file mode 100644
index 00000000000..280211bd780
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp
@@ -0,0 +1,75 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "json_formatter.h"
+
+namespace vespalib {
+namespace metrics {
+
+JsonFormatter::JsonFormatter(const Snapshot &snapshot)
+ : _data(),
+ _top(_data.setObject()),
+ _snapLen(snapshot.endTime() - snapshot.startTime())
+{
+ if (_snapLen < 0.1) {
+ _snapLen = 0.1;
+ }
+ // cosmetics: ordering inside objects
+ _data.insert("name");
+ _data.insert("dimensions");
+ vespalib::slime::Cursor& meta = _top.setObject("snapshot");
+ meta.setLong("from", (long)snapshot.startTime());
+ meta.setLong("to", (long)snapshot.endTime());
+ handle(snapshot, _top.setArray("values"));
+}
+
+void
+JsonFormatter::handle(const Snapshot &snapshot, vespalib::slime::Cursor &target)
+{
+ for (const CounterSnapshot &entry : snapshot.counters()) {
+ handle(entry, target.addObject());
+ }
+ for (const GaugeSnapshot &entry : snapshot.gauges()) {
+ handle(entry, target.addObject());
+ }
+}
+
+void
+JsonFormatter::handle(const CounterSnapshot &snapshot, vespalib::slime::Cursor &target)
+{
+ target.setString("name", snapshot.name());
+ // target.setString("description", ?);
+ handle(snapshot.point(), target);
+ Cursor& inner = target.setObject("values");
+ inner.setLong("count", snapshot.count());
+ inner.setDouble("rate", snapshot.count() / _snapLen);
+}
+
+void
+JsonFormatter::handle(const GaugeSnapshot &snapshot, vespalib::slime::Cursor &target)
+{
+ target.setString("name", snapshot.name());
+ // target.setString("description", ?);
+ handle(snapshot.point(), target);
+ Cursor& inner = target.setObject("values");
+ inner.setDouble("average", snapshot.averageValue());
+ inner.setDouble("min", snapshot.minValue());
+ inner.setDouble("max", snapshot.maxValue());
+ inner.setDouble("last", snapshot.lastValue());
+ inner.setLong("count", snapshot.observedCount());
+ inner.setDouble("rate", snapshot.observedCount() / _snapLen);
+}
+
+void
+JsonFormatter::handle(const PointSnapshot &snapshot, vespalib::slime::Cursor &target)
+{
+ if (snapshot.dimensions.size() == 0) {
+ return;
+ }
+ Cursor& inner = target.setObject("dimensions");
+ for (const DimensionBinding &entry : snapshot.dimensions) {
+ inner.setString(entry.dimensionName(), entry.labelValue());
+ }
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h
new file mode 100644
index 00000000000..672b71766ae
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h
@@ -0,0 +1,37 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "snapshots.h"
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/data/slime/slime.h>
+
+namespace vespalib {
+namespace metrics {
+
+/**
+ * utility for converting a snapshot to JSON format
+ * (which can be inserted into /state/v1/metrics page).
+ **/
+class JsonFormatter
+{
+private:
+ using Cursor = vespalib::slime::Cursor;
+ vespalib::Slime _data;
+ Cursor& _top;
+ double _snapLen;
+
+ void handle(const Snapshot &snapshot, Cursor &target);
+ void handle(const PointSnapshot &snapshot, Cursor &target);
+ void handle(const CounterSnapshot &snapshot, Cursor &target);
+ void handle(const GaugeSnapshot &snapshot, Cursor &target);
+public:
+ JsonFormatter(const Snapshot &snapshot);
+
+ vespalib::string asString() const {
+ return _data.toString();
+ }
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/label.cpp b/staging_vespalib/src/vespa/vespalib/metrics/label.cpp
new file mode 100644
index 00000000000..218db1ca2ce
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/label.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "label.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/label.h b/staging_vespalib/src/vespa/vespalib/metrics/label.h
new file mode 100644
index 00000000000..e2356415fd6
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/label.h
@@ -0,0 +1,18 @@
+// 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>
+#include "handle.h"
+
+namespace vespalib::metrics {
+
+using LabelValue = vespalib::string;
+
+struct LabelTag {};
+
+/**
+ * Opaque handle representing an uniquely named label.
+ **/
+using Label = Handle<LabelTag>;
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp
new file mode 100644
index 00000000000..950adc3462d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "metric_identifier.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h
new file mode 100644
index 00000000000..0fc5af41a81
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h
@@ -0,0 +1,38 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "metric_name.h"
+#include "point.h"
+#include <functional>
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+struct MetricIdentifier {
+ const MetricName _name;
+ const Point _point;
+
+ MetricIdentifier() = delete;
+
+ MetricIdentifier(MetricName name, Point point)
+ : _name(name), _point(point) {}
+
+ bool operator< (const MetricIdentifier &other) const {
+ if (_name != other._name) {
+ return _name < other._name;
+ }
+ return _point < other._point;
+ }
+ bool operator== (const MetricIdentifier &other) const {
+ return (_name == other._name &&
+ _point == other._point);
+ }
+
+ MetricName name() const { return _name; }
+ Point point() const { return _point; }
+
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp
new file mode 100644
index 00000000000..2a58d55e945
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp
@@ -0,0 +1,2 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "metric_name.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h
new file mode 100644
index 00000000000..60dfa0aa00f
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h
@@ -0,0 +1,15 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "handle.h"
+
+namespace vespalib::metrics {
+
+struct MetricNameTag {};
+
+/**
+ * Opaque handle representing an uniquely named metric.
+ **/
+using MetricName = Handle<MetricNameTag>;
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp
new file mode 100644
index 00000000000..1d8b1fe2a01
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp
@@ -0,0 +1,44 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "metric_types.h"
+#include <assert.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".vespalib.metrics.metric_types");
+
+namespace vespalib {
+namespace metrics {
+
+const char* MetricTypes::_typeNames[] = {
+ "INVALID",
+ "Counter",
+ "Gauge",
+ "Histogram",
+ "IntegerHistogram"
+};
+
+void
+MetricTypes::check(size_t id, const vespalib::string &name, MetricType ty)
+{
+ std::lock_guard<std::mutex> guard(_lock);
+ if (id < _seen.size()) {
+ MetricType old = _seen[id];
+ if (old == ty) {
+ return;
+ }
+ if (old == MetricType::INVALID) {
+ _seen[id] = ty;
+ }
+ LOG(warning, "metric '%s' with different types %s and %s, this will be confusing",
+ name.c_str(), _typeNames[ty], _typeNames[old]);
+ }
+ while (_seen.size() < id) {
+ _seen.push_back(MetricType::INVALID);
+ }
+ _seen.push_back(ty);
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
+
+
+
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h
new file mode 100644
index 00000000000..5c764dcc74e
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h
@@ -0,0 +1,33 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include <vespa/vespalib/stllike/string.h>
+
+namespace vespalib {
+namespace metrics {
+
+// internal class for typechecking
+class MetricTypes {
+ static const char *_typeNames[];
+public:
+ enum MetricType {
+ INVALID,
+ COUNTER,
+ GAUGE,
+ HISTOGRAM,
+ INT_HISTOGRAM
+ };
+
+ void check(size_t id, const vespalib::string& name, MetricType ty);
+
+ MetricTypes() = default;
+ ~MetricTypes() {}
+private:
+ std::mutex _lock;
+ std::vector<MetricType> _seen;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp
new file mode 100644
index 00000000000..be6612bd869
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h
new file mode 100644
index 00000000000..14f11fa4ab2
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h
@@ -0,0 +1,91 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <thread>
+#include <vespa/vespalib/stllike/string.h>
+#include "name_collection.h"
+#include "counter.h"
+#include "gauge.h"
+#include "current_samples.h"
+#include "snapshots.h"
+#include "point.h"
+#include "point_builder.h"
+#include "dimension.h"
+#include "label.h"
+
+namespace vespalib::metrics {
+
+
+/**
+ * Interface for a Metrics manager, for creating metrics
+ * and for fetching snapshots.
+ **/
+class MetricsManager
+ : public std::enable_shared_from_this<MetricsManager>
+{
+public:
+ virtual ~MetricsManager() {}
+
+ /**
+ * Get or create a counter metric.
+ * @param name the name of the metric.
+ **/
+ virtual Counter counter(const vespalib::string &name, const vespalib::string &description) = 0;
+
+ /**
+ * Get or create a gauge metric.
+ * @param name the name of the metric.
+ **/
+ virtual Gauge gauge(const vespalib::string &name, const vespalib::string &description) = 0;
+
+ /**
+ * Get or create a dimension for labeling metrics.
+ * @param name the name of the dimension.
+ **/
+ virtual Dimension dimension(const vespalib::string &name) = 0; // get or create
+
+ /**
+ * Get or create a label.
+ * @param value the label value.
+ **/
+ virtual Label label(const vespalib::string &value) = 0; // get or create
+
+ /**
+ * Create a PointBuilder for labeling metrics.
+ **/
+ PointBuilder pointBuilder() {
+ return PointBuilder(shared_from_this());
+ }
+
+ /**
+ * Create a PointBuilder for labeling metrics, starting with
+ * an Point of already existing dimension/label pairs, which can
+ * then be added to or changed.
+ * @param from provide a Point to start from.
+ *
+ **/
+ virtual PointBuilder pointBuilder(Point from) = 0;
+
+ /**
+ * Create a snapshot of sampled metrics (usually for the last minute).
+ **/
+ virtual Snapshot snapshot() = 0;
+
+ /**
+ * Create a snapshot of all sampled metrics the manager has seen.
+ **/
+ virtual Snapshot totalSnapshot() = 0;
+
+ // for use from PointBuilder only
+ virtual Point pointFrom(PointMap::BackingMap map) = 0;
+
+ // for use from Counter only
+ virtual void add(Counter::Increment inc) = 0;
+
+ // for use from Gauge only
+ virtual void sample(Gauge::Measurement value) = 0;
+};
+
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp
new file mode 100644
index 00000000000..697d41c4c6b
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp
@@ -0,0 +1,38 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "name_collection.h"
+#include <assert.h>
+
+namespace vespalib {
+namespace metrics {
+
+using Guard = std::lock_guard<std::mutex>;
+
+const vespalib::string &
+NameCollection::lookup(size_t id) const
+{
+ Guard guard(_lock);
+ assert(id < _names_by_id.size());
+ return _names_by_id[id]->first;
+}
+
+size_t
+NameCollection::resolve(const vespalib::string& name)
+{
+ Guard guard(_lock);
+ size_t nextId = _names_by_id.size();
+ auto iter_check = _names.emplace(name, nextId);
+ if (iter_check.second) {
+ _names_by_id.push_back(iter_check.first);
+ }
+ return iter_check.first->second;
+}
+
+size_t
+NameCollection::size() const
+{
+ Guard guard(_lock);
+ return _names_by_id.size();
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h
new file mode 100644
index 00000000000..566fd2a3997
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h
@@ -0,0 +1,29 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <mutex>
+#include <map>
+#include <vector>
+#include <vespa/vespalib/stllike/string.h>
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+class NameCollection {
+private:
+ using Map = std::map<vespalib::string, size_t>;
+ mutable std::mutex _lock;
+ Map _names;
+ std::vector<Map::const_iterator> _names_by_id;
+public:
+ const vespalib::string &lookup(size_t id) const;
+ size_t resolve(const vespalib::string& name);
+ size_t size() const;
+
+ NameCollection() = default;
+ ~NameCollection() {}
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point.cpp
new file mode 100644
index 00000000000..a5f99a4ba6d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point.cpp
@@ -0,0 +1,10 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "point.h"
+
+namespace vespalib {
+namespace metrics {
+
+Point Point::empty(0);
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point.h b/staging_vespalib/src/vespa/vespalib/metrics/point.h
new file mode 100644
index 00000000000..e50283bd94c
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point.h
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "handle.h"
+
+namespace vespalib::metrics {
+
+/**
+ * Opaque handle representing an unique N-dimensional point
+ **/
+class Point : public Handle<Point> {
+public:
+ static Point empty;
+ explicit Point(size_t id) : Handle<Point>(id) {}
+};
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp
new file mode 100644
index 00000000000..7ee4ca35d0e
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp
@@ -0,0 +1,52 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "point.h"
+#include "metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+PointBuilder::PointBuilder(std::shared_ptr<MetricsManager> m)
+ : _owner(std::move(m)), _map()
+{}
+
+PointBuilder::PointBuilder(std::shared_ptr<MetricsManager> m,
+ const PointMap::BackingMap &copyFrom)
+ : _owner(std::move(m)), _map(copyFrom)
+{}
+
+PointBuilder &&
+PointBuilder::bind(Dimension dimension, Label label) &&
+{
+ _map.erase(dimension);
+ _map.emplace(dimension, label);
+ return std::move(*this);
+}
+
+PointBuilder &&
+PointBuilder::bind(Dimension dimension, LabelValue label) &&
+{
+ Label c = _owner->label(label);
+ return std::move(*this).bind(dimension, c);
+}
+
+PointBuilder &&
+PointBuilder::bind(DimensionName dimension, LabelValue label) &&
+{
+ Dimension a = _owner->dimension(dimension);
+ Label c = _owner->label(label);
+ return std::move(*this).bind(a, c);
+}
+
+Point
+PointBuilder::build()
+{
+ return _owner->pointFrom(PointMap::BackingMap(_map));
+}
+
+PointBuilder::operator Point() &&
+{
+ return _owner->pointFrom(std::move(_map));
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h
new file mode 100644
index 00000000000..83b804228dd
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h
@@ -0,0 +1,55 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <vespa/vespalib/stllike/string.h>
+
+#include "point.h"
+#include "point_map.h"
+
+namespace vespalib {
+namespace metrics {
+
+class MetricsManager;
+
+/**
+ * Build a Point for labeling metrics
+ **/
+class PointBuilder {
+private:
+ std::shared_ptr<MetricsManager> _owner;
+ PointMap::BackingMap _map;
+
+public:
+ // for use from MetricsManager
+ PointBuilder(std::shared_ptr<MetricsManager> m);
+ PointBuilder(std::shared_ptr<MetricsManager> m, const PointMap::BackingMap &from);
+ ~PointBuilder() {}
+
+ /**
+ * Bind a dimension to a label.
+ * Overwrites any label already bound to that dimension.
+ **/
+ PointBuilder &&bind(Dimension dimension, Label label) &&;
+
+ /**
+ * Bind a dimension to a label.
+ * Convenience method that converts the label value.
+ **/
+ PointBuilder &&bind(Dimension dimension, LabelValue label) &&;
+
+ /**
+ * Bind a dimension to a label.
+ * Convenience method that converts both the dimension name and the label value.
+ **/
+ PointBuilder &&bind(DimensionName dimension, LabelValue label) &&;
+
+ /** make a Point from the builder */
+ Point build();
+
+ /** make a Point from the builder */
+ operator Point () &&;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp
new file mode 100644
index 00000000000..51908ae235e
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp
@@ -0,0 +1,49 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "point.h"
+#include "metrics_manager.h"
+
+namespace vespalib {
+namespace metrics {
+
+PointMap::PointMap(BackingMap &&from)
+ : _map(std::move(from)),
+ _hash(0)
+{
+ for (const BackingMap::value_type &entry : _map) {
+ _hash = (_hash << 7) + (_hash >> 31) + entry.first.id();
+ _hash = (_hash << 7) + (_hash >> 31) + entry.second.id();
+ }
+}
+
+bool
+PointMap::operator< (const PointMap &other) const
+{
+ // cheap comparison first
+ if (_hash != other._hash) {
+ return _hash < other._hash;
+ }
+ if (_map.size() != other._map.size()) {
+ return _map.size() < other._map.size();
+ }
+ // sizes equal, iterate in parallel
+ for (auto m = _map.begin(), o = other._map.begin();
+ m != _map.end();
+ ++m, ++o)
+ {
+ const Dimension& d1 = m->first;
+ const Dimension& d2 = o->first;
+ if (d1 != d2) {
+ return d1 < d2;
+ }
+ const Label &l1 = m->second;
+ const Label &l2 = o->second;
+ if (l1 != l2) {
+ return l1 < l2;
+ }
+ }
+ // equal
+ return false;
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map.h b/staging_vespalib/src/vespa/vespalib/metrics/point_map.h
new file mode 100644
index 00000000000..2ed50a85842
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map.h
@@ -0,0 +1,27 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <map>
+#include "dimension.h"
+#include "label.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+class PointMap {
+public:
+ using BackingMap = std::map<Dimension, Label>;
+private:
+ const PointMap::BackingMap _map;
+ size_t _hash;
+public:
+ PointMap() : _map(), _hash(0) {}
+ PointMap(BackingMap &&from);
+ bool operator< (const PointMap &other) const;
+
+ const BackingMap &backingMap() const { return _map; }
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp
new file mode 100644
index 00000000000..7b09fbf7746
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp
@@ -0,0 +1,39 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "point_map_collection.h"
+#include <assert.h>
+
+namespace vespalib {
+namespace metrics {
+
+using Guard = std::lock_guard<std::mutex>;
+
+const PointMap &
+PointMapCollection::lookup(size_t id)
+{
+ Guard guard(_lock);
+ assert(id < _vec.size());
+ PointMapMap::const_iterator iter = _vec[id];
+ return iter->first;
+}
+
+size_t
+PointMapCollection::resolve(PointMap map)
+{
+ Guard guard(_lock);
+ size_t nextId = _vec.size();
+ auto iter_check = _map.emplace(std::move(map), nextId);
+ if (iter_check.second) {
+ _vec.push_back(iter_check.first);
+ }
+ return iter_check.first->second;
+}
+
+size_t
+PointMapCollection::size() const
+{
+ Guard guard(_lock);
+ return _vec.size();
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h
new file mode 100644
index 00000000000..ba301ff3f06
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h
@@ -0,0 +1,30 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <mutex>
+#include <map>
+#include <vector>
+#include "point_map.h"
+
+namespace vespalib {
+namespace metrics {
+
+// internal
+class PointMapCollection {
+private:
+ using PointMapMap = std::map<PointMap, size_t>;
+
+ mutable std::mutex _lock;
+ PointMapMap _map;
+ std::vector<PointMapMap::const_iterator> _vec;
+public:
+ const PointMap &lookup(size_t id);
+ size_t resolve(PointMap map);
+ size_t size() const;
+
+ PointMapCollection() = default;
+ ~PointMapCollection() {}
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp b/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp
new file mode 100644
index 00000000000..ae8d6d76eac
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp
@@ -0,0 +1,33 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "producer.h"
+#include "metrics_manager.h"
+#include "json_formatter.h"
+
+namespace vespalib {
+namespace metrics {
+
+Producer::Producer(std::shared_ptr<MetricsManager> m)
+ : _manager(m)
+{}
+
+vespalib::string
+Producer::getMetrics(const vespalib::string &)
+{
+ Snapshot snap = _manager->snapshot();
+ JsonFormatter fmt(snap);
+ return fmt.asString();
+}
+
+vespalib::string
+Producer::getTotalMetrics(const vespalib::string &)
+{
+ Snapshot snap = _manager->totalSnapshot();
+ JsonFormatter fmt(snap);
+ return fmt.asString();
+}
+
+
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/producer.h b/staging_vespalib/src/vespa/vespalib/metrics/producer.h
new file mode 100644
index 00000000000..387d94379b7
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/producer.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <vespa/vespalib/net/metrics_producer.h>
+
+namespace vespalib {
+namespace metrics {
+
+class MetricsManager;
+
+/**
+ * Utility class for wiring a MetricsManager into a StateApi.
+ **/
+class Producer : public vespalib::MetricsProducer {
+private:
+ std::shared_ptr<MetricsManager> _manager;
+public:
+ Producer(std::shared_ptr<MetricsManager> m);
+ vespalib::string getMetrics(const vespalib::string &consumer) override;
+ vespalib::string getTotalMetrics(const vespalib::string &consumer) override;
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp
new file mode 100644
index 00000000000..d9bd3f00892
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "simple_metrics.h"
+
+namespace vespalib {
+namespace metrics {
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h
new file mode 100644
index 00000000000..6e6cff55b1f
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h
@@ -0,0 +1,23 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vector>
+#include <chrono>
+#include <memory>
+#include <vespa/vespalib/stllike/string.h>
+
+#include "clock.h"
+#include "counter.h"
+#include "dimension.h"
+#include "dummy_metrics_manager.h"
+#include "gauge.h"
+#include "json_formatter.h"
+#include "label.h"
+#include "metric_identifier.h"
+#include "metric_name.h"
+#include "metrics_manager.h"
+#include "point_builder.h"
+#include "point.h"
+#include "producer.h"
+#include "simple_metrics_manager.h"
+#include "snapshots.h"
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp
new file mode 100644
index 00000000000..049be4fd328
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp
@@ -0,0 +1,224 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "simple_metrics_manager.h"
+#include "simple_tick.h"
+
+#include <vespa/log/log.h>
+LOG_SETUP(".vespalib.metrics.simple_metrics_manager");
+
+namespace vespalib {
+namespace metrics {
+
+using Guard = std::lock_guard<std::mutex>;
+
+SimpleMetricsManager::SimpleMetricsManager(const SimpleManagerConfig &config,
+ Tick::UP tick_supplier)
+ : _metricNames(),
+ _dimensionNames(),
+ _labelValues(),
+ _pointMaps(),
+ _currentSamples(),
+ _tickSupplier(std::move(tick_supplier)),
+ _startTime(_tickSupplier->first()),
+ _curTime(_startTime),
+ _collectCnt(0),
+ _buckets(),
+ _firstBucket(0),
+ _maxBuckets(config.sliding_window_seconds),
+ _totalsBucket(0, _startTime, _startTime),
+ _thread(&SimpleMetricsManager::tickerLoop, this)
+{
+ if (_maxBuckets < 1) _maxBuckets = 1;
+ Point empty = pointFrom(PointMap::BackingMap());
+ assert(empty.id() == 0);
+}
+
+SimpleMetricsManager::~SimpleMetricsManager()
+{
+ stopThread();
+}
+
+std::shared_ptr<MetricsManager>
+SimpleMetricsManager::create(const SimpleManagerConfig &config)
+{
+ return std::shared_ptr<MetricsManager>(
+ new SimpleMetricsManager(config, std::make_unique<SimpleTick>()));
+}
+
+std::shared_ptr<MetricsManager>
+SimpleMetricsManager::createForTest(const SimpleManagerConfig &config,
+ Tick::UP tick_supplier)
+{
+ return std::shared_ptr<MetricsManager>(
+ new SimpleMetricsManager(config, std::move(tick_supplier)));
+}
+
+Counter
+SimpleMetricsManager::counter(const vespalib::string &name, const vespalib::string &)
+{
+ size_t id = _metricNames.resolve(name);
+ _metricTypes.check(id, name, MetricTypes::MetricType::COUNTER);
+ LOG(debug, "counter with metric name %s -> %zu", name.c_str(), id);
+ return Counter(shared_from_this(), MetricName(id));
+}
+
+Gauge
+SimpleMetricsManager::gauge(const vespalib::string &name, const vespalib::string &)
+{
+ size_t id = _metricNames.resolve(name);
+ _metricTypes.check(id, name, MetricTypes::MetricType::GAUGE);
+ LOG(debug, "gauge with metric name %s -> %zu", name.c_str(), id);
+ return Gauge(shared_from_this(), MetricName(id));
+}
+
+Bucket
+SimpleMetricsManager::mergeBuckets()
+{
+ Guard bucketsGuard(_bucketsLock);
+ if (_buckets.size() > 0) {
+ TimeStamp startTime = _buckets[_firstBucket].startTime;
+ Bucket merger(0, startTime, startTime);
+ for (size_t i = 0; i < _buckets.size(); i++) {
+ size_t off = (_firstBucket + i) % _buckets.size();
+ merger.merge(_buckets[off]);
+ }
+ merger.padMetrics(_totalsBucket);
+ return merger;
+ }
+ // no data
+ return Bucket(0, _startTime, _curTime);
+}
+
+Bucket
+SimpleMetricsManager::totalsBucket()
+{
+ Guard bucketsGuard(_bucketsLock);
+ return _totalsBucket;
+}
+
+Snapshot
+SimpleMetricsManager::snapshotFrom(const Bucket &bucket)
+{
+ std::vector<PointSnapshot> points;
+
+ double s = bucket.startTime.count();
+ double e = bucket.endTime.count();
+ Snapshot snap(s, e);
+ {
+ for (size_t i = 0; i < _pointMaps.size(); ++i) {
+ const PointMap::BackingMap &map = _pointMaps.lookup(i).backingMap();
+ PointSnapshot point;
+ for (const PointMap::BackingMap::value_type &kv : map) {
+ point.dimensions.emplace_back(nameFor(kv.first), valueFor(kv.second));
+ }
+ snap.add(point);
+ }
+ }
+ for (const CounterAggregator& entry : bucket.counters) {
+ size_t ni = entry.idx.name().id();
+ size_t pi = entry.idx.point().id();
+ const vespalib::string &name = _metricNames.lookup(ni);
+ CounterSnapshot val(name, snap.points()[pi], entry);
+ snap.add(val);
+ }
+ for (const GaugeAggregator& entry : bucket.gauges) {
+ size_t ni = entry.idx.name().id();
+ size_t pi = entry.idx.point().id();
+ const vespalib::string &name = _metricNames.lookup(ni);
+ GaugeSnapshot val(name, snap.points()[pi], entry);
+ snap.add(val);
+ }
+ return snap;
+}
+
+Snapshot
+SimpleMetricsManager::snapshot()
+{
+ Bucket merged = mergeBuckets();
+ return snapshotFrom(merged);
+}
+
+Snapshot
+SimpleMetricsManager::totalSnapshot()
+{
+ Bucket totals = totalsBucket();
+ return snapshotFrom(totals);
+}
+
+void
+SimpleMetricsManager::collectCurrentSamples(TimeStamp prev,
+ TimeStamp curr)
+{
+ CurrentSamples samples;
+ _currentSamples.extract(samples);
+ Bucket newBucket(++_collectCnt, prev, curr);
+ newBucket.merge(samples);
+
+ Guard guard(_bucketsLock);
+ _totalsBucket.merge(newBucket);
+ if (_buckets.size() < _maxBuckets) {
+ _buckets.push_back(std::move(newBucket));
+ } else {
+ _buckets[_firstBucket] = std::move(newBucket);
+ _firstBucket = (_firstBucket + 1) % _buckets.size();
+ }
+}
+
+Dimension
+SimpleMetricsManager::dimension(const vespalib::string &name)
+{
+ size_t id = _dimensionNames.resolve(name);
+ LOG(debug, "dimension name %s -> %zu", name.c_str(), id);
+ return Dimension(id);
+}
+
+Label
+SimpleMetricsManager::label(const vespalib::string &value)
+{
+ size_t id = _labelValues.resolve(value);
+ LOG(debug, "label value %s -> %zu", value.c_str(), id);
+ return Label(id);
+}
+
+PointBuilder
+SimpleMetricsManager::pointBuilder(Point from)
+{
+ const PointMap &map = _pointMaps.lookup(from.id());
+ return PointBuilder(shared_from_this(), map.backingMap());
+}
+
+Point
+SimpleMetricsManager::pointFrom(PointMap::BackingMap map)
+{
+ size_t id = _pointMaps.resolve(PointMap(std::move(map)));
+ return Point(id);
+}
+
+
+void
+SimpleMetricsManager::tickerLoop()
+{
+ while (_tickSupplier->alive()) {
+ TimeStamp now = _tickSupplier->next(_curTime);
+ if (_tickSupplier->alive()) {
+ tick(now);
+ }
+ }
+}
+
+void
+SimpleMetricsManager::stopThread()
+{
+ _tickSupplier->kill();
+ _thread.join();
+}
+
+void
+SimpleMetricsManager::tick(TimeStamp now)
+{
+ TimeStamp prev = _curTime;
+ collectCurrentSamples(prev, now);
+ _curTime = now;
+}
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h
new file mode 100644
index 00000000000..01725bb1739
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h
@@ -0,0 +1,97 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <atomic>
+#include <mutex>
+#include <memory>
+#include <thread>
+#include <vespa/vespalib/stllike/string.h>
+#include "name_collection.h"
+#include "current_samples.h"
+#include "snapshots.h"
+#include "metrics_manager.h"
+#include "metric_types.h"
+#include "clock.h"
+#include "point_map_collection.h"
+#include "bucket.h"
+
+namespace vespalib {
+namespace metrics {
+
+struct SimpleManagerConfig {
+ int sliding_window_seconds;
+ // possibly more config later
+ SimpleManagerConfig() : sliding_window_seconds(60) {}
+};
+
+
+/**
+ * Simple manager class that puts everything into a
+ * single global repo with std::mutex locks used around
+ * most operations. Only implements sliding window
+ * and a fixed (1 Hz) collecting interval.
+ * XXX: Consider renaming this to "SlidingWindowManager".
+ **/
+class SimpleMetricsManager : public MetricsManager
+{
+private:
+ NameCollection _metricNames;
+ MetricTypes _metricTypes;
+ NameCollection _dimensionNames;
+ NameCollection _labelValues;
+ PointMapCollection _pointMaps;
+
+ const vespalib::string& nameFor(Dimension dimension) { return _dimensionNames.lookup(dimension.id()); }
+ const vespalib::string& valueFor(Label label) { return _labelValues.lookup(label.id()); }
+
+ CurrentSamples _currentSamples;
+
+ Tick::UP _tickSupplier;
+ TimeStamp _startTime;
+ TimeStamp _curTime;
+
+ std::mutex _bucketsLock;
+ size_t _collectCnt;
+ std::vector<Bucket> _buckets;
+ size_t _firstBucket;
+ size_t _maxBuckets;
+ Bucket _totalsBucket;
+
+ std::thread _thread;
+ void tickerLoop();
+ void stopThread();
+ void tick(TimeStamp now); // called once per second from another thread
+
+ void collectCurrentSamples(TimeStamp prev, TimeStamp curr);
+ Bucket mergeBuckets();
+ Bucket totalsBucket();
+ Snapshot snapshotFrom(const Bucket &bucket);
+
+ SimpleMetricsManager(const SimpleManagerConfig &config,
+ Tick::UP tick_supplier);
+public:
+ ~SimpleMetricsManager();
+ static std::shared_ptr<MetricsManager> create(const SimpleManagerConfig &config);
+ static std::shared_ptr<MetricsManager> createForTest(const SimpleManagerConfig &config,
+ Tick::UP tick_supplier);
+ Counter counter(const vespalib::string &name, const vespalib::string &description) override;
+ Gauge gauge(const vespalib::string &name, const vespalib::string &description) override;
+ Dimension dimension(const vespalib::string &name) override;
+ Label label(const vespalib::string &value) override;
+ PointBuilder pointBuilder(Point from) override;
+ Point pointFrom(PointMap::BackingMap map) override;
+ Snapshot snapshot() override;
+ Snapshot totalSnapshot() override;
+
+ // for use from Counter only
+ void add(Counter::Increment inc) override {
+ _currentSamples.add(inc);
+ }
+ // for use from Gauge only
+ void sample(Gauge::Measurement value) override {
+ _currentSamples.sample(value);
+ }
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp
new file mode 100644
index 00000000000..31a04e67ac4
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp
@@ -0,0 +1,62 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "simple_tick.h"
+
+namespace vespalib::metrics {
+
+namespace {
+
+const TimeStamp oneSec{1.0};
+
+TimeStamp now()
+{
+ using Clock = std::chrono::system_clock;
+ Clock::time_point now = Clock::now();
+ return now.time_since_epoch();
+}
+
+} // namespace <unnamed>
+
+SimpleTick::SimpleTick()
+ : _lock(), _runFlag(true), _cond()
+{}
+
+TimeStamp
+SimpleTick::first()
+{
+ return now();
+}
+
+TimeStamp
+SimpleTick::next(TimeStamp prev)
+{
+ std::unique_lock<std::mutex> locker(_lock);
+ while (_runFlag) {
+ TimeStamp curr = now();
+ if (curr - prev >= oneSec) {
+ return curr;
+ } else if (curr < prev) {
+ // clock was adjusted backwards
+ prev = curr;
+ _cond.wait_for(locker, oneSec);
+ } else {
+ _cond.wait_for(locker, oneSec - (curr - prev));
+ }
+ }
+ return now();
+}
+
+void
+SimpleTick::kill()
+{
+ std::unique_lock<std::mutex> locker(_lock);
+ _runFlag.store(false);
+ _cond.notify_all();
+}
+
+bool
+SimpleTick::alive() const
+{
+ return _runFlag;
+}
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h
new file mode 100644
index 00000000000..74b9d88d255
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "clock.h"
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+
+namespace vespalib::metrics {
+
+// internal
+class SimpleTick : public Tick {
+private:
+ std::mutex _lock;
+ std::atomic<bool> _runFlag;
+ std::condition_variable _cond;
+public:
+ SimpleTick();
+ TimeStamp first() override;
+ TimeStamp next(TimeStamp prev) override;
+ void kill() override;
+ bool alive() const override;
+};
+
+} // namespace vespalib::metrics
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp
new file mode 100644
index 00000000000..46de7beaac3
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "snapshots.h"
+
+namespace vespalib {
+namespace metrics {
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h
new file mode 100644
index 00000000000..0e9915cfb6d
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h
@@ -0,0 +1,106 @@
+// 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>
+#include <vector>
+#include "counter_aggregator.h"
+#include "gauge_aggregator.h"
+
+namespace vespalib {
+namespace metrics {
+
+class DimensionBinding {
+private:
+ const vespalib::string _dimensionName;
+ const vespalib::string _labelValue;
+public:
+ const vespalib::string &dimensionName() const { return _dimensionName; }
+ const vespalib::string &labelValue() const { return _labelValue; }
+ DimensionBinding(const vespalib::string &a,
+ const vespalib::string &v)
+ : _dimensionName(a), _labelValue(v)
+ {}
+ ~DimensionBinding() {}
+};
+
+struct PointSnapshot {
+ std::vector<DimensionBinding> dimensions;
+};
+
+class CounterSnapshot {
+private:
+ const vespalib::string _name;
+ const PointSnapshot &_point;
+ const size_t _count;
+public:
+ CounterSnapshot(const vespalib::string &n, const PointSnapshot &p, const CounterAggregator &c)
+ : _name(n), _point(p), _count(c.count)
+ {}
+ ~CounterSnapshot() {}
+ const vespalib::string &name() const { return _name; }
+ const PointSnapshot &point() const { return _point; }
+ size_t count() const { return _count; }
+};
+
+class GaugeSnapshot {
+private:
+ const vespalib::string _name;
+ const PointSnapshot &_point;
+ const size_t _observedCount;
+ const double _averageValue;
+ const double _minValue;
+ const double _maxValue;
+ const double _lastValue;
+public:
+ GaugeSnapshot(const vespalib::string &n, const PointSnapshot &p, const GaugeAggregator &c)
+ : _name(n),
+ _point(p),
+ _observedCount(c.observedCount),
+ _averageValue(c.sumValue / (c.observedCount > 0 ? c.observedCount : 1)),
+ _minValue(c.minValue),
+ _maxValue(c.maxValue),
+ _lastValue(c.lastValue)
+ {}
+ ~GaugeSnapshot() {}
+ const vespalib::string &name() const { return _name; }
+ const PointSnapshot &point() const { return _point; }
+ size_t observedCount() const { return _observedCount; }
+ double averageValue() const { return _averageValue; }
+ double minValue() const { return _minValue; }
+ double maxValue() const { return _maxValue; }
+ double lastValue() const { return _lastValue; }
+};
+
+class Snapshot {
+private:
+ double _start;
+ double _end;
+ std::vector<CounterSnapshot> _counters;
+ std::vector<GaugeSnapshot> _gauges;
+ std::vector<PointSnapshot> _points;
+public:
+ double startTime() const { return _start; }; // seconds since 1970
+ double endTime() const { return _end; }; // seconds since 1970
+
+ const std::vector<CounterSnapshot> &counters() const {
+ return _counters;
+ }
+ const std::vector<GaugeSnapshot> &gauges() const {
+ return _gauges;
+ }
+ const std::vector<PointSnapshot> &points() const {
+ return _points;
+ }
+
+ // builders:
+ Snapshot(double s, double e)
+ : _start(s), _end(e), _counters(), _gauges()
+ {}
+ ~Snapshot() {}
+ void add(const PointSnapshot &entry) { _points.push_back(entry); }
+ void add(const CounterSnapshot &entry) { _counters.push_back(entry); }
+ void add(const GaugeSnapshot &entry) { _gauges.push_back(entry); }
+};
+
+} // namespace vespalib::metrics
+} // namespace vespalib
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp
new file mode 100644
index 00000000000..dd649133988
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp
@@ -0,0 +1,4 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "stable_store.h"
+
diff --git a/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h
new file mode 100644
index 00000000000..b62e0f2a94e
--- /dev/null
+++ b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h
@@ -0,0 +1,88 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <assert.h>
+
+namespace vespalib {
+
+/** metrics-internal utility class */
+template <typename T>
+class StableStore
+{
+ using MyClass = StableStore<T>;
+ template<typename U>
+ friend void swap(StableStore<U> &a, StableStore<U> &b);
+public:
+ typedef std::unique_ptr<MyClass> UP;
+
+ StableStore();
+ ~StableStore() {}
+
+ void add(T t) {
+ size_t sz = _mine.size();
+ if (sz == _mine.capacity()) {
+ UP next(new MyClass(_size, std::move(_more), std::move(_mine)));;
+ _mine.clear();
+ _mine.reserve(sz << 1);
+ _more = std::move(next);
+ }
+ _mine.push_back(t);
+ ++_size;
+ }
+
+ template<typename FUNC>
+ void for_each(FUNC &&func) const {
+ std::vector<const MyClass *> vv;
+ dffill(vv);
+ for (const MyClass *p : vv) {
+ for (const T& elem : p->_mine) {
+ func(elem);
+ }
+ }
+ }
+
+ size_t size() const { return _size; }
+
+private:
+ void dffill(std::vector<const MyClass *> &vv) const {
+ if (_more) { _more->dffill(vv); }
+ vv.push_back(this);
+ }
+
+ StableStore(size_t sz, UP &&more, std::vector<T> &&mine);
+
+ size_t _size;
+ UP _more;
+ std::vector<T> _mine;
+};
+
+template<typename T>
+StableStore<T>::StableStore()
+ : _size(0),
+ _more(),
+ _mine()
+{
+ _mine.reserve(3);
+}
+
+template<typename T>
+StableStore<T>::StableStore(size_t sz, UP &&more, std::vector<T> &&mine)
+ : _size(sz),
+ _more(std::move(more)),
+ _mine(std::move(mine))
+{}
+
+template <typename T>
+void swap(StableStore<T> &a,
+ StableStore<T> &b)
+{
+ using std::swap;
+ swap(a._size, b._size);
+ swap(a._mine, b._mine);
+ swap(a._more, b._more);
+}
+
+} // namespace vespalib
diff --git a/standalone-container/pom.xml b/standalone-container/pom.xml
index 9300181e00b..0d66951f364 100644
--- a/standalone-container/pom.xml
+++ b/standalone-container/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>standalone-container</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
index fe1af676b54..41e6b66b986 100644
--- a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
+++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
@@ -10,8 +10,8 @@ import scala.language.implicitConversions
import scala.util.Try
/**
- * @author tonytv
- */
+ * @author Tony Vaagenes
+ */
class CloudConfigYinstVariables extends CloudConfigOptions {
import CloudConfigYinstVariables._
@@ -39,6 +39,7 @@ class CloudConfigYinstVariables extends CloudConfigOptions {
override val dockerRegistry = optionalYinstVar[java.lang.String]("docker_registry")
override val dockerVespaBaseImage = optionalYinstVar[java.lang.String]("docker_vespa_base_image")
override val loadBalancerAddress = optionalYinstVar[java.lang.String]("load_balancer_address")
+ override val disableFiledistributor = optionalYinstVar[java.lang.Boolean]("disable_filedistributor")
}
object CloudConfigYinstVariables {
diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
index 1d4b83ce7d3..e1d5ba6577d 100644
--- a/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
+++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala
@@ -158,7 +158,7 @@ object StandaloneContainerApplication {
val logger = new BaseDeployLogger
val rawApplicationPackage = new FilesApplicationPackage.Builder(applicationPath.toFile).includeSourceFiles(true).preprocessedDir(preprocessedApplicationDir).build()
// TODO: Needed until we get rid of semantic rules
- val applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone(), new RuleConfigDeriver {
+ val applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone().id, new RuleConfigDeriver {
override def derive(ruleBaseDir: String, outputDir: String): Unit = {}
}, logger)
validateApplication(applicationPackage)
diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh
new file mode 100755
index 00000000000..dd0693f6f85
--- /dev/null
+++ b/standalone-container/src/main/sh/standalone-container.sh
@@ -0,0 +1,309 @@
+#!/bin/bash
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# BEGIN environment bootstrap section
+# Do not edit between here and END as this section should stay identical in all scripts
+
+findpath () {
+ myname=${0}
+ mypath=${myname%/*}
+ myname=${myname##*/}
+ if [ "$mypath" ] && [ -d "$mypath" ]; then
+ return
+ fi
+ mypath=$(pwd)
+ if [ -f "${mypath}/${myname}" ]; then
+ return
+ fi
+ echo "FATAL: Could not figure out the path where $myname lives from $0"
+ exit 1
+}
+
+COMMON_ENV=libexec/vespa/common-env.sh
+
+source_common_env () {
+ if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then
+ export VESPA_HOME
+ common_env=$VESPA_HOME/$COMMON_ENV
+ if [ -f "$common_env" ]; then
+ . $common_env
+ return
+ fi
+ fi
+ return 1
+}
+
+findroot () {
+ source_common_env && return
+ if [ "$VESPA_HOME" ]; then
+ echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'"
+ exit 1
+ fi
+ if [ "$ROOT" ] && [ -d "$ROOT" ]; then
+ VESPA_HOME="$ROOT"
+ source_common_env && return
+ fi
+ findpath
+ while [ "$mypath" ]; do
+ VESPA_HOME=${mypath}
+ source_common_env && return
+ mypath=${mypath%/*}
+ done
+ echo "FATAL: missing VESPA_HOME environment variable"
+ echo "Could not locate $COMMON_ENV anywhere"
+ exit 1
+}
+
+findroot
+
+# END environment bootstrap section
+
+Usage() {
+ cat <<EOF
+Usage: ${0##*/} start [OPTION]...
+Usage: ${0##*/} stop [OPTION]...
+Manage Vespa standalone jdisc container service.
+
+Options:
+ -u USER Run as USER. Overrides any VESPA_USER environment variable.
+ -s SERVICE The service name.
+EOF
+
+ exit 1
+}
+
+Fail() {
+ printf "%s\n" "$*"
+ exit 1
+}
+
+FixDataDirectory() {
+ if ! [ -d "$1" ]; then
+ echo "Creating data directory '$1'"
+ mkdir -p "$1" || exit 1
+ fi
+ chown "${VESPA_USER}" "$1"
+ chmod 755 "$1"
+}
+
+StartCommand() {
+ local service="$1"
+ shift
+
+ if (( $# > 0 )); then
+ Fail "Too many arguments"
+ fi
+
+ local service_regex='^[0-9a-zA-Z_-]+$'
+ if ! [[ "$service" =~ $service_regex ]]; then
+ Fail "Service must match regex '$service_regex'"
+ fi
+
+ # common setup
+ export VESPA_SERVICE_NAME="$service"
+
+ # stuff for the process:
+ local appdir="$VESPA_HOME/conf/$service-app"
+ local pidfile="$VESPA_HOME/var/run/$service.pid"
+ local cfpfile="$VESPA_HOME/var/jdisc_core/$service.properties"
+ local bundlecachedir="$VESPA_HOME/var/vespa/bundlecache/$service"
+
+ cd "$VESPA_HOME" || Fail "Cannot cd to $VESPA_HOME"
+
+ fixlimits
+ checkjava
+
+ local vespa_log="$VESPA_HOME/logs/vespa/vespa.log"
+ export VESPA_LOG_TARGET="file:$vespa_log"
+ FixDataDirectory "$(dirname "$vespa_log")"
+
+ export VESPA_LOG_CONTROL_FILE="$VESPA_HOME/var/db/vespa/logcontrol/$service.logcontrol"
+ export VESPA_LOG_CONTROL_DIR="$(dirname "$VESPA_LOG_CONTROL_FILE")"
+ FixDataDirectory "$VESPA_LOG_CONTROL_DIR"
+
+ # Does not need fast allocation
+ export MALLOC_ARENA_MAX=1
+
+ # will be picked up by standalone-container:
+ export standalone_jdisc_container__app_location="$appdir"
+
+ # class path
+ CP="$VESPA_HOME/lib/jars/jdisc_core-jar-with-dependencies.jar"
+
+ FixDataDirectory "$(dirname "$cfpfile")"
+ printenv > "$cfpfile"
+ FixDataDirectory "$bundlecachedir"
+
+ java \
+ -Xms128m -Xmx2048m \
+ -XX:+PreserveFramePointer \
+ -XX:+HeapDumpOnOutOfMemoryError \
+ -XX:HeapDumpPath="$VESPA_HOME/var/crash" \
+ -XX:OnOutOfMemoryError="kill -9 %p" \
+ -Djava.library.path="$VESPA_HOME/lib64" \
+ -Djava.awt.headless=true \
+ -Dsun.rmi.dgc.client.gcInterval=3600000 \
+ -Dsun.net.client.defaultConnectTimeout=5000 \
+ -Dsun.net.client.defaultReadTimeout=60000 \
+ -Djavax.net.ssl.keyStoreType=JKS \
+ -Djdisc.config.file="$cfpfile" \
+ -Djdisc.export.packages= \
+ -Djdisc.cache.path="$bundlecachedir" \
+ -Djdisc.debug.resources=false \
+ -Djdisc.bundle.path="$VESPA_HOME/lib/jars" \
+ -Djdisc.logger.enabled=true \
+ -Djdisc.logger.level=ALL \
+ -Djdisc.logger.tag="jdisc/$service" \
+ -Dfile.encoding=UTF-8 \
+ -cp "$CP" \
+ com.yahoo.jdisc.core.StandaloneMain standalone-container-jar-with-dependencies.jar &
+
+ local pid="$!"
+ echo "$pid" > "$pidfile"
+}
+
+Kill() {
+ local force="$1"
+ local expected_user="$2"
+ local expected_comm="$3" # Executable name only
+ local pid="$4"
+
+ local -i now
+ if ! now=$(date +%s); then
+ Fail "Failed to get the current date in seconds since epoch"
+ fi
+ local -i timeout=$(( now + 300 ))
+
+ local has_killed=false
+
+ while true; do
+ local ps_output=""
+ if ! ps_output=$(ps -p "$pid" -o user= -o comm=); then
+ # success
+ return
+ fi
+
+ local user comm
+ read -r user comm <<< "$ps_output"
+
+ if test "$user" != "$expected_user"; then
+ echo "Warning: Pid collision ($pid): Expected user $expected_user but found $user."
+ echo "Will assume original process has died."
+ return
+ fi
+
+ if test "$comm" != "$expected_comm"; then
+ echo "Warning: Pid collision ($pid): Expected program $expected_comm but found $comm."
+ echo "Will assume original process has died."
+ return
+ fi
+
+ if ! "$has_killed"; then
+ if $force; then
+ if ! kill -KILL "$pid"; then
+ Fail "Failed to kill $pid"
+ fi
+ else
+ if ! kill "$pid"; then
+ Fail "Failed to kill $pid"
+ fi
+ fi
+
+ has_killed=true
+ fi
+
+ sleep 1
+
+ now=$(date +%s)
+ if (( now >= timeout )); then
+ Fail "Process $pid still exists after $timeout seconds, giving up"
+ fi
+ done
+}
+
+StopCommand() {
+ local user="$1"
+ shift
+
+ local force=false
+ while (( $# > 0 )); do
+ case "$1" in
+ -f|--force)
+ force=true
+ shift
+ ;;
+ *) break ;;
+ esac
+ done
+
+ if (( $# != 1 )); then
+ Fail "Stop command takes exactly one argument"
+ fi
+
+ local service="$1"
+
+ local pidfile="$VESPA_HOME/var/run/$service.pid"
+ if ! test -r "$pidfile"; then
+ echo "$service is not running"
+ return
+ fi
+
+ local pid=$(< "$pidfile")
+ if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
+ Fail "Pid file '$pidfile' does not contain a valid pid: $pid"
+ fi
+
+ Kill "$force" "$user" java "$pid"
+ rm -f "$pidfile"
+}
+
+Main() {
+ if (( $# == 0 )); then
+ Usage
+ fi
+
+ local command="$1"
+ shift
+
+ local service="standalone/container"
+ local user="$VESPA_USER"
+
+ while (( $# > 0 )); do
+ case "$1" in
+ --help|-h) Usage ;;
+ --service|-s)
+ service="$2"
+ shift 2
+ ;;
+ --user|-u)
+ user="$2"
+ shift 2
+ ;;
+ *) break ;;
+ esac
+ done
+
+ # Service name will be included in paths and possibly environment variable
+ # names, so be restrictive.
+ local service_regex='^[a-zA-Z0-9_-]+$'
+ if test -z "$service"; then
+ Fail "SERVICE not specified"
+ elif ! [[ "$service" =~ $service_regex ]]; then
+ Fail "Service must math the regex '$service_regex'"
+ fi
+
+ if ! getent passwd "$user" &> /dev/null; then
+ Fail "Bad user ($user): not found in passwd"
+ elif test "$(id -un)" != "$user"; then
+ Fail "${0##*/} must be started by $user"
+ fi
+
+ case "$command" in
+ help) Usage ;;
+ start) StartCommand "$service" "$@" ;;
+ stop) StopCommand "$user" "$service" "$@" ;;
+ *) Fail "Unknown command '$command'" ;;
+ esac
+}
+
+Main "$@"
diff --git a/standalone-container/vespa-standalone-container.spec b/standalone-container/vespa-standalone-container.spec
new file mode 100644
index 00000000000..5ab7e9409a5
--- /dev/null
+++ b/standalone-container/vespa-standalone-container.spec
@@ -0,0 +1,95 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# Force special prefix for Vespa
+%define _prefix /opt/vespa
+
+# Hack to speed up jar packing for now. This does not affect the rpm size.
+%define __jar_repack %{nil}
+
+Name: vespa-standalone-container
+Version: %version
+BuildArch: noarch
+Release: 1%{?dist}
+Summary: Vespa standalone JDisc container
+Group: Applications/Databases
+License: Commercial
+URL: http://vespa.ai
+
+Requires: bash
+Requires: java-1.8.0-openjdk-headless
+
+Conflicts: vespa
+
+%description
+The Vespa standalone JDisc container is a runtime environment for Java
+applications.
+
+%install
+declare jars_dir=%buildroot%_prefix/lib/jars
+mkdir -p "$jars_dir"
+
+declare -a dirs=(
+ jdisc_jetty/target/dependency
+ vespa_jersey2/target/dependency
+)
+for dir in "${dirs[@]}"; do
+ cp "$dir"/* "$jars_dir"
+done
+
+declare -a modules=(
+ component
+ config-bundle
+ config-model-api
+ config-model
+ config-provisioning
+ configdefinitions
+ container-disc
+ container-jersey2
+ container-search-and-docproc
+ defaults
+ docprocs
+ jdisc_core
+ jdisc_http_service
+ simplemetrics
+ standalone-container
+ vespaclient-container-plugin
+ zkfacade
+)
+for module in "${modules[@]}"; do
+ cp "$module"/target/"$module"-jar-with-dependencies.jar "$jars_dir"
+done
+
+# vespajlib must be installed _without_ dependencies.
+cp vespajlib/target/vespajlib.jar "$jars_dir"
+
+declare -a libexec_files=(
+ vespabase/src/common-env.sh
+ standalone-container/src/main/sh/standalone-container.sh
+)
+declare libexec_dir=%buildroot%_prefix/libexec/vespa
+mkdir -p "$libexec_dir"
+for file in "${libexec_files[@]}"; do
+ cp "$file" "$libexec_dir"
+done
+
+%clean
+rm -rf %buildroot
+
+%pre
+getent group vespa >/dev/null || groupadd -r vespa
+getent passwd vespa >/dev/null || \
+ useradd -r -g vespa -d %_prefix -s /sbin/nologin \
+ -c "Create owner of all Vespa data files" vespa
+echo "pathmunge %_prefix/bin" > /etc/profile.d/vespa.sh
+echo "export VESPA_HOME=%_prefix" >> /etc/profile.d/vespa.sh
+chmod +x /etc/profile.d/vespa.sh
+
+%postun
+if [ $1 -eq 0 ]; then # this is an uninstallation
+ rm -f /etc/profile.d/vespa.sh
+ userdel vespa
+fi
+
+%files
+%defattr(-,vespa,vespa,-)
+%_prefix/*
diff --git a/statistics/pom.xml b/statistics/pom.xml
index 7bbef0ea3c0..521efa9a2a1 100644
--- a/statistics/pom.xml
+++ b/statistics/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>statistics</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt
index 24d5a2d27f2..269785d629e 100644
--- a/storage/CMakeLists.txt
+++ b/storage/CMakeLists.txt
@@ -35,7 +35,6 @@ vespa_define_module(
src/vespa/storage/distributor/operations/external
src/vespa/storage/distributor/operations/idealstate
src/vespa/storage/frameworkimpl/component
- src/vespa/storage/frameworkimpl/memory
src/vespa/storage/frameworkimpl/status
src/vespa/storage/frameworkimpl/thread
src/vespa/storage/persistence
@@ -60,12 +59,10 @@ vespa_define_module(
src/tests/common
src/tests/common/hostreporter
src/tests/distributor
- src/tests/frameworkimpl/memory
src/tests/frameworkimpl/status
src/tests/persistence
src/tests/persistence/common
src/tests/persistence/filestorage
src/tests/storageserver
- src/tests/storageutil
src/tests/visiting
)
diff --git a/storage/pom.xml b/storage/pom.xml
index 49b103cbe34..e0b21d5a8a9 100644
--- a/storage/pom.xml
+++ b/storage/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>storage</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/storage/src/tests/CMakeLists.txt b/storage/src/tests/CMakeLists.txt
index 7747e8bacc1..a0264bf2946 100644
--- a/storage/src/tests/CMakeLists.txt
+++ b/storage/src/tests/CMakeLists.txt
@@ -5,7 +5,6 @@ vespa_add_executable(storage_testrunner_app TEST
DEPENDS
storage_teststorageserver
storage_testbucketmover
- storage_teststorageutil
storage_testvisiting
storage_testbucketdb
storage_testcommon
@@ -13,7 +12,6 @@ vespa_add_executable(storage_testrunner_app TEST
storage_testdistributor
storage_testpersistence
storage_testfilestorage
- storage_testmemory
storage_teststatus
)
diff --git a/storage/src/tests/common/CMakeLists.txt b/storage/src/tests/common/CMakeLists.txt
index 8dd4e969d04..991726c935b 100644
--- a/storage/src/tests/common/CMakeLists.txt
+++ b/storage/src/tests/common/CMakeLists.txt
@@ -2,6 +2,7 @@
vespa_add_library(storage_testcommon TEST
SOURCES
dummystoragelink.cpp
+ global_bucket_space_distribution_converter_test.cpp
metricstest.cpp
storagelinktest.cpp
testhelper.cpp
diff --git a/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp b/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp
new file mode 100644
index 00000000000..d34bed304a0
--- /dev/null
+++ b/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp
@@ -0,0 +1,385 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/storage/common/global_bucket_space_distribution_converter.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/config/config.h>
+#include <vespa/config/print/asciiconfigwriter.h>
+#include <vespa/config/print/asciiconfigreader.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <random>
+
+namespace storage {
+
+struct GlobalBucketSpaceDistributionConverterTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(GlobalBucketSpaceDistributionConverterTest);
+ CPPUNIT_TEST(can_transform_flat_cluster_config);
+ CPPUNIT_TEST(can_transform_single_level_multi_group_config);
+ CPPUNIT_TEST(can_transform_multi_level_multi_group_config);
+ CPPUNIT_TEST(can_transform_heterogenous_multi_group_config);
+ CPPUNIT_TEST(config_retired_state_is_propagated);
+ CPPUNIT_TEST(group_capacities_are_propagated);
+ CPPUNIT_TEST(global_distribution_has_same_owner_distributors_as_default);
+ CPPUNIT_TEST_SUITE_END();
+
+ void can_transform_flat_cluster_config();
+ void can_transform_single_level_multi_group_config();
+ void can_transform_multi_level_multi_group_config();
+ void can_transform_heterogenous_multi_group_config();
+ void config_retired_state_is_propagated();
+ void group_capacities_are_propagated();
+ void global_distribution_has_same_owner_distributors_as_default();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GlobalBucketSpaceDistributionConverterTest);
+
+using DistributionConfig = vespa::config::content::StorDistributionConfig;
+
+namespace {
+
+std::unique_ptr<DistributionConfig> string_to_config(const vespalib::string& cfg) {
+ vespalib::asciistream iss(cfg);
+ config::AsciiConfigReader<vespa::config::content::StorDistributionConfig> reader(iss);
+ return reader.read();
+}
+
+vespalib::string config_to_string(const DistributionConfig& cfg) {
+ vespalib::asciistream ost;
+ config::AsciiConfigWriter writer(ost);
+ writer.write(cfg);
+ return ost.str();
+}
+
+vespalib::string default_to_global_config(const vespalib::string& default_config) {
+ auto default_cfg = string_to_config(default_config);
+ auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg);
+ return config_to_string(*as_global);
+}
+
+}
+
+void GlobalBucketSpaceDistributionConverterTest::can_transform_flat_cluster_config() {
+ vespalib::string default_config(
+R"(redundancy 1
+group[1]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].nodes[3]
+group[0].nodes[0].index 0
+group[0].nodes[1].index 1
+group[0].nodes[2].index 2
+)");
+
+ vespalib::string expected_global_config(
+R"(redundancy 3
+initial_redundancy 0
+ensure_primary_persisted true
+ready_copies 3
+active_per_leaf_group true
+distributor_auto_ownership_transfer_on_whole_group_down true
+group[0].index "invalid"
+group[0].name "invalid"
+group[0].capacity 1
+group[0].partitions "*"
+group[0].nodes[0].index 0
+group[0].nodes[0].retired false
+group[0].nodes[1].index 1
+group[0].nodes[1].retired false
+group[0].nodes[2].index 2
+group[0].nodes[2].retired false
+disk_distribution MODULO_BID
+)");
+ CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config));
+}
+
+
+void GlobalBucketSpaceDistributionConverterTest::can_transform_single_level_multi_group_config() {
+ vespalib::string default_config(
+R"(redundancy 2
+group[3]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].nodes[0]
+group[1].name rack0
+group[1].index 0
+group[1].nodes[3]
+group[1].nodes[0].index 0
+group[1].nodes[1].index 1
+group[1].nodes[2].index 2
+group[2].name rack1
+group[2].index 1
+group[2].nodes[3]
+group[2].nodes[0].index 3
+group[2].nodes[1].index 4
+group[2].nodes[2].index 5
+)");
+
+ // The config converter cannot distinguish between default values
+ // and explicitly set ones, so we get a few more entries in our output
+ // config string.
+ // Most crucial parts of the transformed config is the root redundancy
+ // and the new partition config. We test _all_ config fields here so that
+ // we catch anything we miss transferring state of.
+ vespalib::string expected_global_config(
+R"(redundancy 6
+initial_redundancy 0
+ensure_primary_persisted true
+ready_copies 6
+active_per_leaf_group true
+distributor_auto_ownership_transfer_on_whole_group_down true
+group[0].index "invalid"
+group[0].name "invalid"
+group[0].capacity 1
+group[0].partitions "3|3|*"
+group[1].index "0"
+group[1].name "rack0"
+group[1].capacity 1
+group[1].partitions ""
+group[1].nodes[0].index 0
+group[1].nodes[0].retired false
+group[1].nodes[1].index 1
+group[1].nodes[1].retired false
+group[1].nodes[2].index 2
+group[1].nodes[2].retired false
+group[2].index "1"
+group[2].name "rack1"
+group[2].capacity 1
+group[2].partitions ""
+group[2].nodes[0].index 3
+group[2].nodes[0].retired false
+group[2].nodes[1].index 4
+group[2].nodes[1].retired false
+group[2].nodes[2].index 5
+group[2].nodes[2].retired false
+disk_distribution MODULO_BID
+)");
+ CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config));
+}
+
+void GlobalBucketSpaceDistributionConverterTest::can_transform_multi_level_multi_group_config() {
+ vespalib::string default_config(
+R"(redundancy 2
+group[5]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions *|*
+group[0].nodes[0]
+group[1].name switch0
+group[1].index 0
+group[1].partitions 1|*
+group[1].nodes[0]
+group[2].name rack0
+group[2].index 0.0
+group[2].nodes[1]
+group[2].nodes[0].index 0
+group[3].name rack1
+group[3].index 0.1
+group[3].nodes[1]
+group[3].nodes[0].index 1
+group[4].name switch0
+group[4].index 1
+group[4].partitions *
+group[4].nodes[0]
+group[5].name rack0
+group[5].index 1.0
+group[5].nodes[1]
+group[5].nodes[0].index 2
+group[6].name rack1
+group[6].index 1.1
+group[6].nodes[1]
+group[6].nodes[0].index 3
+)");
+
+ // Note: leaf groups do not have a partition spec, only inner groups.
+ vespalib::string expected_global_config(
+R"(redundancy 4
+initial_redundancy 0
+ensure_primary_persisted true
+ready_copies 4
+active_per_leaf_group true
+distributor_auto_ownership_transfer_on_whole_group_down true
+group[0].index "invalid"
+group[0].name "invalid"
+group[0].capacity 1
+group[0].partitions "2|2|*"
+group[1].index "0"
+group[1].name "switch0"
+group[1].capacity 1
+group[1].partitions "1|1|*"
+group[2].index "0.0"
+group[2].name "rack0"
+group[2].capacity 1
+group[2].partitions ""
+group[2].nodes[0].index 0
+group[2].nodes[0].retired false
+group[3].index "0.1"
+group[3].name "rack1"
+group[3].capacity 1
+group[3].partitions ""
+group[3].nodes[0].index 1
+group[3].nodes[0].retired false
+group[4].index "1"
+group[4].name "switch0"
+group[4].capacity 1
+group[4].partitions "1|1|*"
+group[5].index "1.0"
+group[5].name "rack0"
+group[5].capacity 1
+group[5].partitions ""
+group[5].nodes[0].index 2
+group[5].nodes[0].retired false
+group[6].index "1.1"
+group[6].name "rack1"
+group[6].capacity 1
+group[6].partitions ""
+group[6].nodes[0].index 3
+group[6].nodes[0].retired false
+disk_distribution MODULO_BID
+)");
+ CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config));
+}
+
+void GlobalBucketSpaceDistributionConverterTest::can_transform_heterogenous_multi_group_config() {
+ vespalib::string default_config(
+R"(redundancy 2
+ready_copies 2
+group[3]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].nodes[0]
+group[1].name rack0
+group[1].index 0
+group[1].nodes[1]
+group[1].nodes[0].index 0
+group[2].name rack1
+group[2].index 1
+group[2].nodes[2]
+group[2].nodes[0].index 1
+group[2].nodes[1].index 2
+)");
+
+ vespalib::string expected_global_config(
+R"(redundancy 3
+initial_redundancy 0
+ensure_primary_persisted true
+ready_copies 3
+active_per_leaf_group true
+distributor_auto_ownership_transfer_on_whole_group_down true
+group[0].index "invalid"
+group[0].name "invalid"
+group[0].capacity 1
+group[0].partitions "1|2|*"
+group[1].index "0"
+group[1].name "rack0"
+group[1].capacity 1
+group[1].partitions ""
+group[1].nodes[0].index 0
+group[1].nodes[0].retired false
+group[2].index "1"
+group[2].name "rack1"
+group[2].capacity 1
+group[2].partitions ""
+group[2].nodes[0].index 1
+group[2].nodes[0].retired false
+group[2].nodes[1].index 2
+group[2].nodes[1].retired false
+disk_distribution MODULO_BID
+)");
+ CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config));
+}
+
+void GlobalBucketSpaceDistributionConverterTest::config_retired_state_is_propagated() {
+ vespalib::string default_config(
+R"(redundancy 1
+group[1]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].nodes[3]
+group[0].nodes[0].index 0
+group[0].nodes[0].retired false
+group[0].nodes[1].index 1
+group[0].nodes[1].retired true
+group[0].nodes[2].index 2
+group[0].nodes[2].retired true
+)");
+
+ auto default_cfg = string_to_config(default_config);
+ auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(1), as_global->group.size());
+ CPPUNIT_ASSERT_EQUAL(size_t(3), as_global->group[0].nodes.size());
+ CPPUNIT_ASSERT_EQUAL(false, as_global->group[0].nodes[0].retired);
+ CPPUNIT_ASSERT_EQUAL(true, as_global->group[0].nodes[1].retired);
+ CPPUNIT_ASSERT_EQUAL(true, as_global->group[0].nodes[2].retired);
+}
+
+void GlobalBucketSpaceDistributionConverterTest::group_capacities_are_propagated() {
+ vespalib::string default_config(
+R"(redundancy 2
+group[3]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].capacity 5
+group[0].nodes[0]
+group[1].name rack0
+group[1].index 0
+group[1].capacity 2
+group[1].nodes[1]
+group[1].nodes[0].index 0
+group[2].name rack1
+group[2].capacity 3
+group[2].index 1
+group[2].nodes[1]
+group[2].nodes[0].index 1
+)");
+ auto default_cfg = string_to_config(default_config);
+ auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), as_global->group.size());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, as_global->group[0].capacity, 0.00001);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, as_global->group[1].capacity, 0.00001);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, as_global->group[2].capacity, 0.00001);
+}
+
+void GlobalBucketSpaceDistributionConverterTest::global_distribution_has_same_owner_distributors_as_default() {
+ vespalib::string default_config(
+R"(redundancy 2
+ready_copies 2
+group[3]
+group[0].name "invalid"
+group[0].index "invalid"
+group[0].partitions 1|*
+group[0].nodes[0]
+group[1].name rack0
+group[1].index 0
+group[1].nodes[1]
+group[1].nodes[0].index 0
+group[2].name rack1
+group[2].index 1
+group[2].nodes[2]
+group[2].nodes[0].index 1
+group[2].nodes[1].index 2
+)");
+
+ auto default_cfg = string_to_config(default_config);
+ auto global_cfg = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg);
+
+ lib::Distribution default_distr(*default_cfg);
+ lib::Distribution global_distr(*global_cfg);
+ lib::ClusterState state("distributor:6 storage:6");
+
+ std::mt19937 rng;
+ std::uniform_int_distribution<uint64_t> d(0, UINT64_MAX);
+ for (int i = 0; i < 100; ++i) {
+ document::BucketId bucket(16, d(rng));
+ const auto default_index = default_distr.getIdealDistributorNode(state, bucket, "ui");
+ const auto global_index = global_distr.getIdealDistributorNode(state, bucket, "ui");
+ CPPUNIT_ASSERT_EQUAL(default_index, global_index);
+ }
+}
+
+} \ No newline at end of file
diff --git a/storage/src/tests/common/teststorageapp.h b/storage/src/tests/common/teststorageapp.h
index 6906d41ac47..46744ecb3c6 100644
--- a/storage/src/tests/common/teststorageapp.h
+++ b/storage/src/tests/common/teststorageapp.h
@@ -24,7 +24,6 @@
#include <vespa/storage/storageserver/framework.h>
#include <vespa/storage/frameworkimpl/component/distributorcomponentregisterimpl.h>
#include <vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
#include <vespa/storageframework/defaultimplementation/component/testcomponentregister.h>
#include <vespa/persistence/spi/persistenceprovider.h>
diff --git a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp
index a7418629f81..16d317551e0 100644
--- a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp
+++ b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp
@@ -1070,9 +1070,8 @@ TwoPhaseUpdateOperationTest::testSafePathConditionParseFailureFailsWithIllegalPa
"timestamp 0, timestamp of updated doc: 0) "
"ReturnCode(ILLEGAL_PARAMETERS, "
"Failed to parse test and set condition: "
- "Unexpected token at position 16 "
- "('==fran...c') in query 'testdoctype1."
- "san==fran...cisco',)"s,
+ "syntax error, unexpected . at column 24 when "
+ "parsing selection 'testdoctype1.san==fran...cisco')"s,
sender.getLastReply(true));
}
@@ -1096,7 +1095,8 @@ TwoPhaseUpdateOperationTest::testSafePathConditonUnknownDocTypeFailsWithIllegalP
"timestamp 0, timestamp of updated doc: 0) "
"ReturnCode(ILLEGAL_PARAMETERS, "
"Failed to parse test and set condition: "
- "Document type langbein not found)"s,
+ "Document type 'langbein' not found at column 1 "
+ "when parsing selection 'langbein.headerval=1234')"s,
sender.getLastReply(true));
}
diff --git a/storage/src/tests/distributor/visitoroperationtest.cpp b/storage/src/tests/distributor/visitoroperationtest.cpp
index 26f4fb3e784..972ccf41bfe 100644
--- a/storage/src/tests/distributor/visitoroperationtest.cpp
+++ b/storage/src/tests/distributor/visitoroperationtest.cpp
@@ -454,7 +454,8 @@ VisitorOperationTest::testInvalidOrderDocSelection()
CPPUNIT_ASSERT_EQUAL(
std::string("CreateVisitorReply(last=BucketId(0x0000000000000000)) "
"ReturnCode(ILLEGAL_PARAMETERS, Failed to parse document select "
- "string 'id.order(10,3)=1 and dummy': Document type dummy not found)"),
+ "string 'id.order(10,3)=1 and dummy': Document type 'dummy' not "
+ "found at column 22 when parsing selection 'id.order(10,3)=1 and dummy')"),
runEmptyVisitor(
createVisitorCommand("invalidOrderDoc",
id,
diff --git a/storage/src/tests/frameworkimpl/memory/CMakeLists.txt b/storage/src/tests/frameworkimpl/memory/CMakeLists.txt
deleted file mode 100644
index e5a364e02a3..00000000000
--- a/storage/src/tests/frameworkimpl/memory/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_library(storage_testmemory TEST
- SOURCES
- memorystatusviewertest.cpp
- DEPENDS
- storage
- storage_testcommon
-)
diff --git a/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp b/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp
deleted file mode 100644
index 1ad4b16751d..00000000000
--- a/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/metrics/metrics.h>
-#include <vespa/metrics/metricmanager.h>
-#include <vespa/storage/frameworkimpl/memory/memorystatusviewer.h>
-#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h>
-#include <tests/common/teststorageapp.h>
-#include <vespa/vdstestlib/cppunit/macros.h>
-#include <vespa/vespalib/util/exceptions.h>
-#include <boost/lexical_cast.hpp>
-
-namespace storage {
-
-struct MemoryStatusViewerTest : public CppUnit::TestFixture
-{
- static const int maxMemory = 1000;
- std::unique_ptr<TestServiceLayerApp> _node;
- std::unique_ptr<framework::defaultimplementation::MemoryManager> _memMan;
-
- void setUp() override;
-
- void testEmptyState();
- void testSnapshots();
-
- CPPUNIT_TEST_SUITE(MemoryStatusViewerTest);
- CPPUNIT_TEST(testEmptyState);
- CPPUNIT_TEST(testSnapshots);
- CPPUNIT_TEST_SUITE_END();
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(MemoryStatusViewerTest);
-
-void
-MemoryStatusViewerTest::setUp()
-{
- _node.reset(new TestServiceLayerApp(DiskCount(2)));
- framework::defaultimplementation::PriorityMemoryLogic* logic(
- new framework::defaultimplementation::PriorityMemoryLogic(
- _node->getClock(), maxMemory));
- logic->setMinJumpToUpdateMax(1);
- _memMan.reset(new framework::defaultimplementation::MemoryManager(
- framework::defaultimplementation::AllocationLogic::UP(logic)));
-}
-
-void
-MemoryStatusViewerTest::testEmptyState()
-{
- // Add a memory manager, and add a bit of load to it, so it's not
- // totally empty.
- StorageComponent component(_node->getComponentRegister(), "test");
-
- metrics::MetricManager mm;
- MemoryStatusViewer viewer(
- *_memMan, mm, _node->getComponentRegister());
- std::ostringstream actual;
- viewer.reportStatus(actual, framework::HttpUrlPath("/"));
- CPPUNIT_ASSERT_MATCH_REGEX(".*Plotr.LineChart.*", actual.str());
- CPPUNIT_ASSERT_MATCH_REGEX(
- ".*Current: 1970-01-01 00:00:00 Max memory 1000 SnapShot\\(Used 0, w/o cache 0\\).*",
- actual.str());
- CPPUNIT_ASSERT_MATCH_REGEX(
- ".*Last hour: na.*", actual.str());
-}
-
-namespace {
- void waitForProcessedTime(
- const MemoryStatusViewer& viewer, framework::SecondTime time,
- framework::SecondTime timeout = framework::SecondTime(30))
- {
- framework::defaultimplementation::RealClock clock;
- framework::MilliSecTime endTime(
- clock.getTimeInMillis() + timeout.getMillis());
- framework::SecondTime processedTime(0);
- while (clock.getTimeInMillis() < endTime) {
- processedTime = viewer.getProcessedTime();
- if (processedTime >= time) return;
- FastOS_Thread::Sleep(1);
- }
- std::ostringstream ost;
- ost << "Timed out waiting " << timeout << " ms for time " << time
- << " to be processed. Currently time is only processed up to "
- << processedTime;
- throw new vespalib::IllegalStateException(ost.str(), VESPA_STRLOC);
- }
-}
-
-#define ASSERT_MEMORY(output, period, maxmem, used, usedwocache) \
-{ \
- std::string::size_type _pos1_(output.find(period)); \
- std::string::size_type _pos2_(output.find("Max memory", _pos1_)); \
- std::string::size_type _pos3_(output.find("SnapShot", _pos2_)); \
- std::string _maxMemory_(output.substr(_pos2_ + 11, _pos3_ - _pos2_ - 12)); \
- std::string::size_type _pos4_(output.find(",", _pos3_)); \
- std::string _used_(output.substr(_pos3_ + 14, _pos4_ - _pos3_ - 14)); \
- std::string::size_type _pos5_(output.find(")", _pos4_)); \
- std::string _usedwo_(output.substr(_pos4_ + 12, _pos5_ - _pos4_ - 12)); \
- std::ostringstream _failure_; \
- _failure_ << "Wrong match in period " << period << " in output:\n" \
- << output << "\nFor value: "; \
- \
- CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Max memory", \
- uint64_t(maxmem), boost::lexical_cast<uint64_t>(_maxMemory_)); \
- CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Used memory", \
- uint64_t(used), boost::lexical_cast<uint64_t>(_used_)); \
- CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Used memory w/o cache", \
- uint64_t(usedwocache), boost::lexical_cast<uint64_t>(_usedwo_)); \
-}
-
-void
-MemoryStatusViewerTest::testSnapshots()
-{
- // Add a memory manager, and add a bit of load to it, so it's not
- // totally empty.
- StorageComponent component(_node->getComponentRegister(), "test");
- const framework::MemoryAllocationType putAlloc(
- component.getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType("PUT")));
- const framework::MemoryAllocationType getAlloc(
- component.getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType("GET")));
-
- framework::MemoryToken::UP put = _memMan->allocate(putAlloc, 0, 100, 80);
- framework::MemoryToken::UP get = _memMan->allocate(getAlloc, 30, 200, 50);
- framework::MemoryToken::UP get2 = _memMan->allocate(getAlloc, 70, 150, 60);
-
- metrics::MetricManager mm;
- MemoryStatusViewer viewer(*_memMan, mm, _node->getComponentRegister());
-
- _node->getClock().addSecondsToTime(1000);
- viewer.notifyThread();
- waitForProcessedTime(viewer, framework::SecondTime(1000));
-
- std::ostringstream actual;
- viewer.printDebugOutput(actual);
- //std::cerr << actual.str() << "\n";
- ASSERT_MEMORY(actual.str(), "Current", 1000, 450, 450);
- ASSERT_MEMORY(actual.str(), "Last hour", 1000, 450, 450);
- ASSERT_MEMORY(actual.str(), "Last ever", 1000, 450, 450);
-
- put = _memMan->allocate(putAlloc, 0, 50, 80);
- get = _memMan->allocate(getAlloc, 100, 140, 50);
- get2 = _memMan->allocate(getAlloc, 20, 100, 70);
-
- _node->getClock().addSecondsToTime(3600);
- viewer.notifyThread();
- waitForProcessedTime(viewer, framework::SecondTime(4600));
-
- actual.str("");
- viewer.printDebugOutput(actual);
- //std::cerr << actual.str() << "\n";
- ASSERT_MEMORY(actual.str(), "Current", 1000, 290, 290);
- ASSERT_MEMORY(actual.str(), "Last hour", 1000, 540, 540);
- ASSERT_MEMORY(actual.str(), "Last ever", 1000, 540, 540);
-
- get.reset();
-
- _node->getClock().addSecondsToTime(3600);
- viewer.notifyThread();
- waitForProcessedTime(viewer, framework::SecondTime(4600 + 3600));
-
- actual.str("");
- viewer.printDebugOutput(actual);
- //std::cerr << actual.str() << "\n";
- ASSERT_MEMORY(actual.str(), "Current", 1000, 150, 150);
- ASSERT_MEMORY(actual.str(), "Last hour", 1000, 290, 290);
- ASSERT_MEMORY(actual.str(), "Last ever", 1000, 540, 540);
-
-}
-
-} // storage
diff --git a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp
index 67b93108139..af0082fa788 100644
--- a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp
+++ b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp
@@ -231,8 +231,6 @@ struct FileStorManagerTest : public CppUnit::TestFixture {
fprintf(stderr, "%s\n", e.what());
}
_testdoctype1 = _node->getTypeRepo()->getDocumentType("testdoctype1");
- _node->getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType("VISITOR_BUFFER"));
}
void putDoc(DummyStorageLink& top,
@@ -2010,19 +2008,11 @@ FileStorManagerTest::testVisiting()
top.reset();
// Visit bucket with no split, using no selection
{
- framework::MemoryToken::UP token(
- _node->getMemoryManager().allocate(
- _node->getMemoryManager().getAllocationType(
- "VISITOR_BUFFER"),
- 16*1024,
- 16*1024,
- 127));
spi::IteratorId iterId(createIterator(top, ids[0], "true"));
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), makeDocumentBucket(ids[0]), iterId, 16*1024));
+ auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[0]), iterId, 16*1024);
top.sendDown(cmd);
top.waitForMessages(1, _waitTime);
- CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies());
std::shared_ptr<GetIterReply> reply(
std::dynamic_pointer_cast<GetIterReply>(top.getReply(0)));
CPPUNIT_ASSERT(reply.get());
@@ -2039,15 +2029,7 @@ FileStorManagerTest::testVisiting()
ids[1],
"testdoctype1.hstringval = \"John Doe\""));
while (true) {
- framework::MemoryToken::UP token(
- _node->getMemoryManager().allocate(
- _node->getMemoryManager().getAllocationType(
- "VISITOR_BUFFER"),
- 16*1024,
- 16*1024,
- 127));
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), makeDocumentBucket(ids[1]), iterId, 16*1024));
+ auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[1]), iterId, 16*1024);
top.sendDown(cmd);
top.waitForMessages(1, _waitTime);
CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies());
@@ -2078,18 +2060,10 @@ FileStorManagerTest::testVisiting()
true));
uint32_t totalDocs = 0;
while (true) {
- framework::MemoryToken::UP token(
- _node->getMemoryManager().allocate(
- _node->getMemoryManager().getAllocationType(
- "VISITOR_BUFFER"),
- 16*1024,
- 16*1024,
- 127));
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), makeDocumentBucket(ids[1]), iterId, 16*1024));
+ auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[1]), iterId, 16*1024);
top.sendDown(cmd);
top.waitForMessages(1, _waitTime);
- CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies());
std::shared_ptr<GetIterReply> reply(
std::dynamic_pointer_cast<GetIterReply>(
top.getReply(0)));
@@ -2799,15 +2773,8 @@ FileStorManagerTest::testGetIter()
// Sending a getiter request that will only visit some of the docs
spi::IteratorId iterId(createIterator(top, bid, ""));
{
- framework::MemoryToken::UP token(
- _node->getMemoryManager().allocate(
- _node->getMemoryManager().getAllocationType(
- "VISITOR_BUFFER"),
- 2048,
- 2048,
- 127));
std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), makeDocumentBucket(bid), iterId, 2048));
+ new GetIterCommand(makeDocumentBucket(bid), iterId, 2048));
top.sendDown(cmd);
top.waitForMessages(1, _waitTime);
CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies());
@@ -2837,18 +2804,10 @@ FileStorManagerTest::testGetIter()
CPPUNIT_ASSERT_EQUAL(ReturnCode(ReturnCode::OK), reply->getResult());
}
{
- framework::MemoryToken::UP token(
- _node->getMemoryManager().allocate(
- _node->getMemoryManager().getAllocationType(
- "VISITOR_BUFFER"),
- 2048,
- 2048,
- 127));
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), makeDocumentBucket(bid), iterId, 2048));
+ auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(bid), iterId, 2048);
top.sendDown(cmd);
top.waitForMessages(1, _waitTime);
- CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies());
std::shared_ptr<GetIterReply> reply(
std::dynamic_pointer_cast<GetIterReply>(
top.getReply(0)));
diff --git a/storage/src/tests/persistence/persistencetestutils.cpp b/storage/src/tests/persistence/persistencetestutils.cpp
index fcfbcd0a78b..9d3a3d5f008 100644
--- a/storage/src/tests/persistence/persistencetestutils.cpp
+++ b/storage/src/tests/persistence/persistencetestutils.cpp
@@ -11,7 +11,6 @@
#include <vespa/vespalib/util/exceptions.h>
using document::DocumentType;
-using storage::framework::defaultimplementation::AllocationLogic;
using storage::spi::test::makeSpiBucket;
using document::test::makeDocumentBucket;
diff --git a/storage/src/tests/persistence/persistencetestutils.h b/storage/src/tests/persistence/persistencetestutils.h
index ee87925a0bc..36b1fef7285 100644
--- a/storage/src/tests/persistence/persistencetestutils.h
+++ b/storage/src/tests/persistence/persistencetestutils.h
@@ -8,7 +8,6 @@
#include <vespa/storage/persistence/persistenceutil.h>
#include <vespa/storage/common/messagesender.h>
#include <vespa/storage/common/storagecomponent.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h>
#include <vespa/persistence/spi/persistenceprovider.h>
#include <vespa/persistence/dummyimpl/dummypersistence.h>
#include <vespa/document/base/testdocman.h>
diff --git a/storage/src/tests/storageserver/communicationmanagertest.cpp b/storage/src/tests/storageserver/communicationmanagertest.cpp
index 5111368dd1c..cd5dd2a01a4 100644
--- a/storage/src/tests/storageserver/communicationmanagertest.cpp
+++ b/storage/src/tests/storageserver/communicationmanagertest.cpp
@@ -6,7 +6,6 @@
#include <vespa/messagebus/rpcmessagebus.h>
#include <vespa/storageapi/message/persistence.h>
#include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h>
-#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h>
#include <tests/common/teststorageapp.h>
#include <tests/common/dummystoragelink.h>
#include <tests/common/testhelper.h>
diff --git a/storage/src/tests/storageutil/.gitignore b/storage/src/tests/storageutil/.gitignore
deleted file mode 100644
index a080232d5f3..00000000000
--- a/storage/src/tests/storageutil/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-*.So
-*.lo
-*.o
-.*.swp
-.config.log
-.depend
-.depend.NEW
-.deps
-.libs
-Makefile
-statefile*
-testrunner
-testrunner.core
diff --git a/storage/src/tests/storageutil/CMakeLists.txt b/storage/src/tests/storageutil/CMakeLists.txt
deleted file mode 100644
index 68d2517bda3..00000000000
--- a/storage/src/tests/storageutil/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_library(storage_teststorageutil TEST
- SOURCES
- charttest.cpp
- functortest.cpp
- palettetest.cpp
- DEPENDS
- storage
-)
diff --git a/storage/src/tests/storageutil/charttest.cpp b/storage/src/tests/storageutil/charttest.cpp
deleted file mode 100644
index d16f3f11747..00000000000
--- a/storage/src/tests/storageutil/charttest.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/storage/storageutil/piechart.h>
-#include <vespa/vdstestlib/cppunit/macros.h>
-#include <fstream>
-
-namespace storage {
-
-struct PieChartTest : public CppUnit::TestFixture
-{
- void testWriteHtmlFile();
-
- CPPUNIT_TEST_SUITE(PieChartTest);
- CPPUNIT_TEST(testWriteHtmlFile);
- CPPUNIT_TEST_SUITE_END();
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(PieChartTest);
-
-namespace {
- void printHtmlFile(const std::string& filename, const PieChart& chart) {
- std::ofstream out(filename.c_str());
- out << "<html>\n"
- << " <head>\n"
- << " ";
- PieChart::printHtmlHeadAdditions(out, " ");
- out << "\n <title>Pie example</title>\n"
- << " </head>\n"
- << " <body>\n"
- << " ";
- chart.printCanvas(out, 500, 400);
- out << "\n ";
- chart.printScript(out, " ");
- out << "\n </body>\n"
- << "</html>\n";
- out.close();
- }
-}
-
-void
-PieChartTest::testWriteHtmlFile()
-{
- {
- PieChart chart("mypie");
- chart.add(10, "put");
- chart.add(20, "get");
- chart.add(50, "free");
-
- printHtmlFile("piefile.html", chart);
- }
- {
- PieChart chart("mypie", PieChart::SCHEME_CUSTOM);
- chart.add(10, "put", PieChart::RED);
- chart.add(20, "get", PieChart::GREEN);
- chart.add(50, "free", PieChart::BLUE);
-
- printHtmlFile("piefile-customcols.html", chart);
- }
-}
-
-} // storage
diff --git a/storage/src/tests/storageutil/functortest.cpp b/storage/src/tests/storageutil/functortest.cpp
deleted file mode 100644
index 0fa1eeaaa8a..00000000000
--- a/storage/src/tests/storageutil/functortest.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <cppunit/extensions/HelperMacros.h>
-#include <list>
-#include <string>
-#include <algorithm>
-#include <vespa/storage/storageutil/functor.h>
-
-class Functor_Test : public CppUnit::TestFixture {
- CPPUNIT_TEST_SUITE(Functor_Test);
- CPPUNIT_TEST(testReplace);
- CPPUNIT_TEST(testDeletePointer);
- CPPUNIT_TEST_SUITE_END();
-
-public:
-
-protected:
- void testReplace();
- void testDeletePointer();
-};
-
-using namespace storage;
-using namespace std;
-
-CPPUNIT_TEST_SUITE_REGISTRATION(Functor_Test);
-
-void Functor_Test::testReplace()
-{
- string source("this.is.a.string.with.many.dots.");
- for_each(source.begin(), source.end(), Functor::Replace<char>('.', '_'));
- CPPUNIT_ASSERT_EQUAL(string("this_is_a_string_with_many_dots_"), source);
-}
-
-namespace {
-
- static int instanceCounter = 0;
-
- class TestClass {
- public:
- TestClass() { instanceCounter++; }
- ~TestClass() { instanceCounter--; }
- };
-}
-
-void Functor_Test::testDeletePointer()
-{
- list<TestClass*> mylist;
- mylist.push_back(new TestClass());
- mylist.push_back(new TestClass());
- mylist.push_back(new TestClass());
- CPPUNIT_ASSERT_EQUAL(3, instanceCounter);
- for_each(mylist.begin(), mylist.end(), Functor::DeletePointer());
- CPPUNIT_ASSERT_EQUAL(0, instanceCounter);
-}
diff --git a/storage/src/tests/storageutil/palettetest.cpp b/storage/src/tests/storageutil/palettetest.cpp
deleted file mode 100644
index a5d1b7f8b22..00000000000
--- a/storage/src/tests/storageutil/palettetest.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/storage/storageutil/palette.h>
-#include <vespa/vdstestlib/cppunit/macros.h>
-
-namespace storage {
-
-struct PaletteTest : public CppUnit::TestFixture {
- void testNormalUsage();
-
- CPPUNIT_TEST_SUITE(PaletteTest);
- CPPUNIT_TEST(testNormalUsage);
- CPPUNIT_TEST_SUITE_END();
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(PaletteTest);
-
-void
-PaletteTest::testNormalUsage()
-{
- std::ofstream out("palette.html");
- out << "<html><body>\n";
- Palette palette(75);
- palette.printHtmlTablePalette(out);
- out << "</body></html>\n";
- out.close();
-}
-
-} // storage
diff --git a/storage/src/vespa/storage/CMakeLists.txt b/storage/src/vespa/storage/CMakeLists.txt
index 69846cafde2..d854c603097 100644
--- a/storage/src/vespa/storage/CMakeLists.txt
+++ b/storage/src/vespa/storage/CMakeLists.txt
@@ -12,7 +12,6 @@ vespa_add_library(storage
$<TARGET_OBJECTS:storage_bucketmover>
$<TARGET_OBJECTS:storage_thread>
$<TARGET_OBJECTS:storage_status>
- $<TARGET_OBJECTS:storage_memory>
$<TARGET_OBJECTS:storage_component>
INSTALL lib64
DEPENDS
diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp
index 00fa5c95c9b..aa9158f9e5b 100644
--- a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp
+++ b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp
@@ -19,12 +19,13 @@
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/stllike/hash_map.hpp>
#include <vespa/config/config.h>
-#include <unordered_map>
+#include <chrono>
#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".storage.bucketdb.manager");
using document::BucketSpace;
+using namespace std::chrono_literals;
namespace storage {
@@ -33,9 +34,8 @@ BucketManager::BucketManager(const config::ConfigUri & configUri,
: StorageLinkQueued("Bucket manager", compReg),
framework::StatusReporter("bucketdb", "Bucket database"),
_configUri(configUri),
- _stateAccess(),
- _bucketDBMemoryToken(),
- _workerMonitor(),
+ _workerLock(),
+ _workerCond(),
_clusterStateLock(),
_queueProcessingLock(),
_queuedReplies(),
@@ -47,12 +47,6 @@ BucketManager::BucketManager(const config::ConfigUri & configUri,
_requestsCurrentlyProcessing(0),
_component(compReg, "bucketmanager")
{
- const framework::MemoryAllocationType& allocType(
- _component.getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType("DATABASE")));
- _bucketDBMemoryToken = _component.getMemoryManager().allocate(
- allocType, 0, 0, api::StorageMessage::HIGH);
- assert(_bucketDBMemoryToken.get() != 0);
_metrics->setDisks(_component.getDiskCount());
_component.registerStatusPage(*this);
_component.registerMetric(*_metrics);
@@ -81,7 +75,7 @@ void BucketManager::onClose()
{
// Stop internal thread such that we don't send any more messages down.
if (_thread.get() != 0) {
- _thread->interruptAndJoin(&_workerMonitor);
+ _thread->interruptAndJoin(_workerLock, _workerCond);
_thread.reset(0);
}
StorageLinkQueued::onClose();
@@ -221,8 +215,6 @@ BucketManager::updateMetrics(bool updateDocCount)
LOG(debug, "Iterating bucket database to update metrics%s%s",
updateDocCount ? "" : ", minusedbits only",
_doneInitialized ? "" : ", server is not done initializing");
- uint64_t dbMemSize = _component.getBucketSpaceRepo().getBucketMemoryUsage();
- _bucketDBMemoryToken->resize(dbMemSize, dbMemSize);
uint32_t diskCount = _component.getDiskCount();
if (!updateDocCount || _doneInitialized) {
@@ -268,7 +260,7 @@ void BucketManager::run(framework::ThreadHandle& thread)
bool didWork = false;
BucketInfoRequestMap infoReqs;
{
- vespalib::MonitorGuard monitor(_workerMonitor);
+ std::lock_guard<std::mutex> guard(_workerLock);
infoReqs.swap(_bucketInfoRequests);
}
@@ -277,12 +269,12 @@ void BucketManager::run(framework::ThreadHandle& thread)
}
{
- vespalib::MonitorGuard monitor(_workerMonitor);
+ std::unique_lock<std::mutex> guard(_workerLock);
for (const auto &req : infoReqs) {
assert(req.second.empty());
}
if (!didWork) {
- monitor.wait(1000);
+ _workerCond.wait_for(guard, 1s);
thread.registerTick(framework::WAIT_CYCLE);
} else {
thread.registerTick(framework::PROCESS_CYCLE);
@@ -393,9 +385,9 @@ bool BucketManager::onRequestBucketInfo(
LOG(debug, "Got request bucket info command");
if (cmd->getBuckets().size() == 0 && cmd->hasSystemState()) {
- vespalib::MonitorGuard monitor(_workerMonitor);
+ std::lock_guard<std::mutex> guard(_workerLock);
_bucketInfoRequests[cmd->getBucketSpace()].push_back(cmd);
- monitor.signal();
+ _workerCond.notify_all();
LOG(spam, "Scheduled request bucket info request for retrieval");
return true;
}
@@ -472,7 +464,7 @@ BucketManager::ScopedQueueDispatchGuard::~ScopedQueueDispatchGuard()
void
BucketManager::enterQueueProtectedSection()
{
- vespalib::LockGuard guard(_queueProcessingLock);
+ std::lock_guard<std::mutex> guard(_queueProcessingLock);
++_requestsCurrentlyProcessing;
}
@@ -480,7 +472,7 @@ void
BucketManager::leaveQueueProtectedSection(ScopedQueueDispatchGuard& queueGuard)
{
(void) queueGuard; // Only used to enforce guard is held while calling.
- vespalib::LockGuard guard(_queueProcessingLock);
+ std::lock_guard<std::mutex> guard(_queueProcessingLock);
assert(_requestsCurrentlyProcessing > 0);
// Full bucket info fetches may be concurrently interleaved with bucket-
// specific fetches outside of the processing thread. We only allow queued
@@ -529,7 +521,7 @@ BucketManager::processRequestBucketInfoCommands(document::BucketSpace bucketSpac
clusterState->toString().c_str(),
our_hash.c_str());
- vespalib::LockGuard lock(_clusterStateLock);
+ std::lock_guard<std::mutex> clusterStateGuard(_clusterStateLock);
for (auto it = reqs.rbegin(); it != reqs.rend(); ++it) {
// Currently small requests should not be forwarded to worker thread
assert((*it)->hasSystemState());
@@ -629,7 +621,7 @@ BucketManager::processRequestBucketInfoCommands(document::BucketSpace bucketSpac
size_t
BucketManager::bucketInfoRequestsCurrentlyProcessing() const noexcept
{
- vespalib::LockGuard guard(_queueProcessingLock);
+ std::lock_guard<std::mutex> guard(_queueProcessingLock);
return _requestsCurrentlyProcessing;
}
@@ -694,7 +686,7 @@ BucketManager::onSetSystemState(
LOG(debug, "onSetSystemState(%s)", cmd->toString().c_str());
const lib::ClusterState& state(cmd->getSystemState());
std::string unified(unifyState(state));
- vespalib::LockGuard lock(_clusterStateLock);
+ std::lock_guard<std::mutex> lock(_clusterStateLock);
if (unified != _lastUnifiedClusterState
|| state.getVersion() != _lastClusterStateSeen + 1)
{
@@ -804,7 +796,7 @@ BucketManager::enqueueIfBucketHasConflicts(const api::BucketReply::SP& reply)
{
// Should very rarely contend, since persistence replies are all sent up
// via a single dispatcher thread.
- vespalib::LockGuard guard(_queueProcessingLock);
+ std::lock_guard<std::mutex> guard(_queueProcessingLock);
if (_requestsCurrentlyProcessing == 0) {
return false; // Nothing to do here; pass through reply.
}
@@ -841,7 +833,7 @@ bool
BucketManager::enqueueAsConflictIfProcessingRequest(
const api::StorageReply::SP& reply)
{
- vespalib::LockGuard guard(_queueProcessingLock);
+ std::lock_guard<std::mutex> guard(_queueProcessingLock);
if (_requestsCurrentlyProcessing != 0) {
LOG(debug, "Enqueued %s due to concurrent RequestBucketInfo",
reply->toString().c_str());
diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.h b/storage/src/vespa/storage/bucketdb/bucketmanager.h
index 3b71230a8ed..8413c5a0f42 100644
--- a/storage/src/vespa/storage/bucketdb/bucketmanager.h
+++ b/storage/src/vespa/storage/bucketdb/bucketmanager.h
@@ -20,13 +20,14 @@
#include <vespa/storage/common/servicelayercomponent.h>
#include <vespa/storage/common/storagelinkqueued.h>
#include <vespa/storageapi/message/bucket.h>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/generic/metric/metricupdatehook.h>
#include <vespa/storageframework/generic/status/statusreporter.h>
#include <list>
#include <unordered_map>
#include <unordered_set>
+#include <mutex>
+#include <condition_variable>
namespace storage {
@@ -45,21 +46,20 @@ private:
config::ConfigUri _configUri;
uint32_t _chunkLevel;
- mutable vespalib::Lock _stateAccess;
- framework::MemoryToken::UP _bucketDBMemoryToken;
BucketInfoRequestMap _bucketInfoRequests;
/**
* We have our own thread running, which we use to send messages down.
- * Take worker monitor, add to list and signal for messages to be sent.
+ * Take worker lock, add to list and signal for messages to be sent.
*/
- mutable vespalib::Monitor _workerMonitor;
+ mutable std::mutex _workerLock;
+ std::condition_variable _workerCond;
/**
* Lock kept for access to 3 values below concerning cluster state.
*/
- vespalib::Lock _clusterStateLock;
+ std::mutex _clusterStateLock;
- vespalib::Lock _queueProcessingLock;
+ mutable std::mutex _queueProcessingLock;
using ReplyQueue = std::vector<api::StorageReply::SP>;
using ConflictingBuckets = std::unordered_set<document::BucketId,
document::BucketId::hash>;
diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.h b/storage/src/vespa/storage/bucketdb/lockablemap.h
index 03d94b27f0b..a4382ceb683 100644
--- a/storage/src/vespa/storage/bucketdb/lockablemap.h
+++ b/storage/src/vespa/storage/bucketdb/lockablemap.h
@@ -16,10 +16,12 @@
#include <map>
#include <vespa/vespalib/util/printable.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vespalib/stllike/hash_map.h>
#include <vespa/vespalib/stllike/hash_set.h>
#include <vespa/document/bucket/bucketid.h>
+#include <mutex>
+#include <condition_variable>
+#include <cassert>
namespace storage {
@@ -238,7 +240,8 @@ private:
};
Map _map;
- vespalib::Monitor _lock;
+ mutable std::mutex _lock;
+ std::condition_variable _cond;
LockIdSet _lockedKeys;
LockWaiters _lockWaiters;
@@ -247,9 +250,9 @@ private:
const char* clientId, bool haslock, bool& preExisted);
void unlock(const key_type& key);
bool findNextKey(key_type& key, mapped_type& val, const char* clientId,
- vespalib::MonitorGuard& guard);
+ std::unique_lock<std::mutex> &guard);
bool handleDecision(key_type& key, mapped_type& val, Decision decision);
- void ackquireKey(const LockId & lid, vespalib::MonitorGuard & guard);
+ void acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard);
/**
* Process up to `chunkSize` bucket database entries from--and possibly
@@ -304,7 +307,7 @@ private:
void addAndLockResults(const std::vector<BucketId::Type> keys,
const char* clientId,
std::map<BucketId, WrappedEntry>& results,
- vespalib::MonitorGuard& guard);
+ std::unique_lock<std::mutex> &guard);
};
} // storage
diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.hpp b/storage/src/vespa/storage/bucketdb/lockablemap.hpp
index f5d692139be..f370a792145 100644
--- a/storage/src/vespa/storage/bucketdb/lockablemap.hpp
+++ b/storage/src/vespa/storage/bucketdb/lockablemap.hpp
@@ -69,6 +69,7 @@ template<typename Map>
LockableMap<Map>::LockableMap()
: _map(),
_lock(),
+ _cond(),
_lockedKeys(),
_lockWaiters()
{}
@@ -80,8 +81,8 @@ template<typename Map>
bool
LockableMap<Map>::operator==(const LockableMap<Map>& other) const
{
- vespalib::LockGuard guard(_lock);
- vespalib::LockGuard guard2(other._lock);
+ std::lock_guard<std::mutex> guard(_lock);
+ std::lock_guard<std::mutex> guard2(other._lock);
return (_map == other._map);
}
@@ -89,8 +90,8 @@ template<typename Map>
bool
LockableMap<Map>::operator<(const LockableMap<Map>& other) const
{
- vespalib::LockGuard guard(_lock);
- vespalib::LockGuard guard2(other._lock);
+ std::lock_guard<std::mutex> guard(_lock);
+ std::lock_guard<std::mutex> guard2(other._lock);
return (_map < other._map);
}
@@ -98,7 +99,7 @@ template<typename Map>
typename Map::size_type
LockableMap<Map>::size() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _map.size();
}
@@ -106,17 +107,16 @@ template<typename Map>
typename Map::size_type
LockableMap<Map>::getMemoryUsage() const
{
- vespalib::MonitorGuard guard(_lock);
- return _map.getMemoryUsage()
- + _lockedKeys.getMemoryUsage()
- + sizeof(vespalib::Monitor);
+ std::lock_guard<std::mutex> guard(_lock);
+ return _map.getMemoryUsage() + _lockedKeys.getMemoryUsage() +
+ sizeof(std::mutex) + sizeof(std::condition_variable);
}
template<typename Map>
bool
LockableMap<Map>::empty() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _map.empty();
}
@@ -124,18 +124,18 @@ template<typename Map>
void
LockableMap<Map>::swap(LockableMap<Map>& other)
{
- vespalib::LockGuard guard(_lock);
- vespalib::LockGuard guard2(other._lock);
+ std::lock_guard<std::mutex> guard(_lock);
+ std::lock_guard<std::mutex> guard2(other._lock);
return _map.swap(other._map);
}
template<typename Map>
-void LockableMap<Map>::ackquireKey(const LockId & lid, vespalib::MonitorGuard & guard)
+void LockableMap<Map>::acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard)
{
if (_lockedKeys.exist(lid)) {
typename LockWaiters::Key waitId(_lockWaiters.insert(lid));
while (_lockedKeys.exist(lid)) {
- guard.wait();
+ _cond.wait(guard);
}
_lockWaiters.erase(waitId);
}
@@ -148,8 +148,8 @@ LockableMap<Map>::get(const key_type& key, const char* clientId,
bool lockIfNonExistingAndNotCreating)
{
LockId lid(key, clientId);
- vespalib::MonitorGuard guard(_lock);
- ackquireKey(lid, guard);
+ std::unique_lock<std::mutex> guard(_lock);
+ acquireKey(lid, guard);
bool preExisted = false;
typename Map::iterator it =
_map.find(key, createIfNonExisting, preExisted);
@@ -197,9 +197,9 @@ bool
LockableMap<Map>::erase(const key_type& key, const char* clientId, bool haslock)
{
LockId lid(key, clientId);
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (!haslock) {
- ackquireKey(lid, guard);
+ acquireKey(lid, guard);
}
#ifdef ENABLE_BUCKET_OPERATION_LOGGING
debug::logBucketDbErase(key, debug::TypeTag<mapped_type>());
@@ -213,9 +213,9 @@ LockableMap<Map>::insert(const key_type& key, const mapped_type& value,
const char* clientId, bool haslock, bool& preExisted)
{
LockId lid(key, clientId);
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (!haslock) {
- ackquireKey(lid, guard);
+ acquireKey(lid, guard);
}
#ifdef ENABLE_BUCKET_OPERATION_LOGGING
debug::logBucketDbInsert(key, value);
@@ -227,7 +227,7 @@ template<typename Map>
void
LockableMap<Map>::clear()
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_map.clear();
}
@@ -235,13 +235,13 @@ template<typename Map>
bool
LockableMap<Map>::findNextKey(key_type& key, mapped_type& val,
const char* clientId,
- vespalib::MonitorGuard& guard)
+ std::unique_lock<std::mutex> &guard)
{
// Wait for next value to unlock.
typename Map::iterator it(_map.lower_bound(key));
while (it != _map.end() && _lockedKeys.exist(LockId(it->first, ""))) {
typename LockWaiters::Key waitId(_lockWaiters.insert(LockId(it->first, clientId)));
- guard.wait();
+ _cond.wait(guard);
_lockWaiters.erase(waitId);
it = _map.lower_bound(key);
}
@@ -279,16 +279,16 @@ LockableMap<Map>::each(Functor& functor, const char* clientId,
mapped_type val;
Decision decision;
{
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (findNextKey(key, val, clientId, guard) || key > last) return;
_lockedKeys.insert(LockId(key, clientId));
}
try{
while (true) {
decision = functor(const_cast<const key_type&>(key), val);
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
_lockedKeys.erase(LockId(key, clientId));
- guard.broadcast();
+ _cond.notify_all();
if (handleDecision(key, val, decision)) return;
++key;
if (findNextKey(key, val, clientId, guard) || key > last) return;
@@ -297,9 +297,9 @@ LockableMap<Map>::each(Functor& functor, const char* clientId,
} catch (...) {
// Assuming only the functor call can throw exceptions, we need
// to unlock the current key before exiting
- vespalib::MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_lockedKeys.erase(LockId(key, clientId));
- guard.broadcast();
+ _cond.notify_all();
throw;
}
}
@@ -314,16 +314,16 @@ LockableMap<Map>::each(const Functor& functor, const char* clientId,
mapped_type val;
Decision decision;
{
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
if (findNextKey(key, val, clientId, guard) || key > last) return;
_lockedKeys.insert(LockId(key, clientId));
}
try{
while (true) {
decision = functor(const_cast<const key_type&>(key), val);
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
_lockedKeys.erase(LockId(key, clientId));
- guard.broadcast();
+ _cond.notify_all();
if (handleDecision(key, val, decision)) return;
++key;
if (findNextKey(key, val, clientId, guard) || key > last) return;
@@ -332,9 +332,9 @@ LockableMap<Map>::each(const Functor& functor, const char* clientId,
} catch (...) {
// Assuming only the functor call can throw exceptions, we need
// to unlock the current key before exiting
- vespalib::MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_lockedKeys.erase(LockId(key, clientId));
- guard.broadcast();
+ _cond.notify_all();
throw;
}
}
@@ -347,7 +347,7 @@ LockableMap<Map>::all(Functor& functor, const char* clientId,
{
key_type key = first;
mapped_type val;
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
while (true) {
if (findNextKey(key, val, clientId, guard) || key > last) return;
Decision d(functor(const_cast<const key_type&>(key), val));
@@ -364,7 +364,7 @@ LockableMap<Map>::all(const Functor& functor, const char* clientId,
{
key_type key = first;
mapped_type val;
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
while (true) {
if (findNextKey(key, val, clientId, guard) || key > last) return;
Decision d(functor(const_cast<const key_type&>(key), val));
@@ -383,7 +383,7 @@ LockableMap<Map>::processNextChunk(Functor& functor,
const uint32_t chunkSize)
{
mapped_type val;
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
for (uint32_t processed = 0; processed < chunkSize; ++processed) {
if (findNextKey(key, val, clientId, guard)) {
return false;
@@ -422,7 +422,7 @@ void
LockableMap<Map>::print(std::ostream& out, bool verbose,
const std::string& indent) const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
out << "LockableMap {\n" << indent << " ";
if (verbose) {
@@ -462,9 +462,9 @@ template<typename Map>
void
LockableMap<Map>::unlock(const key_type& key)
{
- vespalib::MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_lockedKeys.erase(LockId(key, ""));
- guard.broadcast();
+ _cond.notify_all();
}
/**
@@ -550,7 +550,7 @@ LockableMap<Map>::addAndLockResults(
const std::vector<BucketId::Type> keys,
const char* clientId,
std::map<BucketId, WrappedEntry>& results,
- vespalib::MonitorGuard& guard)
+ std::unique_lock<std::mutex> &guard)
{
// Wait until all buckets are free to be added, then add them all.
while (true) {
@@ -567,7 +567,7 @@ LockableMap<Map>::addAndLockResults(
if (!allOk) {
typename LockWaiters::Key waitId(_lockWaiters.insert(LockId(waitingFor, clientId)));
- guard.wait();
+ _cond.wait(guard);
_lockWaiters.erase(waitId);
} else {
for (uint32_t i=0; i<keys.size(); i++) {
@@ -593,7 +593,7 @@ LockableMap<Map>::createAppropriateBucket(
const char* clientId,
const BucketId& bucket)
{
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
typename Map::const_iterator iter = _map.lower_bound(bucket.toKey());
// Find the two buckets around the possible new bucket. The new
@@ -613,7 +613,7 @@ LockableMap<Map>::createAppropriateBucket(
BucketId::Type key = newBucket.stripUnused().toKey();
LockId lid(key, clientId);
- ackquireKey(lid, guard);
+ acquireKey(lid, guard);
bool preExisted;
typename Map::iterator it = _map.find(key, true, preExisted);
_lockedKeys.insert(LockId(key, clientId));
@@ -625,7 +625,7 @@ std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry>
LockableMap<Map>::getContained(const BucketId& bucket,
const char* clientId)
{
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
std::map<BucketId, WrappedEntry> results;
BucketId result;
@@ -718,7 +718,7 @@ std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry>
LockableMap<Map>::getAll(const BucketId& bucket, const char* clientId,
const BucketId& sibling)
{
- vespalib::MonitorGuard guard(_lock);
+ std::unique_lock<std::mutex> guard(_lock);
std::map<BucketId, WrappedEntry> results;
std::vector<BucketId::Type> keys;
@@ -734,7 +734,7 @@ template<typename Map>
bool
LockableMap<Map>::isConsistent(const typename LockableMap<Map>::WrappedEntry& entry)
{
- vespalib::MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
BucketId sibling(0);
std::vector<BucketId::Type> keys;
@@ -750,7 +750,7 @@ template<typename Map>
void
LockableMap<Map>::showLockClients(vespalib::asciistream & out) const
{
- vespalib::MonitorGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
out << "Currently grabbed locks:";
for (typename LockIdSet::const_iterator it = _lockedKeys.begin();
it != _lockedKeys.end(); ++it)
diff --git a/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp b/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp
index 5ac4c4b1ee7..ad56a4083f8 100644
--- a/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp
+++ b/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp
@@ -4,6 +4,7 @@
#include <vespa/storage/common/bucketoperationlogger.h>
#include <vespa/vespalib/util/backtrace.h>
#include <ostream>
+#include <cassert>
#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".mapbucketdatabase");
diff --git a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp
index fc2c2066b6f..1bc473308a4 100644
--- a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp
+++ b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp
@@ -13,11 +13,13 @@
#include <vespa/vespalib/stllike/hash_map.hpp>
#include <vespa/config/helper/configgetter.hpp>
#include <iomanip>
+#include <chrono>
#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".storage.bucketdb.initializer");
using document::BucketSpace;
+using namespace std::chrono_literals;
namespace storage {
@@ -117,10 +119,11 @@ StorageBucketDBInitializer::Metrics::Metrics(framework::Component& component)
StorageBucketDBInitializer::Metrics::~Metrics() {}
StorageBucketDBInitializer::GlobalState::GlobalState()
- : _insertedCount(0), _infoReadCount(0),
- _infoSetByLoad(0), _dirsListed(0), _dirsToList(0),
- _gottenInitProgress(false), _doneListing(false),
- _doneInitializing(false)
+ : _lists(), _joins(), _infoRequests(), _replies(),
+ _insertedCount(0), _infoReadCount(0),
+ _infoSetByLoad(0), _dirsListed(0), _dirsToList(0),
+ _gottenInitProgress(false), _doneListing(false),
+ _doneInitializing(false), _workerLock(), _workerCond(), _replyLock()
{ }
StorageBucketDBInitializer::GlobalState::~GlobalState() { }
@@ -183,7 +186,7 @@ void
StorageBucketDBInitializer::onClose()
{
if (_system._thread.get() != 0) {
- _system._thread->interruptAndJoin(&_state._workerMonitor);
+ _system._thread->interruptAndJoin(_state._workerLock, _state._workerCond);
_system._thread.reset(0);
}
}
@@ -191,11 +194,11 @@ StorageBucketDBInitializer::onClose()
void
StorageBucketDBInitializer::run(framework::ThreadHandle& thread)
{
- vespalib::MonitorGuard monitor(_state._workerMonitor);
+ std::unique_lock<std::mutex> guard(_state._workerLock);
while (!thread.interrupted() && !_state._doneInitializing) {
std::list<api::StorageMessage::SP> replies;
{
- vespalib::LockGuard lock(_state._replyLock);
+ std::lock_guard<std::mutex> replyGuard(_state._replyLock);
_state._replies.swap(replies);
}
for (std::list<api::StorageMessage::SP>::iterator it = replies.begin();
@@ -218,7 +221,7 @@ StorageBucketDBInitializer::run(framework::ThreadHandle& thread)
updateInitProgress();
}
if (replies.empty()) {
- monitor.wait(10);
+ _state._workerCond.wait_for(guard, 10ms);
thread.registerTick(framework::WAIT_CYCLE);
} else {
thread.registerTick(framework::PROCESS_CYCLE);
@@ -258,7 +261,7 @@ void
StorageBucketDBInitializer::reportHtmlStatus(
std::ostream& out, const framework::HttpUrlPath&) const
{
- vespalib::Monitor monitor(_state._workerMonitor);
+ std::lock_guard<std::mutex> guard(_state._workerLock);
out << "\n <h2>Config</h2>\n"
<< " <table>\n"
<< " <tr><td>Max pending info reads per disk</td><td>"
@@ -583,7 +586,7 @@ StorageBucketDBInitializer::onInternalReply(
case ReadBucketInfoReply::ID:
case InternalBucketJoinReply::ID:
{
- vespalib::LockGuard lock(_state._replyLock);
+ std::lock_guard<std::mutex> guard(_state._replyLock);
_state._replies.push_back(reply);
return true;
}
diff --git a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h
index 57b95e14f48..642fe43ad8a 100644
--- a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h
+++ b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h
@@ -47,11 +47,12 @@
#include <vespa/storageframework/generic/status/htmlstatusreporter.h>
#include <vespa/storageframework/generic/clock/timer.h>
#include <vespa/vespalib/stllike/hash_map.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/vdslib/state/nodestate.h>
#include <vespa/config/subscription/configuri.h>
#include <list>
#include <unordered_map>
+#include <mutex>
+#include <condition_variable>
namespace storage {
@@ -120,9 +121,10 @@ class StorageBucketDBInitializer : public StorageLink,
// This lock is held while the worker thread is working, such that
// status retrieval can lock it. Listing part only grabs it when
// needed to supporting listing in multiple threads
- vespalib::Monitor _workerMonitor;
+ mutable std::mutex _workerLock;
+ std::condition_variable _workerCond;
// This lock protects the reply list.
- vespalib::Monitor _replyLock;
+ std::mutex _replyLock;
GlobalState();
~GlobalState();
diff --git a/storage/src/vespa/storage/common/CMakeLists.txt b/storage/src/vespa/storage/common/CMakeLists.txt
index b98058b3c3d..c53aead2ba2 100644
--- a/storage/src/vespa/storage/common/CMakeLists.txt
+++ b/storage/src/vespa/storage/common/CMakeLists.txt
@@ -6,6 +6,7 @@ vespa_add_library(storage_common OBJECT
content_bucket_space.cpp
content_bucket_space_repo.cpp
distributorcomponent.cpp
+ global_bucket_space_distribution_converter.cpp
messagebucket.cpp
messagesender.cpp
servicelayercomponent.cpp
diff --git a/storage/src/vespa/storage/common/bucketoperationlogger.cpp b/storage/src/vespa/storage/common/bucketoperationlogger.cpp
index d6df7f928e2..905b704409f 100644
--- a/storage/src/vespa/storage/common/bucketoperationlogger.cpp
+++ b/storage/src/vespa/storage/common/bucketoperationlogger.cpp
@@ -34,7 +34,7 @@ BucketOperationLogger::log(const document::BucketId& id,
bool hasError = false;
{
- vespalib::LockGuard lock(_logLock);
+ std::lock_guard<std:.mutex> guard(_logLock);
BucketMapType::iterator i = _bucketMap.lower_bound(id);
if (i != _bucketMap.end() && i->first == id) {
if (i->second._history.size() >= MAX_ENTRIES) {
@@ -145,7 +145,7 @@ void
BucketOperationLogger::dumpHistoryToLog(const document::BucketId& id) const
{
LogWarnAppender handler;
- vespalib::LockGuard lock(_logLock);
+ std::lock_guard<std::mutex> guard(_logLock);
processHistory(*this, id, handler);
}
@@ -153,7 +153,7 @@ vespalib::string
BucketOperationLogger::getHistory(const document::BucketId& id) const
{
LogStringBuilder handler;
- vespalib::LockGuard lock(_logLock);
+ std::lock_guard<std::mutex> lock(_logLock);
processHistory(*this, id, handler);
return handler.ss.str();
}
@@ -167,7 +167,7 @@ BucketOperationLogger::searchBucketHistories(
ss << "<ul>\n";
// This may block for a while... Assuming such searches run when system
// is otherwise idle.
- vespalib::LockGuard lock(_logLock);
+ std::lock_guard<std::mutex> guard(_logLock);
for (BucketMapType::const_iterator
bIt(_bucketMap.begin()), bEnd(_bucketMap.end());
bIt != bEnd; ++bIt)
diff --git a/storage/src/vespa/storage/common/bucketoperationlogger.h b/storage/src/vespa/storage/common/bucketoperationlogger.h
index dce9334a9cf..af4b539a4c8 100644
--- a/storage/src/vespa/storage/common/bucketoperationlogger.h
+++ b/storage/src/vespa/storage/common/bucketoperationlogger.h
@@ -2,10 +2,10 @@
#pragma once
#include <vespa/vespalib/stllike/string.h>
-#include <vespa/vespalib/util/sync.h>
#include <vespa/document/bucket/bucketid.h>
#include <map>
#include <list>
+#include <mutex>
/**
* Enable this to log most slotfile operations (such as all mutations) as
@@ -85,7 +85,7 @@ struct BucketOperationLogger
typedef std::map<document::BucketId, State> BucketMapType;
- vespalib::Lock _logLock;
+ std::mutex _logLock;
BucketMapType _bucketMap;
void log(const document::BucketId& id,
diff --git a/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp
new file mode 100644
index 00000000000..d8a3dd4780f
--- /dev/null
+++ b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp
@@ -0,0 +1,157 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "global_bucket_space_distribution_converter.h"
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vdslib/distribution/distribution_config_util.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <cassert>
+#include <map>
+#include <memory>
+
+namespace storage {
+
+using DistributionConfig = vespa::config::content::StorDistributionConfig;
+using DistributionConfigBuilder = vespa::config::content::StorDistributionConfigBuilder;
+
+namespace {
+
+struct Group {
+ uint16_t nested_leaf_count{0};
+ std::map<uint16_t, std::unique_ptr<Group>> sub_groups;
+};
+
+void set_distribution_invariant_config_fields(DistributionConfigBuilder& builder, const DistributionConfig& source) {
+ builder.diskDistribution = source.diskDistribution;
+ builder.distributorAutoOwnershipTransferOnWholeGroupDown = true;
+ builder.activePerLeafGroup = true;
+ // TODO consider how to best support n-of-m replication for global docs
+ builder.ensurePrimaryPersisted = true;
+ builder.initialRedundancy = 0;
+}
+
+const Group& find_non_root_group_by_index(const vespalib::string& index, const Group& root) {
+ auto path = lib::DistributionConfigUtil::getGroupPath(index);
+ auto* node = &root;
+ for (auto idx : path) {
+ auto child_iter = node->sub_groups.find(idx);
+ assert(child_iter != node->sub_groups.end());
+ node = child_iter->second.get();
+ }
+ return *node;
+}
+
+vespalib::string sub_groups_to_partition_spec(const Group& parent) {
+ vespalib::asciistream partitions;
+ // In case of a flat cluster config, this ends up with a partition spec of '*',
+ // which is fine. It basically means "put all replicas in this group", which
+ // happens to be exactly what we want.
+ for (auto& child : parent.sub_groups) {
+ partitions << child.second->nested_leaf_count << '|';
+ }
+ partitions << '*';
+ return partitions.str();
+}
+
+bool is_leaf_group(const DistributionConfigBuilder::Group& g) noexcept {
+ return !g.nodes.empty();
+}
+
+void insert_new_group_into_tree(
+ std::unique_ptr<Group> new_group,
+ const DistributionConfigBuilder::Group& config_source_group,
+ Group& root) {
+ const auto path = lib::DistributionConfigUtil::getGroupPath(config_source_group.index);
+ assert(!path.empty());
+
+ Group* parent = &root;
+ for (size_t i = 0; i < path.size(); ++i) {
+ const auto idx = path[i];
+ parent->nested_leaf_count += config_source_group.nodes.size(); // Empty if added group is not a leaf.
+ auto g_iter = parent->sub_groups.find(idx);
+ if (g_iter != parent->sub_groups.end()) {
+ assert(i != path.size() - 1);
+ parent = g_iter->second.get();
+ } else {
+ assert(i == path.size() - 1); // Only valid case for last item in path.
+ parent->sub_groups.emplace(path.back(), std::move(new_group));
+ }
+ }
+}
+
+void build_transformed_root_group(DistributionConfigBuilder& builder,
+ const DistributionConfigBuilder::Group& config_source_root,
+ const Group& parsed_root) {
+ DistributionConfigBuilder::Group new_root(config_source_root);
+ new_root.partitions = sub_groups_to_partition_spec(parsed_root);
+ builder.group.emplace_back(std::move(new_root));
+}
+
+void build_transformed_non_root_group(DistributionConfigBuilder& builder,
+ const DistributionConfigBuilder::Group& config_source_group,
+ const Group& parsed_root) {
+ DistributionConfigBuilder::Group new_group(config_source_group);
+ if (!is_leaf_group(config_source_group)) { // Partition specs only apply to inner nodes
+ const auto& g = find_non_root_group_by_index(config_source_group.index, parsed_root);
+ new_group.partitions = sub_groups_to_partition_spec(g);
+ }
+ builder.group.emplace_back(std::move(new_group));
+}
+
+std::unique_ptr<Group> create_group_tree_from_config(const DistributionConfig& source) {
+ std::unique_ptr<Group> root;
+ for (auto& g : source.group) {
+ auto new_group = std::make_unique<Group>();
+ assert(g.nodes.size() < UINT16_MAX);
+ new_group->nested_leaf_count = static_cast<uint16_t>(g.nodes.size());
+ if (root) {
+ insert_new_group_into_tree(std::move(new_group), g, *root);
+ } else {
+ root = std::move(new_group);
+ }
+ }
+ return root;
+}
+
+/* Even though groups are inherently hierarchical, the config is a flat array with a
+ * hierarchy bolted on through the use of (more or less) "multi-dimensional" index strings.
+ * Index string of root group is always "invalid" (or possibly some other string that cannot
+ * be interpreted as a dot-separated tree node path). Other groups have an index of the
+ * form "X.Y.Z", where Z is the group's immediate parent index, Y is Z's parent and so on. Just
+ * stating Z itself is not sufficient to uniquely identify the group, as group indices are
+ * not unique _across_ groups. For indices "0.1" and "1.1", the trailing "1" refers to 2
+ * distinct groups, as they have different parents.
+ *
+ * It may be noted that the group index strings do _not_ include the root group, so we
+ * have to always implicitly include it ourselves.
+ *
+ * Config groups are ordered so that when a group is encountered, all its parents (and
+ * transitively, its parents again etc) have already been processed. This directly
+ * implies that the root group is always the first group present in the config.
+ */
+void build_global_groups(DistributionConfigBuilder& builder, const DistributionConfig& source) {
+ assert(!source.group.empty()); // TODO gracefully handle empty config?
+ auto root = create_group_tree_from_config(source);
+
+ auto g_iter = source.group.begin();
+ const auto g_end = source.group.end();
+ build_transformed_root_group(builder, *g_iter, *root);
+ ++g_iter;
+ for (; g_iter != g_end; ++g_iter) {
+ build_transformed_non_root_group(builder, *g_iter, *root);
+ }
+
+ builder.redundancy = root->nested_leaf_count;
+ builder.readyCopies = builder.redundancy;
+}
+
+} // anon ns
+
+std::shared_ptr<DistributionConfig>
+GlobalBucketSpaceDistributionConverter::convert_to_global(const DistributionConfig& source) {
+ DistributionConfigBuilder builder;
+ set_distribution_invariant_config_fields(builder, source);
+ build_global_groups(builder, source);
+ return std::make_shared<DistributionConfig>(builder);
+}
+
+} \ No newline at end of file
diff --git a/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h
new file mode 100644
index 00000000000..32a43b3081e
--- /dev/null
+++ b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vdslib/distribution/distribution.h>
+#include <vespa/config-stor-distribution.h>
+#include <memory>
+
+namespace storage {
+
+struct GlobalBucketSpaceDistributionConverter {
+ using DistributionConfig = vespa::config::content::StorDistributionConfig;
+ static std::shared_ptr<DistributionConfig> convert_to_global(const DistributionConfig&);
+};
+
+} \ No newline at end of file
diff --git a/storage/src/vespa/storage/common/statusmetricconsumer.cpp b/storage/src/vespa/storage/common/statusmetricconsumer.cpp
index de44116b316..d458a821e02 100644
--- a/storage/src/vespa/storage/common/statusmetricconsumer.cpp
+++ b/storage/src/vespa/storage/common/statusmetricconsumer.cpp
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "statusmetricconsumer.h"
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/generic/status/htmlstatusreporter.h>
#include <boost/assign.hpp>
#include <boost/lexical_cast.hpp>
@@ -26,17 +25,8 @@ StatusMetricConsumer::StatusMetricConsumer(
_component(compReg, "statusmetricsconsumer"),
_name(name),
_startTime(_component.getClock().getTimeInSeconds()),
- _processedTime(0),
- _metricMemoryToken()
+ _processedTime(0)
{
- const framework::MemoryAllocationType& allocType(
- _component.getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType(
- "METRICS", framework::MemoryAllocationType::FORCE_ALLOCATE)
- ));
- _metricMemoryToken = _component.getMemoryManager().allocate(
- allocType, 0, 0, api::StorageMessage::HIGH);
- assert(_metricMemoryToken.get() != 0);
LOG(debug, "Started metrics consumer");
setlocale(LC_NUMERIC, "");
_component.registerMetricUpdateHook(*this, framework::SecondTime(3600));
@@ -51,8 +41,7 @@ void
StatusMetricConsumer::updateMetrics(const MetricLockGuard & guard)
{
metrics::MemoryConsumption::UP mc(_manager.getMemoryConsumption(guard));
- uint32_t usage = mc->getTotalMemoryUsage();
- _metricMemoryToken->resize(usage, usage);
+ // TODO is this hook needed anymore?
}
vespalib::string
diff --git a/storage/src/vespa/storage/common/statusmetricconsumer.h b/storage/src/vespa/storage/common/statusmetricconsumer.h
index e3bae51cf99..6f93f51cfdf 100644
--- a/storage/src/vespa/storage/common/statusmetricconsumer.h
+++ b/storage/src/vespa/storage/common/statusmetricconsumer.h
@@ -53,7 +53,6 @@ private:
vespalib::Monitor _waiter;
framework::SecondTime _startTime;
framework::SecondTime _processedTime;
- std::unique_ptr<framework::MemoryToken> _metricMemoryToken;
void writeXmlTags(std::ostream& out,
const vespalib::StringTokenizer& name,
diff --git a/storage/src/vespa/storage/common/storagecomponent.cpp b/storage/src/vespa/storage/common/storagecomponent.cpp
index a519e39e2ed..bf387240dc5 100644
--- a/storage/src/vespa/storage/common/storagecomponent.cpp
+++ b/storage/src/vespa/storage/common/storagecomponent.cpp
@@ -28,14 +28,14 @@ StorageComponent::setNodeInfo(vespalib::stringref clusterName,
void
StorageComponent::setDocumentTypeRepo(DocumentTypeRepoSP repo)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_docTypeRepo = repo;
}
void
StorageComponent::setLoadTypes(LoadTypeSetSP loadTypes)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_loadTypes = loadTypes;
}
@@ -57,14 +57,14 @@ StorageComponent::setBucketIdFactory(const document::BucketIdFactory& factory)
void
StorageComponent::setDistribution(DistributionSP distribution)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
_distribution = distribution;
}
void
StorageComponent::setNodeStateUpdater(NodeStateUpdater& updater)
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (_nodeStateUpdater != 0) {
throw vespalib::IllegalStateException(
"Node state updater is already set", VESPA_STRLOC);
@@ -87,7 +87,7 @@ StorageComponent::StorageComponent(StorageComponentRegister& compReg,
NodeStateUpdater&
StorageComponent::getStateUpdater() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
if (_nodeStateUpdater == 0) {
throw vespalib::IllegalStateException(
"Component need node state updater at this time, but it has "
@@ -114,21 +114,21 @@ StorageComponent::getPriority(const documentapi::LoadType& lt) const
StorageComponent::DocumentTypeRepoSP
StorageComponent::getTypeRepo() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _docTypeRepo;
}
StorageComponent::LoadTypeSetSP
StorageComponent::getLoadTypes() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _loadTypes;
}
StorageComponent::DistributionSP
StorageComponent::getDistribution() const
{
- vespalib::LockGuard guard(_lock);
+ std::lock_guard<std::mutex> guard(_lock);
return _distribution;
}
diff --git a/storage/src/vespa/storage/common/storagecomponent.h b/storage/src/vespa/storage/common/storagecomponent.h
index f34b6ace745..d469540b55f 100644
--- a/storage/src/vespa/storage/common/storagecomponent.h
+++ b/storage/src/vespa/storage/common/storagecomponent.h
@@ -35,7 +35,7 @@
#include <vespa/storageframework/generic/component/componentregister.h>
#include <vespa/document/bucket/bucketidfactory.h>
#include <vespa/vdslib/state/node.h>
-#include <vespa/vespalib/util/sync.h>
+#include <mutex>
namespace vespa { namespace config { namespace content { namespace core {
namespace internal {
@@ -113,7 +113,7 @@ private:
document::BucketIdFactory _bucketIdFactory;
DistributionSP _distribution;
NodeStateUpdater* _nodeStateUpdater;
- vespalib::Lock _lock;
+ mutable std::mutex _lock;
};
struct StorageComponentRegister : public virtual framework::ComponentRegister
diff --git a/storage/src/vespa/storage/common/storagelinkqueued.h b/storage/src/vespa/storage/common/storagelinkqueued.h
index b7891cc48dc..ee5573bcc22 100644
--- a/storage/src/vespa/storage/common/storagelinkqueued.h
+++ b/storage/src/vespa/storage/common/storagelinkqueued.h
@@ -19,6 +19,8 @@
#include <vespa/vespalib/util/document_runnable.h>
#include <deque>
#include <limits>
+#include <mutex>
+#include <condition_variable>
namespace storage {
@@ -75,7 +77,8 @@ private:
protected:
StorageLinkQueued& _parent;
unsigned int _maxQueueSize;
- vespalib::Monitor _sync;
+ std::mutex _sync;
+ std::condition_variable _syncCond;
std::deque< std::shared_ptr<Message> > _messages;
bool _replyDispatcher;
std::unique_ptr<framework::Component> _component;
@@ -92,11 +95,6 @@ private:
void add(const std::shared_ptr<Message>&);
void flush();
- // You can use the given functions if you need to keep the
- // dispatcher thread locked while you process a message. Bucket
- // manager does this during bucket dumps
- vespalib::Monitor& getMonitor() { return _sync; }
- void addWithoutLocking(const std::shared_ptr<Message>&);
virtual void send(const std::shared_ptr<Message> & ) = 0;
};
diff --git a/storage/src/vespa/storage/common/storagelinkqueued.hpp b/storage/src/vespa/storage/common/storagelinkqueued.hpp
index b34ce58bbb7..22c5e9ba5f2 100644
--- a/storage/src/vespa/storage/common/storagelinkqueued.hpp
+++ b/storage/src/vespa/storage/common/storagelinkqueued.hpp
@@ -7,6 +7,7 @@
#include <vespa/storageframework/generic/component/component.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <sstream>
+#include <chrono>
namespace storage {
@@ -16,8 +17,8 @@ StorageLinkQueued::Dispatcher<Message>::terminate() {
if (_thread.get()) {
_thread->interrupt();
{
- vespalib::MonitorGuard sync(_sync);
- sync.signal();
+ std::lock_guard<std::mutex> guard(_sync);
+ _syncCond.notify_one();
}
_thread->join();
_thread.reset(0);
@@ -29,6 +30,7 @@ StorageLinkQueued::Dispatcher<Message>::Dispatcher(StorageLinkQueued& parent, un
: _parent(parent),
_maxQueueSize(maxQueueSize),
_sync(),
+ _syncCond(),
_messages(),
_replyDispatcher(replyDispatcher)
{
@@ -58,34 +60,28 @@ template<typename Message>
void StorageLinkQueued::Dispatcher<Message>::add(
const std::shared_ptr<Message>& m)
{
- vespalib::MonitorGuard sync(_sync);
+ using namespace std::chrono_literals;
+ std::unique_lock<std::mutex> guard(_sync);
if (_thread.get() == 0) start();
while ((_messages.size() > _maxQueueSize) && !_thread->interrupted()) {
- sync.wait(100);
+ _syncCond.wait_for(guard, 100ms);
}
_messages.push_back(m);
- sync.signal();
-}
-
-template<typename Message>
-void StorageLinkQueued::Dispatcher<Message>::addWithoutLocking(
- const std::shared_ptr<Message>& m)
-{
- if (_thread.get() == 0) start();
- _messages.push_back(m);
+ _syncCond.notify_one();
}
template<typename Message>
void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h)
{
+ using namespace std::chrono_literals;
while (!h.interrupted()) {
h.registerTick(framework::PROCESS_CYCLE);
std::shared_ptr<Message> message;
{
- vespalib::MonitorGuard sync(_sync);
+ std::unique_lock<std::mutex> guard(_sync);
while (!h.interrupted() && _messages.empty()) {
- sync.wait(100);
+ _syncCond.wait_for(guard, 100ms);
h.registerTick(framework::WAIT_CYCLE);
}
if (h.interrupted()) break;
@@ -104,9 +100,9 @@ void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h)
{
// Since flush() only waits for stack to be empty, we must
// pop stack AFTER send have been called.
- vespalib::MonitorGuard sync(_sync);
+ std::lock_guard<std::mutex> guard(_sync);
_messages.pop_front();
- sync.signal();
+ _syncCond.notify_one();
}
}
_parent.logDebug("Finished storage link queued thread");
@@ -115,9 +111,10 @@ void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h)
template<typename Message>
void StorageLinkQueued::Dispatcher<Message>::flush()
{
- vespalib::MonitorGuard sync(_sync);
+ using namespace std::chrono_literals;
+ std::unique_lock<std::mutex> guard(_sync);
while (!_messages.empty()) {
- sync.wait(100);
+ _syncCond.wait_for(guard, 100ms);
}
}
diff --git a/storage/src/vespa/storage/config/.gitignore b/storage/src/vespa/storage/config/.gitignore
index 621aa4f624a..5d9aa323622 100644
--- a/storage/src/vespa/storage/config/.gitignore
+++ b/storage/src/vespa/storage/config/.gitignore
@@ -9,3 +9,5 @@ config-stor-*.cpp
config-stor-*.h
/config-rpc-provider.cpp
/config-rpc-provider.h
+/config-bucketspaces.cpp
+/config-bucketspaces.h
diff --git a/storage/src/vespa/storage/config/bucketspaces.def b/storage/src/vespa/storage/config/bucketspaces.def
index 3ed1abba0b4..4db107ec1ee 100644
--- a/storage/src/vespa/storage/config/bucketspaces.def
+++ b/storage/src/vespa/storage/config/bucketspaces.def
@@ -9,3 +9,6 @@ documenttype[].name string
## The bucket space this document type belongs to.
documenttype[].bucketspace string
+
+## Switch to enable multiple bucket spaces in content layer and content nodes.
+enable_multiple_bucket_spaces bool default=false
diff --git a/storage/src/vespa/storage/distributor/bucketdbupdater.h b/storage/src/vespa/storage/distributor/bucketdbupdater.h
index a3c9804c2b4..29e8d3f6221 100644
--- a/storage/src/vespa/storage/distributor/bucketdbupdater.h
+++ b/storage/src/vespa/storage/distributor/bucketdbupdater.h
@@ -13,7 +13,6 @@
#include <vespa/vdslib/state/clusterstate.h>
#include <vespa/storage/common/storagelink.h>
#include <vespa/storageframework/generic/clock/timer.h>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageapi/messageapi/messagehandler.h>
#include <set>
#include <deque>
diff --git a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h
index e8480902549..8d31a228543 100644
--- a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h
+++ b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h
@@ -6,7 +6,6 @@
#include <vespa/storageapi/messageapi/storagereply.h>
#include <vespa/storageapi/messageapi/maintenancecommand.h>
#include <vespa/document/bucket/bucketid.h>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
namespace storage::distributor {
@@ -229,7 +228,6 @@ protected:
bool _ok;
api::StorageMessage::Priority _priority;
- framework::MemoryToken::UP _memoryToken;
/**
* Checks if the given bucket is blocked by any pending messages to any
diff --git a/storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt b/storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt
deleted file mode 100644
index 2a91ead2b60..00000000000
--- a/storage/src/vespa/storage/frameworkimpl/memory/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_library(storage_memory OBJECT
- SOURCES
- memorystatusviewer.cpp
- DEPENDS
- AFTER
- storage_storageconfig
-)
diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h b/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h
deleted file mode 100644
index fd47bec1424..00000000000
--- a/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::MemorySnapshotList
- *
- * \brief Holds a historic list of MemoryStates.
- *
- */
-
-#pragma once
-
-#include <storage/frameworkimpl/memory/memorystate.h>
-
-namespace storage {
-
-class MemorySnapshotList : public vespalib::Printable {
- std::map<uint64_t time, MemoryState> _snapshots;
-};
-
-} // storage
-
diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp b/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp
deleted file mode 100644
index dd07a61f27b..00000000000
--- a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp
+++ /dev/null
@@ -1,661 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "memorystatusviewer.h"
-#include <vespa/storage/storageutil/graph.h>
-#include <vespa/storage/storageutil/palette.h>
-#include <vespa/storage/storageutil/piechart.h>
-#include <vespa/metrics/metricmanager.h>
-#include <vespa/storageapi/messageapi/storagemessage.h>
-#include <algorithm>
-
-#include <vespa/log/bufferedlogger.h>
-LOG_SETUP(".memory.status.viewer");
-
-using storage::framework::defaultimplementation::MemoryState;
-
-namespace storage {
-
-MemoryStatusViewer::Entry::Entry(
- const std::string& name, framework::Clock& clock,
- framework::SecondTime maxAge)
- : _name(name),
- _maxAge(maxAge),
- _timeTaken(clock.getTimeInSeconds()),
- _data(),
- _maxMemory(0)
-{
-}
-
-MemoryStatusViewer::MemoryStatusViewer(
- framework::defaultimplementation::MemoryManager& mm,
- const metrics::MetricManager& metricMan,
- StorageComponentRegister& compReg)
- : framework::HtmlStatusReporter("memorymanager", "Memory Manager"),
- _component(compReg, "memorystatusviewer"),
- _manager(mm),
- _metricManager(metricMan),
- _workerMonitor(),
- _states(),
- _memoryHistory(),
- _memoryHistorySize(24 * 31),
- _memoryHistoryPeriod(60),
- _allowedSlackPeriod(6),
- _lastHistoryUpdate(_component.getClock().getTimeInSeconds()),
- _processedTime(0)
-{
- addEntry("Current", 0);
- addEntry("Last hour", 60 * 60);
- addEntry("Last day", 24 * 60 * 60);
- addEntry("Last month", 4 * 7 * 24 * 60 * 60);
- addEntry("Last ever", std::numeric_limits<uint32_t>::max());
-
- framework::MilliSecTime maxProcessingTime(60 * 1000);
- _thread = _component.startThread(*this, maxProcessingTime,
- framework::MilliSecTime(1000));
- _component.registerStatusPage(*this);
-}
-
-MemoryStatusViewer::~MemoryStatusViewer()
-{
- if (_thread.get() != 0) {
- _thread->interrupt();
- {
- vespalib::MonitorGuard monitor(_workerMonitor);
- monitor.signal();
- }
- _thread->join();
- }
-}
-
-namespace {
- struct Group {
- std::set<const framework::MemoryAllocationType*> types;
- api::StorageMessage::Priority minPri;
- api::StorageMessage::Priority maxPri;
- MemoryState::Entry entry;
-
- Group(const framework::MemoryAllocationType& type,
- api::StorageMessage::Priority pri,
- const MemoryState::Entry& e)
- : types(),
- minPri(pri),
- maxPri(pri),
- entry(e)
- {
- types.insert(&type);
- }
- };
-
- struct GroupSizeOrder {
- bool operator()(const Group& g1, const Group& g2) {
- return (g1.entry._currentUsedSize > g2.entry._currentUsedSize);
- }
- };
- struct GroupAllocsOrder {
- bool operator()(const Group& g1, const Group& g2) {
- return (g1.entry._totalUserCount > g2.entry._totalUserCount);
- }
- };
- struct GroupMinAllocsOrder {
- bool operator()(const Group& g1, const Group& g2) {
- return (g1.entry._minimumCount > g2.entry._minimumCount);
- }
- };
- struct GroupDeniedAllocsOrder {
- bool operator()(const Group& g1, const Group& g2) {
- return (g1.entry._deniedCount > g2.entry._deniedCount);
- }
- };
-
- std::vector<Group> collapsePriorities(MemoryStatusViewer::Entry& entry) {
- std::vector<Group> groups;
- const MemoryState::SnapShot& ss(entry._data);
- for (MemoryState::AllocationMap::const_iterator it
- = ss.getAllocations().begin();
- it != ss.getAllocations().end(); ++it)
- {
- std::unique_ptr<Group> group;
- for (MemoryState::PriorityMap::const_iterator it2
- = it->second.begin(); it2 != it->second.end(); ++it2)
- {
- if (group.get() == 0) {
- group.reset(new Group(
- *it->first, it2->first, it2->second));
- } else {
- group->entry += it2->second;
- group->minPri = std::min(group->minPri, it2->first);
- group->maxPri = std::max(group->maxPri, it2->first);
- }
- }
- if (group.get() != 0) {
- groups.push_back(*group);
- }
- }
- return groups;
- }
-
- std::vector<Group> groupLoad(uint32_t groupCount, uint64_t minSize,
- uint32_t minEntries,
- MemoryStatusViewer::Entry& entry)
- {
- assert(groupCount > 1);
- std::vector<Group> groups(collapsePriorities(entry));
- if (groups.size() == 0) return groups;
- std::sort(groups.begin(), groups.end(), GroupSizeOrder());
- assert(groups.front().entry._currentUsedSize
- >= groups.back().entry._currentUsedSize);
- while (groups.size() > minEntries
- && (groups.size() > groupCount
- || groups[groups.size() - 2].entry._currentUsedSize
- < minSize))
- {
- Group& nextButLast(groups[groups.size() - 2]);
- Group& last(groups.back());
- if (last.entry._currentUsedSize > 0) {
- nextButLast.entry += last.entry;
- nextButLast.minPri = std::min(nextButLast.minPri, last.minPri);
- nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri);
- nextButLast.types.insert(*last.types.begin());
- }
- groups.pop_back();
- }
- return groups;
- }
-
- std::vector<Group> groupAllocs(uint32_t groupCount, uint64_t minSize,
- uint32_t minEntries,
- MemoryStatusViewer::Entry& entry)
- {
- assert(groupCount > 1);
- std::vector<Group> groups(collapsePriorities(entry));
- if (groups.size() == 0) return groups;
- std::sort(groups.begin(), groups.end(), GroupAllocsOrder());
- assert(groups.front().entry._totalUserCount
- >= groups.back().entry._totalUserCount);
- while (groups.size() > minEntries
- && (groups.size() > groupCount
- || groups[groups.size() - 2].entry._totalUserCount
- < minSize))
- {
- Group& nextButLast(groups[groups.size() - 2]);
- Group& last(groups.back());
- nextButLast.entry += last.entry;
- nextButLast.minPri = std::min(nextButLast.minPri, last.minPri);
- nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri);
- nextButLast.types.insert(*last.types.begin());
- groups.pop_back();
- }
- return groups;
- }
-
- std::vector<Group> groupMinAllocs(uint32_t groupCount, uint64_t minSize,
- uint32_t minEntries,
- MemoryStatusViewer::Entry& entry)
- {
- assert(groupCount > 1);
- std::vector<Group> groups(collapsePriorities(entry));
- if (groups.size() == 0) return groups;
- std::sort(groups.begin(), groups.end(), GroupMinAllocsOrder());
- assert(groups.front().entry._minimumCount
- >= groups.back().entry._minimumCount);
- while (groups.size() > minEntries
- && (groups.size() > groupCount
- || groups[groups.size() - 2].entry._minimumCount
- < minSize))
- {
- Group& nextButLast(groups[groups.size() - 2]);
- Group& last(groups.back());
- nextButLast.entry += last.entry;
- nextButLast.minPri = std::min(nextButLast.minPri, last.minPri);
- nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri);
- nextButLast.types.insert(*last.types.begin());
- groups.pop_back();
- }
- return groups;
- }
-
- std::vector<Group> groupDeniedAllocs(uint32_t groupCount, uint64_t minSize,
- uint32_t minEntries,
- MemoryStatusViewer::Entry& entry)
- {
- assert(groupCount > 1);
- std::vector<Group> groups(collapsePriorities(entry));
- if (groups.size() == 0) return groups;
- std::sort(groups.begin(), groups.end(), GroupDeniedAllocsOrder());
- assert(groups.front().entry._deniedCount
- >= groups.back().entry._deniedCount);
- while (groups.size() > minEntries
- && (groups.size() > groupCount
- || groups[groups.size() - 2].entry._deniedCount
- < minSize))
- {
- Group& nextButLast(groups[groups.size() - 2]);
- Group& last(groups.back());
- nextButLast.entry += last.entry;
- nextButLast.minPri = std::min(nextButLast.minPri, last.minPri);
- nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri);
- nextButLast.types.insert(*last.types.begin());
- groups.pop_back();
- }
- return groups;
- }
-}
-
-void
-MemoryStatusViewer::printSnapshot(
- std::ostream& out, Entry& entry,
- std::map<const framework::MemoryAllocationType*,
- uint32_t>& colors) const
-{
- out << "<h4>" << entry._name << " - Taken at "
- << entry._timeTaken.toString() << "</h4>\n"
- << "<table><tr><td>\n"
- << "<b>Memory usage";
- if (entry._name != "Current") {
- out << ", maxed at " << framework::SecondTime(entry._timeTaken);
- }
- out << " with "
- << (entry._data.getUsedSizeIgnoringCache() / (1024 * 1024))
- << " MB.</b><br>\n";
- std::string piename = entry._name;
- std::replace(piename.begin(), piename.end(), ' ', '_');
- uint64_t freeSize = entry._maxMemory - entry._data.getUsedSize();
- // Memory usage pie
- uint64_t minSize = freeSize / 20;
- std::vector<Group> groups(groupLoad(20, minSize, 5, entry));
- PieChart chart(piename, PieChart::SCHEME_CUSTOM);
- chart.printLabels(false);
- for (uint32_t i=0; i<groups.size(); ++i) {
- std::string name = "Other";
- if (groups[i].types.size() == 1) {
- name = (*groups[i].types.begin())->getName();
- }
- uint32_t mbytes = groups[i].entry._currentUsedSize / (1024 * 1024);
- std::ostringstream ost;
- ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri);
- if (groups[i].minPri != groups[i].maxPri) {
- ost << " - " << static_cast<uint16_t>(groups[i].maxPri);
- }
- ost << " (" << mbytes << " MB)";
- name = ost.str();
- if (groups[i].entry._currentUsedSize > 0) {
- chart.add(groups[i].entry._currentUsedSize, name,
- colors[*groups[i].types.begin()]);
- }
- }
- {
- std::ostringstream ost;
- ost << "Free (" << (freeSize / (1024 * 1024)) << " MB)";
- chart.add(freeSize, ost.str(), colors[0]);
- }
- chart.printCanvas(out, 750, 300);
- out << "\n\n";
- chart.printScript(out, "");
- out << "\n\n";
- // Total allocations pie
- out << "</td><td>\n";
- PieChart allocChart(piename + "Alloc", PieChart::SCHEME_CUSTOM);
- allocChart.printLabels(false);
- groups = groupAllocs(20, 100, 5, entry);
- uint64_t totalAllocs = 0;
- for (uint32_t i=0; i<groups.size(); ++i) {
- std::string name = "Other";
- if (groups[i].types.size() == 1) {
- name = (*groups[i].types.begin())->getName();
- }
- uint32_t allocs = groups[i].entry._totalUserCount;
- totalAllocs += allocs;
- std::ostringstream ost;
- ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri);
- if (groups[i].minPri != groups[i].maxPri) {
- ost << " - " << static_cast<uint16_t>(groups[i].maxPri);
- }
- ost << " (" << allocs << " allocations)";
- name = ost.str();
- if (groups[i].entry._totalUserCount > 0) {
- allocChart.add(groups[i].entry._totalUserCount, name,
- colors[*groups[i].types.begin()]);
- }
- }
- out << "<b>Allocations, totalling " << totalAllocs << "</b><br>\n";
- allocChart.printCanvas(out, 750, 300);
- out << "\n\n";
- allocChart.printScript(out, "");
- out << "\n\n";
- out << "</td></tr><tr><td>\n";
- PieChart minChart(piename + "Min", PieChart::SCHEME_CUSTOM);
- minChart.printLabels(false);
- groups = groupMinAllocs(20, 100, 5, entry);
- uint64_t totalMinAllocs = 0;
- for (uint32_t i=0; i<groups.size(); ++i) {
- std::string name = "Other";
- if (groups[i].types.size() == 1) {
- name = (*groups[i].types.begin())->getName();
- }
- uint32_t allocs = groups[i].entry._minimumCount;
- totalMinAllocs += allocs;
- std::ostringstream ost;
- ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri);
- if (groups[i].minPri != groups[i].maxPri) {
- ost << " - " << static_cast<uint16_t>(groups[i].maxPri);
- }
- ost << " (" << allocs << " min allocations)";
- name = ost.str();
- if (groups[i].entry._minimumCount > 0) {
- minChart.add(groups[i].entry._minimumCount, name,
- colors[*groups[i].types.begin()]);
- }
- }
- out << "<b>Minimum allocations, totalling " << totalMinAllocs
- << "</b><br>\n";
- if (totalMinAllocs > 0) {
- minChart.printCanvas(out, 750, 300);
- out << "\n\n";
- minChart.printScript(out, "");
- out << "\n\n";
- }
- out << "</td><td>\n";
- PieChart deniedChart(piename + "Denied", PieChart::SCHEME_CUSTOM);
- deniedChart.printLabels(false);
- groups = groupDeniedAllocs(20, 100, 5, entry);
- uint64_t totalDeniedAllocs = 0;
- for (uint32_t i=0; i<groups.size(); ++i) {
- std::string name = "Other";
- if (groups[i].types.size() == 1) {
- name = (*groups[i].types.begin())->getName();
- }
- uint32_t allocs = groups[i].entry._deniedCount;
- totalDeniedAllocs += allocs;
- std::ostringstream ost;
- ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri);
- if (groups[i].minPri != groups[i].maxPri) {
- ost << " - " << static_cast<uint16_t>(groups[i].maxPri);
- }
- ost << " (" << allocs << " denied allocations)";
- name = ost.str();
- if (groups[i].entry._deniedCount > 0) {
- deniedChart.add(groups[i].entry._deniedCount, name,
- colors[*groups[i].types.begin()]);
- }
- }
- out << "<b>Denied allocations, totalling " << totalDeniedAllocs
- << "</b><br>\n";
- if (totalDeniedAllocs > 0) {
- deniedChart.printCanvas(out, 750, 300);
- out << "\n\n";
- deniedChart.printScript(out, "");
- out << "\n\n";
- }
- out << "</td></tr></table>\n";
-}
-
-void
-MemoryStatusViewer::reportHtmlHeaderAdditions(
- std::ostream& out, const framework::HttpUrlPath&) const
-{
- (void) out;
- // FIXME this function used to emit Yahoo-internal links to graph plotting
- // JS files. Obviously, this won't work for external users. Either way, the
- // memory manager/status reporter is deprecated.
-}
-
-namespace {
- std::map<const framework::MemoryAllocationType*, uint32_t> assignColors(
- const std::vector<const framework::MemoryAllocationType*>& types)
- {
- Palette palette(types.size() + 1);
- std::map<const framework::MemoryAllocationType*, uint32_t> colors;
- uint32_t nextCol = 0;
- colors[0] = palette[nextCol++];
- for (std::vector<const framework::MemoryAllocationType*>
- ::const_iterator it = types.begin(); it != types.end(); ++it)
- {
- colors[*it] = palette[nextCol++];
- }
- return colors;
- }
-}
-
-void
-MemoryStatusViewer::reportHtmlStatus(std::ostream& out,
- const framework::HttpUrlPath& path) const
-{
- vespalib::MonitorGuard monitor(_workerMonitor);
-
- if (path.getAttribute("page") == "reset") {
- }
- if (path.getAttribute("interval") == "current") {
- Entry& e(*_states[0]);
- out << "<pre>" << e._name << ": ";
- if (e.containsData()) {
- e._data.print(out, true, " ");
- } else {
- out << "na";
- }
- out << "\n</pre>";
- return;
- }
- const_cast<MemoryStatusViewer*>(this)->grabMemoryUsage();
- framework::SecondTime currentTime(_component.getClock().getTimeInSeconds());
- std::vector<const framework::MemoryAllocationType*> allocTypes(
- _manager.getAllocationTypes());
- std::map<const framework::MemoryAllocationType*, uint32_t> colors(
- assignColors(allocTypes));
- // Print memory usage graph
- {
- uint32_t mb = 1024 * 1024;
- Graph memoryHistory("memhistory", Graph::SCHEME_CUSTOM);
- std::vector<Graph::Point> total;
- std::vector<Graph::Point> used;
- std::vector<Graph::Point> usedWoCache;
- uint32_t xval = 0;
- for (std::deque<MemoryTimeEntry>::const_iterator it
- = _memoryHistory.begin(); it != _memoryHistory.end();
- ++it, ++xval)
- {
- used.push_back(Graph::Point(xval, it->used));
- usedWoCache.push_back(Graph::Point(xval, it->usedWithoutCache));
- }
- used.push_back(Graph::Point(
- xval, _states[0]->_data.getUsedSize() / mb));
- usedWoCache.push_back(Graph::Point(
- xval, _states[0]->_data.getUsedSizeIgnoringCache() / mb));
- uint32_t totalSize = _states[0]->_maxMemory / mb;
- total.push_back(Graph::Point(0, totalSize));
- total.push_back(Graph::Point(xval, totalSize));
- memoryHistory.add(total, "Total memory", Graph::GREEN);
- memoryHistory.add(used, "Used memory", Graph::YELLOW);
- memoryHistory.add(usedWoCache, "Used memory excluding freeable cache",
- Graph::RED);
- out << "<p>Memory available for lowest priority (255): "
- << _manager.getMemorySizeFreeForPriority(255) << " byte(s).</p>\n";
- out << "<h3>Historic memory usage</h3>\n";
- uint32_t yAxisUnit = ((totalSize / 4) / 256) * 256;
- if (yAxisUnit == 0) yAxisUnit = (totalSize / 4);
- if (yAxisUnit == 0) yAxisUnit = 1;
- uint32_t size = yAxisUnit;
- memoryHistory.addYAxisLabel(0, "0 B");
- while (size <= totalSize) {
- std::ostringstream label;
- if (size % 1024 == 0) {
- label << (size / 1024) << " GB";
- } else {
- label << size << " MB";
- }
- memoryHistory.addYAxisLabel(size, label.str());
- size += yAxisUnit;
- }
- uint32_t xAxisUnit = ((_memoryHistory.size() / 4) / 24) * 24;
- if (xAxisUnit == 0) xAxisUnit = _memoryHistoryPeriod.getTime();
- uint32_t startTime = ((currentTime.getTime()
- / _memoryHistoryPeriod.getTime())
- / 24) * 24;
- uint32_t stopTime = (currentTime.getTime()
- / _memoryHistoryPeriod.getTime())
- - _memoryHistory.size() + 1;
- memoryHistory.addXAxisLabel(xval, currentTime.toString());
- bool addedMiddlePoints = false;
- while (startTime >= stopTime) {
- if (currentTime.getTime() / _memoryHistoryPeriod.getTime()
- - startTime > 48)
- {
- memoryHistory.addXAxisLabel(
- (startTime - stopTime),
- framework::SecondTime(
- startTime * _memoryHistoryPeriod.getTime())
- .toString());
- addedMiddlePoints = true;
- }
- startTime -= xAxisUnit;
- }
- if (!addedMiddlePoints && _memoryHistory.size() > 2) {
- memoryHistory.addXAxisLabel(
- 1,
- framework::SecondTime(
- stopTime * _memoryHistoryPeriod.getTime())
- .toString());
- }
- memoryHistory.setBorders(50, 0, 0, 30);
- memoryHistory.setLegendPos(80, 20);
- memoryHistory.printCanvas(out, 1000, 250);
- memoryHistory.printScript(out, "");
- }
- uint32_t maxUsedWithoutCache = 0;
- for (uint32_t i=0; i<_states.size(); ++i) {
- Entry& e(*_states[i]);
- if (!e.containsData()
- || e._data.getUsedSizeIgnoringCache() == maxUsedWithoutCache)
- {
- continue;
- }
- printSnapshot(out, e, colors);
- maxUsedWithoutCache = e._data.getUsedSizeIgnoringCache();
- }
- out << "<h3>Raw output of stored data</h3>\n"
- << "<pre>\n";
- monitor.unlock();
- printDebugOutput(out);
- out << "</pre>\n";
- out << "<h2>Memory used for metrics. (Not tracked in memory manager)</h2>\n"
- << "<pre>\n"
- << _metricManager.getMemoryConsumption(_metricManager.getMetricLock())->toString()
- << "\n</pre>\n";
-}
-
-void
-MemoryStatusViewer::run(framework::ThreadHandle& thread)
-{
- while (!thread.interrupted()) {
- vespalib::MonitorGuard monitor(_workerMonitor);
- framework::SecondTime currentTime(
- _component.getClock().getTimeInSeconds());
- if (_lastHistoryUpdate + _memoryHistoryPeriod <= currentTime
- || _states[0]->_timeTaken + _memoryHistoryPeriod <= currentTime)
- {
- grabMemoryUsage();
- _processedTime = currentTime;
- LOG(spam, "Done processing time %" PRIu64, currentTime.getTime());
- thread.registerTick(framework::PROCESS_CYCLE);
- } else {
- monitor.wait(thread.getWaitTime());
- thread.registerTick(framework::WAIT_CYCLE);
- }
- }
-}
-
-// You should have worker monitor when calling this function
-void
-MemoryStatusViewer::grabMemoryUsage()
-{
- framework::SecondTime currentTime(_component.getClock().getTimeInSeconds());
- MemoryState state(_component.getClock(), 0);
- _manager.getState(state, true);
-
- if (_lastHistoryUpdate + _memoryHistoryPeriod <= currentTime) {
- LOG(spam, "Adding another %" PRIu64 " sec entry to memory history.",
- _memoryHistoryPeriod.getTime());
- // Add history once an hour
- uint32_t mb = 1024 * 1024;
- _memoryHistory.push_back(MemoryTimeEntry(
- state.getMaxSnapshot().getUsedSize() / mb,
- state.getMaxSnapshot().getUsedSizeIgnoringCache() / mb));
- if (_memoryHistory.size() > _memoryHistorySize) {
- if (_memoryHistoryPeriod != framework::SecondTime(60 * 60)) {
- uint32_t periodDiff = 60 * 60 / _memoryHistoryPeriod.getTime();
- std::deque<MemoryTimeEntry> newHistory;
- uint32_t count = 0;
- MemoryTimeEntry entry(0, 0);
- for (std::deque<MemoryTimeEntry>::const_iterator it
- = _memoryHistory.begin();
- it != _memoryHistory.end(); ++it)
- {
- entry.keepMax(*it);
- if (++count == periodDiff) {
- newHistory.push_back(entry);
- entry = MemoryTimeEntry(0, 0);
- count = 0;
- }
- }
- if (entry.used != 0) {
- newHistory.push_back(entry);
- }
- _memoryHistory.swap(newHistory);
- _memoryHistoryPeriod = framework::SecondTime(60 * 60);
- }
- }
- _lastHistoryUpdate += _memoryHistoryPeriod;
- if (_lastHistoryUpdate + _allowedSlackPeriod < currentTime) {
- LOGBP(warning, "Memory history is supposed to be tracked every %"
- PRIu64 " seconds, but %" PRIu64" seconds have passed "
- "since last update. Memory history graph will be "
- "incorrect.",
- _memoryHistoryPeriod.getTime(),
- (currentTime - _lastHistoryUpdate + _memoryHistoryPeriod)
- .getTime());
- _lastHistoryUpdate = currentTime;
- }
- }
- LOG(spam, "Overwriting current with snapshot using %" PRIu64 " bytes.",
- state.getCurrentSnapshot().getUsedSize());
- _states[0]->assign(state.getCurrentSnapshot(),
- state.getTotalSize(), currentTime);
- for (uint32_t i=1, n=_states.size(); i<n; ++i) {
- if (currentTime - _states[i]->_timeTaken >= _states[i]->_maxAge
- || state.getMaxSnapshot().getUsedSize()
- > _states[i]->_data.getUsedSize())
- {
- LOG(spam, "Updating period %s usage. Old usage was %" PRIu64 ". "
- "Last set at %" PRIu64,
- _states[i]->_name.c_str(), _states[i]->_data.getUsedSize(),
- _states[i]->_timeTaken.getTime());
- _states[i]->assign(state.getMaxSnapshot(),
- state.getTotalSize(), currentTime);
- }
- }
-}
-
-void
-MemoryStatusViewer::notifyThread() const
-{
- vespalib::MonitorGuard monitor(_workerMonitor);
- monitor.broadcast();
-}
-
-void
-MemoryStatusViewer::printDebugOutput(std::ostream& out) const
-{
- vespalib::MonitorGuard monitor(_workerMonitor);
- for (uint32_t i=0; i<_states.size(); ++i) {
- Entry& e(*_states[i]);
- out << e._name << ": ";
- if (e.containsData()) {
- out << e._timeTaken.toString() << " Max memory " << e._maxMemory << " ";
- e._data.print(out, true, " ");
- } else {
- out << "na";
- }
- out << "\n\n";
- }
-}
-
-} // storage
diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h b/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h
deleted file mode 100644
index 2c7510d371d..00000000000
--- a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::MemoryStatusViewer
- *
- * \brief Generates status to access through status pages.
- *
- * Keeps a history of the largest memory inprints seen historically. This is
- * done be defining periods, where a period is always a multiplum of the length
- * of the period shorter than it. The last entry will store the biggest memory
- * imprint ever seen, and the earlier entries will show biggest for their time
- * period.
- *
- * To avoid having all periods cleared once the biggest period resets, the
- * periods keep data for each of the periods one size below it. Thus, a year
- * keeps data for 12 months, a month for 30 days, and so on.
- *
- * The memory state objects are divided in 3 parts. Current memory data, max
- * memory data since since reset and counts for how often various events have
- * happened.
- *
- * The counts will have their total count values stored in the current entry.
- * When the next period is updated getting a copy of these counts, we can see
- * how many counts have happened recently, by taking the current entry and
- * subtract those accounted for earlier.
- *
- * The current memory data will not be interesting for anything than to show the
- * actual now values in the current entry.
- *
- * The max since reset values will be the values used for the various periods.
- * When a period is updated with new data for a subpart of their period, the
- * max seen data is reset in the period in front, such that a lower maximum
- * can be found.
- */
-
-#pragma once
-
-#include <vespa/storage/common/storagecomponent.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorystate.h>
-#include <vespa/storageframework/generic/status/htmlstatusreporter.h>
-#include <vespa/vespalib/util/document_runnable.h>
-#include <vespa/vespalib/util/sync.h>
-#include <deque>
-#include <vector>
-
-
-namespace metrics {
- class MetricManager;
-}
-
-namespace storage {
-
-class StorageServerInterface;
-
-class MemoryStatusViewer : public framework::HtmlStatusReporter,
- private framework::Runnable
-{
-public:
- typedef framework::defaultimplementation::MemoryState::SnapShot SnapShot;
- struct Entry {
- typedef std::shared_ptr<Entry> SP;
-
- std::string _name;
- framework::SecondTime _maxAge;
- framework::SecondTime _timeTaken;
- SnapShot _data;
- uint64_t _maxMemory;
-
- Entry(const std::string& name, framework::Clock&,
- framework::SecondTime maxAge);
- bool containsData() const { return (_maxMemory != 0); }
-
- void assign(const SnapShot& snapshot, uint64_t maxMemory,
- framework::SecondTime time)
- {
- _data = snapshot;
- _maxMemory = maxMemory;
- _timeTaken = time;
- }
- };
-
- struct MemoryTimeEntry {
- uint64_t used;
- uint64_t usedWithoutCache;
-
- MemoryTimeEntry(uint64_t u, uint64_t wo)
- : used(u), usedWithoutCache(wo) {}
-
- void keepMax(const MemoryTimeEntry& e) {
- used = (used > e.used ? used : e.used);
- usedWithoutCache = (usedWithoutCache > e.usedWithoutCache
- ? usedWithoutCache : e.usedWithoutCache);
- }
- };
-
-private:
- framework::Component _component;
- framework::defaultimplementation::MemoryManager& _manager;
- const metrics::MetricManager& _metricManager;
- vespalib::Monitor _workerMonitor;
-
- std::vector<Entry::SP> _states;
- std::deque<MemoryTimeEntry> _memoryHistory;
- uint32_t _memoryHistorySize;
- framework::SecondTime _memoryHistoryPeriod;
- framework::SecondTime _allowedSlackPeriod;
- framework::SecondTime _lastHistoryUpdate;
- framework::Thread::UP _thread;
- framework::SecondTime _processedTime;
-
- void addEntry(const std::string& name, uint32_t maxAge) {
- _states.push_back(Entry::SP(new Entry(name, _component.getClock(),
- framework::SecondTime(maxAge))));
- }
- void run(framework::ThreadHandle&) override;
- void grabMemoryUsage();
- void printSnapshot(std::ostream& out, Entry& entry,
- std::map<const framework::MemoryAllocationType*,
- uint32_t>& colors) const;
-
-public:
- MemoryStatusViewer(
- framework::defaultimplementation::MemoryManager&,
- const metrics::MetricManager&,
- StorageComponentRegister&);
- ~MemoryStatusViewer();
-
- void reportHtmlHeaderAdditions(std::ostream&, const framework::HttpUrlPath&) const override;
- void reportHtmlStatus(std::ostream&, const framework::HttpUrlPath&) const override;
-
- /** Useful for testing. */
- framework::SecondTime getProcessedTime() const { return _processedTime; }
- void notifyThread() const;
- void printDebugOutput(std::ostream&) const;
-
-};
-
-}
diff --git a/storage/src/vespa/storage/persistence/fieldvisitor.h b/storage/src/vespa/storage/persistence/fieldvisitor.h
index 96b73ca779b..4b47c68e33b 100644
--- a/storage/src/vespa/storage/persistence/fieldvisitor.h
+++ b/storage/src/vespa/storage/persistence/fieldvisitor.h
@@ -42,7 +42,6 @@ public:
void visitArithmeticValueNode(const document::select::ArithmeticValueNode &) override {}
void visitFunctionValueNode(const document::select::FunctionValueNode &) override {}
void visitIdValueNode(const document::select::IdValueNode &) override {}
- void visitSearchColumnValueNode(const document::select::SearchColumnValueNode &) override {}
void visitFloatValueNode(const document::select::FloatValueNode &) override {}
void visitVariableValueNode(const document::select::VariableValueNode &) override {}
void visitIntegerValueNode(const document::select::IntegerValueNode &) override {}
diff --git a/storage/src/vespa/storage/persistence/messages.cpp b/storage/src/vespa/storage/persistence/messages.cpp
index 876067e4e15..720371a68f1 100644
--- a/storage/src/vespa/storage/persistence/messages.cpp
+++ b/storage/src/vespa/storage/persistence/messages.cpp
@@ -6,17 +6,14 @@ using document::BucketSpace;
namespace storage {
-GetIterCommand::GetIterCommand(framework::MemoryToken::UP token,
- const document::Bucket &bucket,
+GetIterCommand::GetIterCommand(const document::Bucket &bucket,
const spi::IteratorId iteratorId,
uint32_t maxByteSize)
: api::InternalCommand(ID),
- _token(std::move(token)),
_bucket(bucket),
_iteratorId(iteratorId),
_maxByteSize(maxByteSize)
{
- assert(_token.get());
}
GetIterCommand::~GetIterCommand() { }
@@ -38,7 +35,6 @@ GetIterCommand::makeReply() {
GetIterReply::GetIterReply(GetIterCommand& cmd)
: api::InternalReply(ID, cmd),
- _token(cmd.releaseMemoryToken()),
_bucket(cmd.getBucket()),
_completed(false)
{ }
diff --git a/storage/src/vespa/storage/persistence/messages.h b/storage/src/vespa/storage/persistence/messages.h
index 7bbeea8a12a..ba7f5979569 100644
--- a/storage/src/vespa/storage/persistence/messages.h
+++ b/storage/src/vespa/storage/persistence/messages.h
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
-#include <vespa/storageframework/generic/memory/memorytoken.h>
#include <vespa/storageapi/message/internal.h>
#include <vespa/persistence/spi/docentry.h>
#include <vespa/persistence/spi/bucket.h>
@@ -14,7 +13,6 @@ namespace storage {
class GetIterCommand : public api::InternalCommand {
private:
- mutable framework::MemoryToken::UP _token;
document::Bucket _bucket;
spi::IteratorId _iteratorId;
uint32_t _maxByteSize;
@@ -24,8 +22,7 @@ public:
typedef std::unique_ptr<GetIterCommand> UP;
typedef std::shared_ptr<GetIterCommand> SP;
- GetIterCommand(framework::MemoryToken::UP token,
- const document::Bucket &bucket,
+ GetIterCommand(const document::Bucket &bucket,
const spi::IteratorId iteratorId,
uint32_t maxByteSize);
~GetIterCommand();
@@ -44,13 +41,11 @@ public:
void print(std::ostream& out, bool verbose, const std::string& indent) const override;
private:
- framework::MemoryToken::UP releaseMemoryToken() { return std::move(_token); }
friend class GetIterReply;
};
class GetIterReply : public api::InternalReply {
private:
- framework::MemoryToken::UP _token;
document::Bucket _bucket;
std::vector<spi::DocEntry::UP> _entries;
bool _completed;
diff --git a/storage/src/vespa/storage/storageserver/CMakeLists.txt b/storage/src/vespa/storage/storageserver/CMakeLists.txt
index 3d0a0fdfa14..c0238922a91 100644
--- a/storage/src/vespa/storage/storageserver/CMakeLists.txt
+++ b/storage/src/vespa/storage/storageserver/CMakeLists.txt
@@ -11,7 +11,6 @@ vespa_add_library(storage_storageserver
documentapiconverter.cpp
fnetlistener.cpp
mergethrottler.cpp
- messageallocationtypes.cpp
messagesink.cpp
opslogger.cpp
priorityconverter.cpp
diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.cpp b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
index eae51b90165..c19dc7cfd27 100644
--- a/storage/src/vespa/storage/storageserver/communicationmanager.cpp
+++ b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
@@ -96,13 +96,6 @@ StorageTransportContext::StorageTransportContext(std::unique_ptr<RPCRequestWrapp
StorageTransportContext::~StorageTransportContext() { }
-const framework::MemoryAllocationType&
-CommunicationManager::getAllocationType(api::StorageMessage& msg) const
-{
- return _messageAllocTypes.getType(msg.getType().getId());
-}
-
-
void
CommunicationManager::receiveStorageReply(const std::shared_ptr<api::StorageReply>& reply)
{
@@ -296,8 +289,7 @@ CommunicationManager::CommunicationManager(StorageComponentRegister& compReg, co
_configUri(configUri),
_closed(false),
_bucketResolver(std::make_unique<PlaceHolderBucketResolver>()),
- _docApiConverter(configUri, *_bucketResolver),
- _messageAllocTypes(_component.getMemoryManager())
+ _docApiConverter(configUri, *_bucketResolver)
{
_component.registerMetricUpdateHook(*this, framework::SecondTime(5));
_component.registerMetric(_metrics);
@@ -483,33 +475,9 @@ CommunicationManager::process(const std::shared_ptr<api::StorageMessage>& msg)
void
CommunicationManager::enqueue(const std::shared_ptr<api::StorageMessage> & msg)
{
- using MemoryToken = framework::MemoryToken;
assert(msg.get());
-
- const uint32_t memoryFootprint = msg->getMemoryFootprint();
- MemoryToken::UP token = _component.getMemoryManager().allocate(getAllocationType(*msg), memoryFootprint * 2,
- memoryFootprint * 2, msg->getPriority());
-
- if (token) {
- msg->setMemoryToken(std::move(token));
-
- LOG(spam, "Enq storage message %s, priority %d", msg->toString().c_str(), msg->getPriority());
- _eventQueue.enqueue(msg);
- } else {
- _metrics.failedDueToTooLittleMemory.inc();
- std::ostringstream ost;
- ost << "Failed to aquire " << (memoryFootprint * 2)
- << " bytes of memory to handle command of type "
- << msg->getType() << "\n";
- LOG(spam, "%s", ost.str().c_str());
- api::StorageCommand* cmd(dynamic_cast<api::StorageCommand*>(msg.get()));
-
- if (cmd) {
- std::shared_ptr<api::StorageReply> reply(cmd->makeReply());
- reply->setResult(api::ReturnCode(api::ReturnCode::BUSY, ost.str()));
- sendReply(reply);
- }
- }
+ LOG(spam, "Enq storage message %s, priority %d", msg->toString().c_str(), msg->getPriority());
+ _eventQueue.enqueue(msg);
}
bool
@@ -770,10 +738,11 @@ CommunicationManager::run(framework::ThreadHandle& thread)
if (_eventQueue.getNext(msg, 100)) {
process(msg);
}
- for (Protocols::iterator it(_earlierGenerations.begin());
+ std::lock_guard<std::mutex> guard(_earlierGenerationsLock);
+ for (EarlierProtocols::iterator it(_earlierGenerations.begin());
!_earlierGenerations.empty() &&
((it->first + TEN_MINUTES) < _component.getClock().getTimeInSeconds());
- _earlierGenerations.begin())
+ it = _earlierGenerations.begin())
{
_earlierGenerations.erase(it);
}
@@ -798,8 +767,8 @@ void CommunicationManager::updateMessagebusProtocol(
if (_mbus.get()) {
framework::SecondTime now(_component.getClock().getTimeInSeconds());
mbus::IProtocol::SP newDocumentProtocol(new documentapi::DocumentProtocol( *_component.getLoadTypes(), repo));
+ std::lock_guard<std::mutex> guard(_earlierGenerationsLock);
_earlierGenerations.push_back(std::make_pair(now, _mbus->getMessageBus().putProtocol(newDocumentProtocol)));
-
mbus::IProtocol::SP newStorageProtocol(new mbusprot::StorageProtocol(repo, *_component.getLoadTypes()));
_earlierGenerations.push_back(std::make_pair(now, _mbus->getMessageBus().putProtocol(newStorageProtocol)));
}
diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.h b/storage/src/vespa/storage/storageserver/communicationmanager.h
index 4cf3f33e6ea..f4f4aa5a236 100644
--- a/storage/src/vespa/storage/storageserver/communicationmanager.h
+++ b/storage/src/vespa/storage/storageserver/communicationmanager.h
@@ -11,7 +11,6 @@
#pragma once
#include "communicationmanagermetrics.h"
-#include "messageallocationtypes.h"
#include "documentapiconverter.h"
#include <vespa/storage/common/storagelink.h>
#include <vespa/storage/common/storagecomponent.h>
@@ -27,6 +26,7 @@
#include <map>
#include <queue>
#include <atomic>
+#include <mutex>
namespace mbus {
class RPCMessageBus;
@@ -139,8 +139,10 @@ private:
PriorityQueue _eventQueue;
// XXX: Should perhaps use a configsubscriber and poll from StorageComponent ?
std::unique_ptr<config::ConfigFetcher> _configFetcher;
- typedef std::vector< std::pair<framework::SecondTime, mbus::IProtocol::SP> > Protocols;
- Protocols _earlierGenerations;
+ using EarlierProtocol = std::pair<framework::SecondTime, mbus::IProtocol::SP>;
+ using EarlierProtocols = std::vector<EarlierProtocol>;
+ std::mutex _earlierGenerationsLock;
+ EarlierProtocols _earlierGenerations;
void onOpen() override;
void onClose() override;
@@ -171,10 +173,7 @@ private:
std::unique_ptr<BucketResolver> _bucketResolver;
DocumentApiConverter _docApiConverter;
framework::Thread::UP _thread;
- MessageAllocationTypes _messageAllocTypes;
- const framework::MemoryAllocationType&
- getAllocationType(api::StorageMessage& msg) const;
void updateMetrics(const MetricLockGuard &) override;
// Test needs access to configure() for live reconfig testing.
diff --git a/storage/src/vespa/storage/storageserver/framework.h b/storage/src/vespa/storage/storageserver/framework.h
index 724b4fe0944..f0ea1d71aa6 100644
--- a/storage/src/vespa/storage/storageserver/framework.h
+++ b/storage/src/vespa/storage/storageserver/framework.h
@@ -18,7 +18,6 @@
#include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h>
#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h>
#include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h>
namespace storage {
@@ -27,7 +26,6 @@ struct Framework {
// Typedefs to simplify the remainder of the interface
typedef StorageComponentRegisterImpl CompReg;
typedef framework::defaultimplementation::RealClock RealClock;
- typedef framework::defaultimplementation::MemoryManager MemoryManager;
/**
* You can provide your own clock implementation. Useful in testing where
@@ -48,20 +46,10 @@ struct Framework {
*/
FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); }
- /**
- * Get the memory manager. Components that wants to print status of memory
- * manager need access to the actual implementation.
- */
- MemoryManager& getMemoryManager() { return _memoryManager; }
-
- void setMaximumMemoryUsage(uint64_t max);
-
private:
CompReg _componentRegister;
framework::Clock::UP _clock;
framework::defaultimplementation::ThreadPoolImpl _threadPool;
- framework::defaultimplementation::AllocationLogic* _memoryLogic;
- MemoryManager _memoryManager;
};
diff --git a/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp b/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp
deleted file mode 100644
index ac2d99a439c..00000000000
--- a/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "messageallocationtypes.h"
-#include <vespa/storageapi/messageapi/storagemessage.h>
-#include <vespa/vespalib/util/exceptions.h>
-#include <vespa/vespalib/stllike/asciistream.h>
-
-namespace storage {
-
-MessageAllocationTypes::MessageAllocationTypes(framework::MemoryManagerInterface& manager)
-{
- using api::MessageType;
- using framework::MemoryAllocationType;
-
- _types.resize(MessageType::MESSAGETYPE_MAX_ID);
- _types[MessageType::DOCBLOCK_ID] = &manager.registerAllocationType(MemoryAllocationType("MESSAGE_DOCBLOCK"));
- _types[MessageType::DOCBLOCK_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MESSAGE_DOCBLOCK_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GET", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::GET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::INTERNAL_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::INTERNAL"));
- _types[MessageType::INTERNAL_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::INTERNAL_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::PUT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::PUT", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::PUT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::PUT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::REMOVE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVE", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::REMOVE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::REVERT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REVERT"));
- _types[MessageType::REVERT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REVERT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::VISITOR_CREATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_CREATE", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::VISITOR_CREATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_CREATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::VISITOR_DESTROY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_DESTROY"));
- _types[MessageType::VISITOR_DESTROY_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_DESTROY_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::REQUESTBUCKETINFO_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REQUESTBUCKETINFO"));
- _types[MessageType::REQUESTBUCKETINFO_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REQUESTBUCKETINFO_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::NOTIFYBUCKETCHANGE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::NOTIFYBUCKETCHANGE"));
- _types[MessageType::NOTIFYBUCKETCHANGE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::NOTIFYBUCKETCHANGE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::CREATEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::CREATEBUCKET"));
- _types[MessageType::CREATEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::CREATEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::MERGEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MERGEBUCKET"));
- _types[MessageType::MERGEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MERGEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::DELETEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DELETEBUCKET"));
- _types[MessageType::DELETEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DELETEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SETNODESTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETNODESTATE", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SETNODESTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETNODESTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETNODESTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETNODESTATE", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETNODESTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETNODESTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SETSYSTEMSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETSYSTEMSTATE", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SETSYSTEMSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETSYSTEMSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETSYSTEMSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETSYSTEMSTATE", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETSYSTEMSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETSYSTEMSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETBUCKETDIFF_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETDIFF", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETBUCKETDIFF_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETDIFF_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::APPLYBUCKETDIFF_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::APPLYBUCKETDIFF", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::APPLYBUCKETDIFF_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::APPLYBUCKETDIFF_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::VISITOR_INFO_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_INFO"));
- _types[MessageType::VISITOR_INFO_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_INFO_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SEARCHRESULT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SEARCHRESULT"));
- _types[MessageType::SEARCHRESULT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SEARCHRESULT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SPLITBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SPLITBUCKET"));
- _types[MessageType::SPLITBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SPLITBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::JOINBUCKETS_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::JOINBUCKETS"));
- _types[MessageType::JOINBUCKETS_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::JOINBUCKETS_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::SETBUCKETSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETBUCKETSTATE"));
- _types[MessageType::SETBUCKETSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETBUCKETSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::MULTIOPERATION_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MULTIOPERATION", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::MULTIOPERATION_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MULTIOPERATION_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::DOCUMENTSUMMARY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTSUMMARY"));
- _types[MessageType::DOCUMENTSUMMARY_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTSUMMARY_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::MAPVISITOR_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MAPVISITOR"));
- _types[MessageType::MAPVISITOR_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MAPVISITOR_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::STATBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::STATBUCKET", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::STATBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::STATBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::GETBUCKETLIST_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETLIST", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::GETBUCKETLIST_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETLIST_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::DOCUMENTLIST_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTLIST"));
- _types[MessageType::DOCUMENTLIST_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTLIST_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::UPDATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::UPDATE", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::UPDATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::UPDATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::EMPTYBUCKETS_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::EMPTYBUCKETS"));
- _types[MessageType::EMPTYBUCKETS_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::EMPTYBUCKETS_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::REMOVELOCATION_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVELOCATION", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::REMOVELOCATION_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVELOCATION_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::QUERYRESULT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::QUERYRESULT"));
- _types[MessageType::QUERYRESULT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::QUERYRESULT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE));
- _types[MessageType::BATCHPUTREMOVE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHPUTREMOVE", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::BATCHPUTREMOVE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHPUTREMOVE_REPLY", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::BATCHDOCUMENTUPDATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHDOCUMENTUPDATE", framework::MemoryAllocationType::EXTERNAL_LOAD));
- _types[MessageType::BATCHDOCUMENTUPDATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHDOCUMENTUPDATE_REPLY", framework::MemoryAllocationType::EXTERNAL_LOAD));
-}
-
-const framework::MemoryAllocationType&
-MessageAllocationTypes::getType(uint32_t type) const {
- if (_types.size() > size_t(type) && _types[type] != 0) {
- return *_types[type];
- }
- vespalib::asciistream ost;
- ost << "No type registered with value " << type << ".";
- throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
-}
-
-} // storage
diff --git a/storage/src/vespa/storage/storageserver/messageallocationtypes.h b/storage/src/vespa/storage/storageserver/messageallocationtypes.h
deleted file mode 100644
index 8f7e8e4b3c3..00000000000
--- a/storage/src/vespa/storage/storageserver/messageallocationtypes.h
+++ /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.
-/**
- * \class storage::MessageAllocationTypes
- *
- * \brief Memory allocation types for messages in storage.
- */
-#pragma once
-
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
-#include <vector>
-
-namespace storage {
-
-class MessageAllocationTypes {
- std::vector<const framework::MemoryAllocationType*> _types;
-
-public:
- MessageAllocationTypes(framework::MemoryManagerInterface& manager);
-
- const framework::MemoryAllocationType& getType(uint32_t type) const;
-};
-
-}
-
diff --git a/storage/src/vespa/storage/storageserver/storagenode.cpp b/storage/src/vespa/storage/storageserver/storagenode.cpp
index 855efaed6aa..ba1556bd3b9 100644
--- a/storage/src/vespa/storage/storageserver/storagenode.cpp
+++ b/storage/src/vespa/storage/storageserver/storagenode.cpp
@@ -7,7 +7,6 @@
#include "storagemetricsset.h"
#include "storagenodecontext.h"
-#include <vespa/storage/frameworkimpl/memory/memorystatusviewer.h>
#include <vespa/storage/frameworkimpl/status/statuswebserver.h>
#include <vespa/storage/frameworkimpl/thread/deadlockdetector.h>
#include <vespa/storage/common/statusmetricconsumer.h>
@@ -117,7 +116,6 @@ StorageNode::initialize()
// and store them away, while having the config lock.
subscribeToConfigs();
- _context.getMemoryManager().setMaximumMemoryUsage(_serverConfig->memorytouse);
updateUpgradeFlag(*_clusterConfig);
// First update some basics that doesn't depend on anything else to be
@@ -157,11 +155,6 @@ StorageNode::initialize()
initializeNodeSpecific();
- _memoryStatusViewer.reset(new MemoryStatusViewer(
- _context.getMemoryManager(),
- _context.getComponentRegister().getMetricManager(),
- _context.getComponentRegister()));
-
_statusMetrics.reset(new StatusMetricConsumer(
_context.getComponentRegister(), _context.getComponentRegister().getMetricManager()));
_stateReporter.reset(new StateReporter(
@@ -259,14 +252,6 @@ StorageNode::handleLiveConfigUpdate(const InitialGuard & initGuard)
DIFFERWARN(clusterName, "Cannot alter cluster name of node live");
DIFFERWARN(nodeIndex, "Cannot alter node index of node live");
DIFFERWARN(isDistributor, "Cannot alter role of node live");
- {
- if (DIFFER(memorytouse)) {
- LOG(info, "Live config update: Memory to use changed from %" PRId64 " to %" PRId64 ".",
- oldC.memorytouse, newC.memorytouse);
- ASSIGN(memorytouse);
- _context.getMemoryManager().setMaximumMemoryUsage(newC.memorytouse);
- }
- }
_serverConfig.reset(new StorServerConfig(oldC));
_newServerConfig.reset();
(void)updated;
@@ -413,10 +398,6 @@ StorageNode::shutdown()
LOG(debug, "Deleting state reporter");
_stateReporter.reset();
}
- if (_memoryStatusViewer) {
- LOG(debug, "Deleting memory status viewer");
- _memoryStatusViewer.reset();
- }
if (_stateManager) {
LOG(debug, "Deleting state manager");
_stateManager.reset();
diff --git a/storage/src/vespa/storage/storageserver/storagenode.h b/storage/src/vespa/storage/storageserver/storagenode.h
index 9b727ef3e0c..e9d3004be68 100644
--- a/storage/src/vespa/storage/storageserver/storagenode.h
+++ b/storage/src/vespa/storage/storageserver/storagenode.h
@@ -121,8 +121,6 @@ private:
// Depends on bucket databases and stop() functionality
std::unique_ptr<DeadLockDetector> _deadLockDetector;
- // Depends on dead lock detector and threadpool
- std::unique_ptr<MemoryStatusViewer> _memoryStatusViewer;
// Depends on metric manager
std::unique_ptr<StatusMetricConsumer> _statusMetrics;
// Depends on metric manager
diff --git a/storage/src/vespa/storage/storageserver/storagenodecontext.cpp b/storage/src/vespa/storage/storageserver/storagenodecontext.cpp
index 2e1aa52e68d..75e1f12773f 100644
--- a/storage/src/vespa/storage/storageserver/storagenodecontext.cpp
+++ b/storage/src/vespa/storage/storageserver/storagenodecontext.cpp
@@ -2,31 +2,15 @@
#include "storagenodecontext.h"
-#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h>
-
namespace storage {
-using framework::defaultimplementation::AllocationLogic;
-using framework::defaultimplementation::PriorityMemoryLogic;
-
StorageNodeContext::StorageNodeContext(ComponentRegister::UP compReg, framework::Clock::UP clock)
: _componentRegister(std::move(compReg)),
_clock(std::move(clock)),
- _threadPool(*_clock),
- _memoryLogic(new PriorityMemoryLogic(*_clock, 1024 * 1024 * 1024)),
- _memoryManager(AllocationLogic::UP(_memoryLogic))
+ _threadPool(*_clock)
{
_componentRegister->setClock(*_clock);
_componentRegister->setThreadPool(_threadPool);
- _componentRegister->setMemoryManager(_memoryManager);
-}
-
-void
-StorageNodeContext::setMaximumMemoryUsage(uint64_t max)
-{
- using storage::framework::defaultimplementation::PriorityMemoryLogic;
- dynamic_cast<PriorityMemoryLogic*>(_memoryLogic)
- ->setMaximumMemoryUsage(max);
}
} // storage
diff --git a/storage/src/vespa/storage/storageserver/storagenodecontext.h b/storage/src/vespa/storage/storageserver/storagenodecontext.h
index 0149f975f63..eabca618bfb 100644
--- a/storage/src/vespa/storage/storageserver/storagenodecontext.h
+++ b/storage/src/vespa/storage/storageserver/storagenodecontext.h
@@ -18,7 +18,6 @@
#include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h>
#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h>
#include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h>
namespace storage {
@@ -27,7 +26,6 @@ struct StorageNodeContext {
// Typedefs to simplify the remainder of the interface
typedef StorageComponentRegisterImpl ComponentRegister;
typedef framework::defaultimplementation::RealClock RealClock;
- typedef framework::defaultimplementation::MemoryManager MemoryManager;
/**
* Get the actual component register. Available as the actual type as the
@@ -42,14 +40,6 @@ struct StorageNodeContext {
*/
FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); }
- /**
- * Get the memory manager. Components that wants to print status of memory
- * manager need access to the actual implementation.
- */
- MemoryManager& getMemoryManager() { return _memoryManager; }
-
- void setMaximumMemoryUsage(uint64_t max);
-
protected:
// Initialization has been split in two as subclass needs to initialize
// component register before sending it on.
@@ -59,8 +49,6 @@ private:
ComponentRegister::UP _componentRegister;
framework::Clock::UP _clock;
framework::defaultimplementation::ThreadPoolImpl _threadPool;
- framework::defaultimplementation::AllocationLogic* _memoryLogic;
- MemoryManager _memoryManager;
};
diff --git a/storage/src/vespa/storage/storageutil/CMakeLists.txt b/storage/src/vespa/storage/storageutil/CMakeLists.txt
index 6317465ab8e..df942a049fb 100644
--- a/storage/src/vespa/storage/storageutil/CMakeLists.txt
+++ b/storage/src/vespa/storage/storageutil/CMakeLists.txt
@@ -2,9 +2,6 @@
vespa_add_library(storage_storageutil OBJECT
SOURCES
bloomfilter.cpp
- graph.cpp
- palette.cpp
- piechart.cpp
DEPENDS
AFTER
storage_storageconfig
diff --git a/storage/src/vespa/storage/storageutil/functor.h b/storage/src/vespa/storage/storageutil/functor.h
deleted file mode 100644
index 4ca6b4bf926..00000000000
--- a/storage/src/vespa/storage/storageutil/functor.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * @ingroup storageutil
- *
- * @brief Functors ards storage, not dependent on external messaging.
- *
- * @author H�kon Humberset
- * @date 2005-05-13
- * @version $Id$
- */
-
-#pragma once
-
-namespace storage {
-
-class Functor {
-public:
-
- /**
- * For instance, using this functor you can say:
- *
- * string mystring("this is a test");
- * for_each(mystring.begin(), mystring.end(),
- * Functor.Replace<char>(' ', '_'));
- *
- * or
- *
- * vector<string> myvector;
- * for_each(myvector.begin(), myvector.end(),
- * Functor.Replace<string>("this", "that"));
- */
- template<class T>
- class Replace {
- private:
- const T& _what;
- const T& _with;
-
- public:
- Replace(const T& what, const T& with)
- : _what(what),
- _with(with) {}
-
- void operator()(T& element) const
- { if (element == _what) element = _with; }
- };
-
- /**
- * To easily delete containers of pointers.
- *
- * for_each(myvec.begin(), myvec.end(), Functor::DeletePointer());
- */
- class DeletePointer {
- public:
- template<class T> void operator()(T *ptr) const { delete ptr; }
- };
-
-};
-
-}
-
diff --git a/storage/src/vespa/storage/storageutil/graph.cpp b/storage/src/vespa/storage/storageutil/graph.cpp
deleted file mode 100644
index 391d5c14c66..00000000000
--- a/storage/src/vespa/storage/storageutil/graph.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "graph.h"
-#include <vespa/vespalib/util/exceptions.h>
-#include <iomanip>
-
-namespace storage {
-
-Graph::Entry::Entry(const std::vector<Point>& v, const std::string& name, int32_t col)
- : points(v),
- _name(name),
- _color(col)
-{}
-
- Graph::Entry::~Entry() {}
-
-void
-Graph::printHtmlHeadAdditions(std::ostream& out, const std::string& indent)
-{
- (void) out;
- (void) indent;
- // FIXME this used to reference Yahoo-internal JS URIs
-}
-
-Graph::Graph(const std::string& name, ColorScheme cs)
- : _name(name),
- _graphs(),
- _colors(cs),
- _leftPad(50),
- _rightPad(0),
- _topPad(0),
- _bottomPad(0)
-{}
-
-Graph::~Graph() {}
-
-void
-Graph::add(const std::vector<Point>& values, const std::string& name)
-{
- if (_colors == SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Using custom color scheme you need to supply a color for each "
- "graph.", VESPA_STRLOC);
- }
- _graphs.push_back(Entry(values, name, UNDEFINED));
-}
-
-void
-Graph::add(const std::vector<Point>& values, const std::string& name, Color c)
-{
- if (_colors != SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Not using custom color scheme you cannot supply a custom "
- "color for a graph.", VESPA_STRLOC);
- }
- _graphs.push_back(Entry(values, name, c));
-}
-
-void
-Graph::add(const std::vector<Point>& values, const std::string& name, int32_t c)
-{
- if (_colors != SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Not using custom color scheme you cannot supply a custom "
- "color for a graph.", VESPA_STRLOC);
- }
- _graphs.push_back(Entry(values, name, (Color) c));
-}
-
-void
-Graph::printCanvas(std::ostream& out, uint32_t width, uint32_t height) const
-{
- out << "<div><canvas id=\"" << _name << "\" width=\"" << width
- << "\" height=\"" << height << "\"/></div>";
-}
-
-namespace {
- void printDatasetDefinition(std::ostream& o, const std::string& i,
- const std::string& n, const std::vector<Graph::Entry>& e)
- {
- o << i << " var " << n << "_dataset = {\n" << std::dec;
- bool first = true;
- for (std::vector<Graph::Entry>::const_iterator it = e.begin();
- it != e.end(); ++it)
- {
- if (!first) o << ",\n";
- first = false;
- o << i << " '" << it->_name << "': [";
- for (uint32_t j=0; j<it->points.size(); ++j) {
- if (j != 0) o << ", ";
- o << "[" << it->points[j].x << ", " << it->points[j].y << "]";
- }
- o << "]";
- }
- o << "\n" << i << " };";
- }
-
- void printCustomColorScheme(std::ostream& o, const std::string& i,
- const std::string& n, const std::vector<Graph::Entry>& e)
- {
- o << " var " << n << "_customScheme = new Hash({\n" << std::hex;
- bool first = true;
- for (std::vector<Graph::Entry>::const_iterator it = e.begin();
- it != e.end(); ++it)
- {
- if (!first) o << ",\n";
- first = false;
- o << i << " '" << it->_name << "': '#" << std::setw(6)
- << std::setfill('0') << (it->_color & 0x00FFFFFF) << "'";
- }
- o << "\n" << i << " });" << std::dec;
- }
-
- void printOptions(std::ostream& o, const std::string& i,
- const std::string& n, Graph::ColorScheme c,
- const std::vector<Graph::Axis>& xAxis,
- const std::vector<Graph::Axis>& yAxis,
- uint32_t leftpad, uint32_t rightpad,
- uint32_t toppad, uint32_t bottompad,
- uint32_t legendXPos, uint32_t legendYPos)
- {
- o << " var " << n << "_options = {\n"
- << i << " padding: {\n"
- << i << " left: " << leftpad << ",\n"
- << i << " right: " << rightpad << ",\n"
- << i << " top: " << toppad << ",\n"
- << i << " bottom: " << bottompad << ",\n"
- << i << " },\n"
- << i << " background: {\n"
- << i << " color: '#ffffff'\n"
- << i << " },\n"
- << i << " shouldFill: true,\n";
- if (c == Graph::SCHEME_CUSTOM) {
- o << i << " \"colorScheme\": " << n << "_customScheme,\n";
- } else {
- o << i << " colorScheme: '";
- switch (c) {
- case Graph::SCHEME_RED: o << "red"; break;
- case Graph::SCHEME_BLUE: o << "blue"; break;
- case Graph::SCHEME_CUSTOM: break;
- }
- o << "',\n";
- }
- o << i << " legend: {\n"
- << i << " opacity: 0.9,\n"
- << i << " position: {\n"
- << i << " top: " << legendYPos << ",\n"
- << i << " left: " << legendXPos << "\n"
- << i << " }\n"
- << i << " },\n"
- << i << " axis: {\n"
- << i << " labelColor: '#000000',\n"
- << i << " x: {\n";
- if (xAxis.size() > 0) {
- o << i << " ticks: [\n";
- for (uint32_t j=0; j<xAxis.size(); ++j) {
- o << i << " {v:" << xAxis[j].value << ", label:'"
- << xAxis[j].name << "'},\n";
- }
- o << i << " ]\n";
- }
- o << i << " },\n"
- << i << " y: {\n";
- if (yAxis.size() > 0) {
- o << i << " ticks: [\n";
- for (uint32_t j=0; j<yAxis.size(); ++j) {
- o << i << " {v:" << yAxis[j].value << ", label:'"
- << yAxis[j].name << "'},\n";
- }
- o << i << " ]\n";
- }
-
- o << i << " }\n"
- << i << " }\n"
- << i << " };";
- }
-
- void printChart(std::ostream& o, const std::string& i, const std::string& n)
- {
- o << " var " << n << "_chart = new Plotr.LineChart('" << n
- << "', " << n << "_options);\n"
- << i << " " << n << "_chart.addDataset(" << n << "_dataset);\n"
- << i << " " << n << "_chart.render();";
- }
-}
-
-void
-Graph::printScript(std::ostream& out, const std::string& indent) const
-{
- out << "<script type=\"text/javascript\">\n";
- printDatasetDefinition(out, indent, _name, _graphs);
- if (_colors == SCHEME_CUSTOM) {
- out << "\n" << indent;
- printCustomColorScheme(out, indent, _name, _graphs);
- }
- out << "\n" << indent;
- printOptions(out, indent, _name, _colors, _xAxis, _yAxis,
- _leftPad, _rightPad, _topPad, _bottomPad,
- _legendXPos, _legendYPos);
- out << "\n" << indent;
- printChart(out, indent, _name);
- out << "\n" << indent << "</script>";
-}
-
-} // storage
-
diff --git a/storage/src/vespa/storage/storageutil/graph.h b/storage/src/vespa/storage/storageutil/graph.h
deleted file mode 100644
index e39a0b59686..00000000000
--- a/storage/src/vespa/storage/storageutil/graph.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::Graph
- * \ingroup util
- *
- * \brief Helper library to print graphs in HTML.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include <ostream>
-
-namespace storage {
-
-class Graph {
-public:
- enum ColorScheme {
- SCHEME_CUSTOM,
- SCHEME_RED,
- SCHEME_BLUE
- };
- enum Color {
- UNDEFINED = -1,
- BLACK = 0x000000,
- RED = 0xFF0000,
- GREEN = 0x00FF00,
- BLUE = 0x0000FF,
- WHITE = 0xFFFFFF,
- YELLOW = 0xFFFF00
- };
- struct Point {
- double x;
- double y;
-
- Point(double x_, double y_) : x(x_), y(y_) {}
- };
- struct Entry {
- std::vector<Point> points;
- std::string _name;
- int32_t _color;
-
- Entry(const std::vector<Point>& v, const std::string& name, int32_t col);
- Entry(Entry &&) = default;
- Entry & operator = (Entry &&) = default;
- ~Entry();
- };
- struct Axis {
- double value;
- std::string name;
-
- Axis(double val, const std::string& name_) : value(val), name(name_) {}
- };
-
- static void printHtmlHeadAdditions(
- std::ostream& out, const std::string& indent = "");
-
-private:
- const std::string _name;
- std::vector<Entry> _graphs;
- ColorScheme _colors;
- std::vector<Axis> _xAxis;
- std::vector<Axis> _yAxis;
- uint32_t _leftPad;
- uint32_t _rightPad;
- uint32_t _topPad;
- uint32_t _bottomPad;
- uint32_t _legendXPos;
- uint32_t _legendYPos;
-
-public:
- Graph(const std::string&, ColorScheme = SCHEME_BLUE);
- ~Graph();
-
- void add(const std::vector<Point>&, const std::string& name);
- void add(const std::vector<Point>&, const std::string& name, Color c);
- void add(const std::vector<Point>&, const std::string& name, int32_t color);
-
- void addXAxisLabel(double value, const std::string& name)
- { _xAxis.push_back(Axis(value, name)); }
- void addYAxisLabel(double value, const std::string& name)
- { _yAxis.push_back(Axis(value, name)); }
-
- void setBorders(uint32_t left, uint32_t right,
- uint32_t top, uint32_t bottom)
- {
- _leftPad = left; _rightPad = right; _topPad = top; _bottomPad = bottom;
- }
-
- void setLegendPos(uint32_t left, uint32_t top)
- { _legendXPos = left; _legendYPos = top; }
-
- void printCanvas(std::ostream& out, uint32_t width, uint32_t height) const;
- void printScript(std::ostream& out, const std::string& indent = "") const;
-};
-
-} // storage
-
diff --git a/storage/src/vespa/storage/storageutil/palette.cpp b/storage/src/vespa/storage/storageutil/palette.cpp
deleted file mode 100644
index 31e2f9e426c..00000000000
--- a/storage/src/vespa/storage/storageutil/palette.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "palette.h"
-
-#include <iostream>
-#include <iomanip>
-
-namespace storage {
-
-namespace {
- struct Col {
- int16_t red;
- int16_t green;
- int16_t blue;
-
- Col(int16_t r, int16_t g, int16_t b) : red(r), green(g), blue(b) {}
- };
-
- std::vector<Col> createMainColors() {
- std::vector<Col> v;
- v.push_back(Col(128, 128, 128));
- v.push_back(Col(255, 0, 0));
- v.push_back(Col(255, 255, 0));
- v.push_back(Col(255, 0, 255));
- v.push_back(Col(0, 255, 0));
- v.push_back(Col(0, 255, 255));
- v.push_back(Col(0, 0, 255));
- v.push_back(Col(128, 64, 192));
- v.push_back(Col(192, 128, 64));
- v.push_back(Col(64, 192, 128));
- return v;
- }
-
- std::vector<Col> mainColors(createMainColors());
-}
-
-Palette::Palette(uint32_t colorCount)
-{
-
- uint32_t variations = (colorCount + mainColors.size() - 1)
- / (mainColors.size());
- int16_t darkvars = variations / 2;
- int16_t lightvars = (variations - 1) / 2;
-
- std::vector<Col> darkVars;
- if (darkvars > 0) {
- for (int32_t i=darkvars; i>0; --i) {
- for (uint32_t j=0; j<mainColors.size(); ++j) {
- Col& main(mainColors[j]);
- int rdiff = main.red / (darkvars + 1);
- int gdiff = main.green / (darkvars + 1);
- int bdiff = main.blue / (darkvars + 1);
- darkVars.push_back(Col(
- std::max(0, main.red - rdiff * i),
- std::max(0, main.green - gdiff * i),
- std::max(0, main.blue - bdiff * i)));
- }
- }
- }
- std::vector<Col> lightVars;
- if (lightvars > 0) {
- for (int32_t i=1; i<=lightvars; ++i) {
- for (uint32_t j=0; j<mainColors.size(); ++j) {
- Col& main(mainColors[j]);
- int rdiff = (255 - main.red) / (lightvars + 1);
- int gdiff = (255 - main.green) / (lightvars + 1);
- int bdiff = (255 - main.blue) / (lightvars + 1);
- lightVars.push_back(Col(
- std::min(255, main.red + rdiff * i),
- std::min(255, main.green + gdiff * i),
- std::min(255, main.blue + bdiff * i)));
- }
- }
- }
- for (std::vector<Col>::const_iterator it = darkVars.begin();
- it != darkVars.end(); ++it)
- {
- _colors.push_back((it->red << 16) | (it->green << 8) | it->blue);
- }
- for (std::vector<Col>::const_iterator it = mainColors.begin();
- it != mainColors.end(); ++it)
- {
- _colors.push_back((it->red << 16) | (it->green << 8) | it->blue);
- }
- for (std::vector<Col>::const_iterator it = lightVars.begin();
- it != lightVars.end(); ++it)
- {
- _colors.push_back((it->red << 16) | (it->green << 8) | it->blue);
- }
-}
-
-void
-Palette::printHtmlTablePalette(std::ostream& out) const
-{
- out << "<table>" << std::hex << std::setfill('0');
- uint32_t col = 0;
- while (col < _colors.size()) {
- out << "\n<tr>";
- for (uint32_t i=0; i<mainColors.size(); ++i) {
- out << "\n <td bgcolor=\"#" << std::setw(6) << _colors[col++]
- << "\">";
- for (uint32_t j=0; j<6; ++j) out << "&nbsp;";
- out << "</td>";
- }
- out << "\n</tr>";
- }
- out << "\n</table>" << std::dec;
-}
-
-} // storage
diff --git a/storage/src/vespa/storage/storageutil/palette.h b/storage/src/vespa/storage/storageutil/palette.h
deleted file mode 100644
index e8db8a40aff..00000000000
--- a/storage/src/vespa/storage/storageutil/palette.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::Palette
- *
- * \brief Contains a set of distinct colors.
- *
- * When writing graphics like charts one wants to use distinct colors.
- * This class defines some distinct colors.
- */
-
-#pragma once
-
-#include <vector>
-#include <cstdint>
-#include <iosfwd>
-
-namespace storage {
-
-class Palette {
- std::vector<uint32_t> _colors;
-
-public:
- Palette(uint32_t colorCount);
-
- uint32_t operator[](uint32_t colorIndex) const
- { return _colors[colorIndex]; }
-
- void printHtmlTablePalette(std::ostream& out) const;
-};
-
-} // storage
-
diff --git a/storage/src/vespa/storage/storageutil/piechart.cpp b/storage/src/vespa/storage/storageutil/piechart.cpp
deleted file mode 100644
index 60080a14ead..00000000000
--- a/storage/src/vespa/storage/storageutil/piechart.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "piechart.h"
-
-#include <iomanip>
-#include <vespa/vespalib/util/exceptions.h>
-
-namespace storage {
-
-double PieChart::_minValue = 0.0000001;
-
-PieChart::Entry::Entry(double val, const std::string& name, int32_t col)
- : _value(val), _name(name), _color(col)
-{
-}
-
-void
-PieChart::printHtmlHeadAdditions(std::ostream& out, const std::string& indent)
-{
- (void) out;
- (void) indent;
- // FIXME this used to reference Yahoo-internal JS URIs.
- // Deprecated functionality either way.
-}
-
-PieChart::PieChart(const std::string& name, ColorScheme cs)
- : _name(name),
- _values(),
- _colors(cs),
- _printLabels(true)
-{}
-
-PieChart::~PieChart() {}
-
-void
-PieChart::add(double value, const std::string& name)
-{
- if (value < _minValue) {
- std::ostringstream ost;
- ost << "Value of " << value << " is below the minimum supported value "
- << "of the pie chart (" << _minValue << ")";
- throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
- }
- if (_colors == SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Using custom color scheme you need to supply a color for each "
- "value.", VESPA_STRLOC);
- }
- _values.push_back(Entry(value, name, UNDEFINED));
-}
-
-void
-PieChart::add(double value, const std::string& name, Color c)
-{
- if (value < _minValue) {
- std::ostringstream ost;
- ost << "Value of " << value << " is below the minimum supported value "
- << "of the pie chart (" << _minValue << ")";
- throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
- }
- if (_colors != SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Not using custom color scheme you cannot supply a custom "
- "color for a value.", VESPA_STRLOC);
- }
- _values.push_back(Entry(value, name, c));
-}
-
-void
-PieChart::add(double value, const std::string& name, int32_t color)
-{
- if (value < _minValue) {
- std::ostringstream ost;
- ost << "Value of " << value << " is below the minimum supported value "
- << "of the pie chart (" << _minValue << ")";
- throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC);
- }
- if (_colors != SCHEME_CUSTOM) {
- throw vespalib::IllegalArgumentException(
- "Not using custom color scheme you cannot supply a custom "
- "color for a value.", VESPA_STRLOC);
- }
- _values.push_back(Entry(value, name, (Color) color));
-}
-
-void
-PieChart::printCanvas(std::ostream& out, uint32_t width, uint32_t height) const
-{
- out << "<div><canvas id=\"" << _name << "\" width=\"" << width
- << "\" height=\"" << height << "\"/></div>";
-}
-
-namespace {
- void printDatasetDefinition(std::ostream& o, const std::string& i,
- const std::string& n, const std::vector<PieChart::Entry>& e)
- {
- o << i << " var " << n << "_dataset = {\n" << std::dec;
- bool first = true;
- for (std::vector<PieChart::Entry>::const_iterator it = e.begin();
- it != e.end(); ++it)
- {
- if (!first) o << ",\n";
- first = false;
- o << i << " '" << it->_name << "': [[0," << it->_value
- << "]]";
- }
- o << "\n" << i << " };";
- }
-
- void printCustomColorScheme(std::ostream& o, const std::string& i,
- const std::string& n, const std::vector<PieChart::Entry>& e)
- {
- o << " var " << n << "_customScheme = new Hash({\n" << std::hex;
- bool first = true;
- for (std::vector<PieChart::Entry>::const_iterator it = e.begin();
- it != e.end(); ++it)
- {
- if (!first) o << ",\n";
- first = false;
- o << i << " '" << it->_name << "': '#" << std::setw(6)
- << std::setfill('0') << (it->_color & 0x00FFFFFF) << "'";
- }
- o << "\n" << i << " });" << std::dec;
- }
-
- void printOptions(std::ostream& o, const std::string& i,
- const std::string& n, const std::vector<PieChart::Entry>& e,
- PieChart::ColorScheme c, bool printLabels)
- {
- o << " var " << n << "_options = {\n"
- << i << " padding: {\n"
- << i << " left: 0,\n"
- << i << " right: 0,\n"
- << i << " top: 0,\n"
- << i << " bottom: 0,\n"
- << i << " },\n"
- << i << " background: {\n"
- << i << " color: '#ffffff'\n"
- << i << " },\n"
- << i << " pieRadius: '0.4',\n";
- if (c == PieChart::SCHEME_CUSTOM) {
- o << i << " \"colorScheme\": " << n << "_customScheme,\n";
- } else {
- o << i << " colorScheme: '";
- switch (c) {
- case PieChart::SCHEME_RED: o << "red"; break;
- case PieChart::SCHEME_BLUE: o << "blue"; break;
- case PieChart::SCHEME_CUSTOM: break;
- }
- o << "',\n";
- }
- o << i << " axis: {\n"
- << i << " labelColor: '#000000',\n"
- << i << " x: {\n";
- if (!printLabels) {
- o << i << " hide: true,\n";
- }
- o << i << " ticks: [\n";
- bool first = true;
- uint32_t tmp = 0;
- for (std::vector<PieChart::Entry>::const_iterator it = e.begin();
- it != e.end(); ++it)
- {
- if (!first) o << ",\n";
- first = false;
- o << i << " {v:" << tmp++ << ", label:'" << it->_name
- << "'}";
- }
- o << "\n" << i << " ]\n";
- o << i << " }\n"
- << i << " }\n"
- << i << " };";
- }
-
- void printPie(std::ostream& o, const std::string& i, const std::string& n)
- {
- o << " var " << n << "_pie = new Plotr.PieChart('" << n << "', "
- << n << "_options);\n"
- << i << " " << n << "_pie.addDataset(" << n << "_dataset);\n"
- << i << " " << n << "_pie.render();";
- }
-}
-
-void
-PieChart::printScript(std::ostream& out, const std::string& indent) const
-{
- out << "<script type=\"text/javascript\">\n";
- printDatasetDefinition(out, indent, _name, _values);
- if (_colors == SCHEME_CUSTOM) {
- out << "\n" << indent;
- printCustomColorScheme(out, indent, _name, _values);
- }
- out << "\n" << indent;
- printOptions(out, indent, _name, _values, _colors, _printLabels);
- out << "\n" << indent;
- printPie(out, indent, _name);
- out << "\n" << indent << "</script>";
-}
-
-} // storage
-
diff --git a/storage/src/vespa/storage/storageutil/piechart.h b/storage/src/vespa/storage/storageutil/piechart.h
deleted file mode 100644
index c0c26bdcb65..00000000000
--- a/storage/src/vespa/storage/storageutil/piechart.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::PieChart
- * \ingroup util
- *
- * \brief Helper library to print pie charts in HTML.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include <ostream>
-
-namespace storage {
-
-class PieChart {
-public:
- static double _minValue;
-
- enum ColorScheme {
- SCHEME_CUSTOM,
- SCHEME_RED,
- SCHEME_BLUE
- };
- enum Color {
- UNDEFINED = -1,
- BLACK = 0x000000,
- RED = 0xFF0000,
- GREEN = 0x00FF00,
- BLUE = 0x0000FF,
- WHITE = 0xFFFFFF
- };
- struct Entry {
- double _value;
- std::string _name;
- int32_t _color;
-
- Entry(double val, const std::string& name, int32_t col);
- };
-
- static void printHtmlHeadAdditions(
- std::ostream& out, const std::string& indent = "");
-
-private:
- const std::string _name;
- std::vector<Entry> _values;
- ColorScheme _colors;
- bool _printLabels;
-
-public:
- PieChart(const std::string&, ColorScheme = SCHEME_BLUE);
- ~PieChart();
-
- void printLabels(bool doprint) { _printLabels = doprint; }
-
- void add(double value, const std::string& name);
- void add(double value, const std::string& name, Color c);
- void add(double value, const std::string& name, int32_t color);
-
- void printCanvas(std::ostream& out, uint32_t width, uint32_t height) const;
- void printScript(std::ostream& out, const std::string& indent = "") const;
-};
-
-} // storage
-
diff --git a/storage/src/vespa/storage/visiting/visitor.cpp b/storage/src/vespa/storage/visiting/visitor.cpp
index b91e38e035e..0d230745875 100644
--- a/storage/src/vespa/storage/visiting/visitor.cpp
+++ b/storage/src/vespa/storage/visiting/visitor.cpp
@@ -2,7 +2,6 @@
#include "visitor.h"
#include "visitormetrics.h"
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/generic/clock/timer.h>
#include <vespa/storageapi/message/datagram.h>
#include <vespa/storage/persistence/messages.h>
@@ -267,8 +266,7 @@ Visitor::Visitor(StorageComponent& component)
_id(),
_controlDestination(),
_dataDestination(),
- _documentSelection(),
- _memoryManager(0)
+ _documentSelection()
{
}
@@ -603,10 +601,6 @@ Visitor::start(api::VisitorId id, api::StorageMessage::Id cmdId,
_documentPriority = documentPriority;
_state = STATE_RUNNING;
- if (_memoryAllocType == 0) {
- _memoryAllocType = &_component.getMemoryManager()
- .getAllocationType("VISITOR_BUFFER");
- }
LOG(debug, "Starting visitor '%s' for %" PRIu64 " buckets from %" PRIu64 " to "
"%" PRIu64 ". First is %s. Max pending replies: %u, include "
@@ -803,17 +797,7 @@ Visitor::onCreateIteratorReply(
LOG(debug, "Visitor '%s' starting to visit bucket %s.",
_id.c_str(), bucketId.toString().c_str());
- framework::MemoryToken::UP token(
- _memoryManager->allocate(
- *_memoryAllocType, _docBlockSize, _docBlockSize, _priority));
- if (token.get() == 0) {
- // Not enough memory
- return;
- }
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token), bucket,
- bucketState.getIteratorId(),
- _docBlockSize));
+ auto cmd = std::make_shared<GetIterCommand>(bucket, bucketState.getIteratorId(), _docBlockSize);
cmd->setLoadType(_initiatingCmd->getLoadType());
cmd->getTrace().setLevel(_traceLevel);
cmd->setPriority(_priority);
@@ -1223,19 +1207,8 @@ Visitor::getIterators()
it = _bucketStates.erase(it);
continue;
}
- framework::MemoryToken::UP token(
- _memoryManager->allocate(
- *_memoryAllocType, _docBlockSize, _docBlockSize,
- _priority));
- if (token.get() == 0) {
- // Not enough memory
- return true;
- }
- std::shared_ptr<GetIterCommand> cmd(
- new GetIterCommand(std::move(token),
- bucketState.getBucket(),
- bucketState.getIteratorId(),
- _docBlockSize));
+ auto cmd = std::make_shared<GetIterCommand>(
+ bucketState.getBucket(), bucketState.getIteratorId(), _docBlockSize);
cmd->setLoadType(_initiatingCmd->getLoadType());
cmd->getTrace().setLevel(_traceLevel);
cmd->setPriority(_priority);
diff --git a/storage/src/vespa/storage/visiting/visitor.h b/storage/src/vespa/storage/visiting/visitor.h
index 4436312032f..f84b105f1ed 100644
--- a/storage/src/vespa/storage/visiting/visitor.h
+++ b/storage/src/vespa/storage/visiting/visitor.h
@@ -327,7 +327,6 @@ private:
protected:
// These variables should not be altered after visitor starts. This not
// controlled by locks.
- const framework::MemoryAllocationType* _memoryAllocType;
VisitorMessageHandler* _messageHandler;
VisitorMessageSession::UP _messageSession;
documentapi::Priority::Value _documentPriority;
@@ -339,7 +338,6 @@ protected:
std::string _documentSelectionString;
std::unique_ptr<document::OrderingSpecification> _ordering;
vdslib::VisitorStatistics _visitorStatistics;
- framework::MemoryManagerInterface* _memoryManager;
bool isCompletedCalled() const { return _calledCompletedVisitor; }
@@ -365,8 +363,6 @@ public:
const api::StorageMessageAddress* getDataDestination() const
{ return _dataDestination.get(); } // Can't be null if attached
- void setAllocationType(const framework::MemoryAllocationType& mat)
- { _memoryAllocType = &mat; }
void setMaxPending(unsigned int maxPending)
{ _visitorOptions._maxPending = maxPending; }
@@ -384,8 +380,6 @@ public:
{ _docBlockTimeout = timeout; }
void setVisitorInfoTimeout(framework::MilliSecTime timeout)
{ _visitorInfoTimeout = timeout; }
- void setMemoryManager(framework::MemoryManagerInterface& mm)
- { _memoryManager = &mm; }
void setOwnNodeIndex(uint16_t nodeIndex) { _ownNodeIndex = nodeIndex; }
void setBucketSpace(document::BucketSpace bucketSpace) { _bucketSpace = bucketSpace; }
diff --git a/storage/src/vespa/storage/visiting/visitormanager.cpp b/storage/src/vespa/storage/visiting/visitormanager.cpp
index deb38bb927d..f207c4436b1 100644
--- a/storage/src/vespa/storage/visiting/visitormanager.cpp
+++ b/storage/src/vespa/storage/visiting/visitormanager.cpp
@@ -7,7 +7,6 @@
#include "countvisitor.h"
#include "testvisitor.h"
#include "recoveryvisitor.h"
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storage/common/statusmessages.h>
#include <vespa/config/common/exceptions.h>
#include <vespa/documentapi/loadtypes/loadtypeset.h>
@@ -46,10 +45,6 @@ VisitorManager::VisitorManager(const config::ConfigUri & configUri,
_enforceQueueUse(false),
_visitorFactories(externalFactories)
{
- _component.getMemoryManager().registerAllocationType(
- framework::MemoryAllocationType(
- "VISITOR_BUFFER",
- framework::MemoryAllocationType::EXTERNAL_LOAD));
_configFetcher.subscribe<vespa::config::content::core::StorVisitorConfig>(configUri.getConfigId(), this);
_configFetcher.start();
_component.registerMetric(*_metrics);
diff --git a/storage/src/vespa/storage/visiting/visitorthread.cpp b/storage/src/vespa/storage/visiting/visitorthread.cpp
index d3fed86b741..4743ac66054 100644
--- a/storage/src/vespa/storage/visiting/visitorthread.cpp
+++ b/storage/src/vespa/storage/visiting/visitorthread.cpp
@@ -2,7 +2,6 @@
#include "visitorthread.h"
#include "messages.h"
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/document/select/bodyfielddetector.h>
#include <vespa/document/select/orderingselector.h>
@@ -100,9 +99,7 @@ VisitorThread::VisitorThread(uint32_t threadIndex,
_timeBetweenTicks(1000),
_component(componentRegister, getThreadName(threadIndex)),
_messageSessionFactory(messageSessionFac),
- _visitorFactories(visitorFactories),
- _memoryBufferAlloc(
- _component.getMemoryManager().getAllocationType("VISITOR_BUFFER"))
+ _visitorFactories(visitorFactories)
{
framework::MilliSecTime maxProcessingTime(30 * 1000);
framework::MilliSecTime waitTime(1000);
@@ -473,8 +470,6 @@ VisitorThread::onCreateVisitor(
cmd->getInstanceId().c_str(), errors.str().c_str());
break;
}
- visitor->setAllocationType(_memoryBufferAlloc);
- visitor->setMemoryManager(_component.getMemoryManager());
// Set visitor parameters
if (cmd->getMaximumPendingReplyCount() != 0) {
visitor->setMaxPending(cmd->getMaximumPendingReplyCount());
diff --git a/storage/src/vespa/storage/visiting/visitorthread.h b/storage/src/vespa/storage/visiting/visitorthread.h
index 1f1459ccb2b..97ca70f3761 100644
--- a/storage/src/vespa/storage/visiting/visitorthread.h
+++ b/storage/src/vespa/storage/visiting/visitorthread.h
@@ -90,7 +90,6 @@ class VisitorThread : public framework::Runnable,
framework::Thread::UP _thread;
VisitorMessageSessionFactory& _messageSessionFactory;
VisitorFactory::Map& _visitorFactories;
- const framework::MemoryAllocationType& _memoryBufferAlloc;
public:
VisitorThread(uint32_t threadIndex,
diff --git a/storageapi/src/vespa/storageapi/messageapi/storagemessage.h b/storageapi/src/vespa/storageapi/messageapi/storagemessage.h
index 663a0ccdf4e..32f6c769e87 100644
--- a/storageapi/src/vespa/storageapi/messageapi/storagemessage.h
+++ b/storageapi/src/vespa/storageapi/messageapi/storagemessage.h
@@ -12,7 +12,6 @@
#pragma once
#include "messagehandler.h"
-#include <vespa/storageframework/generic/memory/memorytoken.h>
#include <vespa/documentapi/loadtypes/loadtype.h>
#include <vespa/messagebus/routing/route.h>
#include <vespa/messagebus/trace.h>
@@ -335,7 +334,6 @@ private:
StorageMessage(const StorageMessage&);
mutable std::unique_ptr<TransportContext> _transportContext;
- std::unique_ptr<framework::MemoryToken> _memoryToken;
protected:
static Id generateMsgId();
@@ -375,10 +373,6 @@ public:
void setAddress(const StorageMessageAddress& address)
{ _address.reset(new StorageMessageAddress(address)); }
- void setMemoryToken(std::unique_ptr<framework::MemoryToken> token) {
- _memoryToken = std::move(token);
- }
-
/**
Returns the approximate memory footprint of a storage message.
By default, returns 50 bytes. This only needs to be overriden if the
diff --git a/storageframework/CMakeLists.txt b/storageframework/CMakeLists.txt
index 5a4b2e02372..a3414a8b05c 100644
--- a/storageframework/CMakeLists.txt
+++ b/storageframework/CMakeLists.txt
@@ -11,12 +11,10 @@ vespa_define_module(
src/vespa/storageframework/defaultimplementation
src/vespa/storageframework/defaultimplementation/clock
src/vespa/storageframework/defaultimplementation/component
- src/vespa/storageframework/defaultimplementation/memory
src/vespa/storageframework/defaultimplementation/thread
src/vespa/storageframework/generic
src/vespa/storageframework/generic/clock
src/vespa/storageframework/generic/component
- src/vespa/storageframework/generic/memory
src/vespa/storageframework/generic/metric
src/vespa/storageframework/generic/status
src/vespa/storageframework/generic/thread
@@ -28,7 +26,6 @@ vespa_define_module(
TESTS
src/tests
src/tests/clock
- src/tests/memory
src/tests/status
src/tests/thread
)
diff --git a/storageframework/src/tests/CMakeLists.txt b/storageframework/src/tests/CMakeLists.txt
index eb6d45330e8..0d604d64f74 100644
--- a/storageframework/src/tests/CMakeLists.txt
+++ b/storageframework/src/tests/CMakeLists.txt
@@ -5,7 +5,6 @@ vespa_add_executable(storageframework_testrunner_app TEST
DEPENDS
storageframework_testclock
storageframework_teststatus
- storageframework_testmemory
storageframework_testthread
)
diff --git a/storageframework/src/tests/memory/.gitignore b/storageframework/src/tests/memory/.gitignore
deleted file mode 100644
index 7e7c0fe7fae..00000000000
--- a/storageframework/src/tests/memory/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/.depend
-/Makefile
diff --git a/storageframework/src/tests/memory/CMakeLists.txt b/storageframework/src/tests/memory/CMakeLists.txt
deleted file mode 100644
index 591a78f1046..00000000000
--- a/storageframework/src/tests/memory/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_library(storageframework_testmemory
- SOURCES
- memorymanagertest.cpp
- memorystatetest.cpp
- DEPENDS
- storageframework
-)
diff --git a/storageframework/src/tests/memory/memorymanagertest.cpp b/storageframework/src/tests/memory/memorymanagertest.cpp
deleted file mode 100644
index 5424b8dea82..00000000000
--- a/storageframework/src/tests/memory/memorymanagertest.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h>
-#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h>
-#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h>
-#include <vespa/vdstestlib/cppunit/macros.h>
-#include <vespa/vespalib/util/document_runnable.h>
-#include <vespa/vespalib/util/random.h>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-struct MemoryManagerTest : public CppUnit::TestFixture
-{
- void testBasics();
- void testCacheAllocation();
- void testStress();
-
- CPPUNIT_TEST_SUITE(MemoryManagerTest);
- CPPUNIT_TEST(testBasics);
- CPPUNIT_TEST(testCacheAllocation);
- CPPUNIT_TEST(testStress);
- CPPUNIT_TEST_SUITE_END();
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(MemoryManagerTest);
-
-void
-MemoryManagerTest::testBasics()
-{
- uint64_t maxMemory = 1000;
- RealClock clock;
- SimpleMemoryLogic* logic = new SimpleMemoryLogic(clock, maxMemory);
- AllocationLogic::UP allLogic(std::move(logic));
- MemoryManager manager(std::move(allLogic));
-
- const MemoryAllocationType& putAlloc(manager.registerAllocationType(
- MemoryAllocationType("put", MemoryAllocationType::EXTERNAL_LOAD)));
- const MemoryAllocationType& getAlloc(manager.registerAllocationType(
- MemoryAllocationType("get", MemoryAllocationType::EXTERNAL_LOAD)));
- const MemoryAllocationType& bufAlloc(manager.registerAllocationType(
- MemoryAllocationType("buffer")));
- const MemoryAllocationType& cacheAlloc(manager.registerAllocationType(
- MemoryAllocationType("cache", MemoryAllocationType::CACHE)));
- const MemoryState& state(logic->getState());
- const MemoryState::SnapShot& current(state.getCurrentSnapshot());
- // Basics
- {
- // * Getting a token, and release it back with correct behavior
- framework::MemoryToken::UP put = manager.allocate(putAlloc,
- 0, 100, 80);
- CPPUNIT_ASSERT(put.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(100), put->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(100), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(900), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
-
- // * Do the same while not being empty. Different type.
- framework::MemoryToken::UP get = manager.allocate(getAlloc,
- 30, 200, 50);
- CPPUNIT_ASSERT(get.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(200), get->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(300), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(700), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
-
- // * Do the same while not being empty. Same type.
- framework::MemoryToken::UP get2 = manager.allocate(
- getAlloc,
- 70,
- 150,
- 60);
-
- CPPUNIT_ASSERT(get2.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(150), get2->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(450), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(550), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
-
- // Non-external load
- // * Getting minimum when going beyond 80% full
- {
- framework::MemoryToken::UP filler = manager.allocate(putAlloc,
- 795, 795, 90);
- framework::MemoryToken::UP resize = manager.allocate(
- bufAlloc, 10, 90, 80);
- CPPUNIT_ASSERT(resize.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(10), resize->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(805), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(195), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
-
- // Non-external load
- // * Getting up to threshold if hitting it
- {
- framework::MemoryToken::UP filler = manager.allocate(putAlloc,
- 750, 750, 90);
- framework::MemoryToken::UP resize = manager.allocate(
- bufAlloc, 10, 90, 80);
- CPPUNIT_ASSERT(resize.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(50), resize->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(800), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(200), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
-
- // External load
- {
- // * Stopped when going beyond 80% full
- framework::MemoryToken::UP filler = manager.allocate(putAlloc,
- 795, 795, 90);
- framework::MemoryToken::UP put = manager.allocate(putAlloc,
- 10, 100, 80);
- CPPUNIT_ASSERT(put.get() == 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(795), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(205), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
-
- // External load
- {
- // * Getting up to threshold if hitting it
- framework::MemoryToken::UP filler = manager.allocate(putAlloc,
- 750, 750, 90);
- framework::MemoryToken::UP put = manager.allocate(putAlloc,
- 10, 100, 80);
- CPPUNIT_ASSERT(put.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(50), put->getSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(800), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(200), state.getFreeSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize());
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
-
- // Test caching..
- {
- // Cache paradigm:
- // Allocate a token taking up no space at all.
- // Give it to your ReduceMemoryUsageInterface implementation.
- // Run resize on your token in that implementation to get memory and
- // return memory. That way locking should be easy when needed.
- struct ReduceI : public framework::ReduceMemoryUsageInterface {
- framework::MemoryToken::UP _token;
-
- uint64_t reduceMemoryConsumption(const MemoryToken& token, uint64_t reduceBy) override {
- assert(&token == _token.get());
- (void) &token;
- assert(_token->getSize() >= reduceBy);
- return reduceBy;
- }
- };
- ReduceI reducer;
- framework::MemoryToken::UP cache = manager.allocate(cacheAlloc,
- 0, 0, 0, &reducer);
- CPPUNIT_ASSERT(cache.get() != 0);
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), cache->getSize());
- reducer._token = std::move(cache);
- for (uint32_t i=1; i<=50; ++i) {
- bool success = reducer._token->resize(i * 10, i * 10);
- CPPUNIT_ASSERT_EQUAL(true, success);
- }
- CPPUNIT_ASSERT_EQUAL(uint64_t(500), reducer._token->getSize());
-
- // * Ordered to free space
- framework::MemoryToken::UP put = manager.allocate(putAlloc,
- 600, 600, 80);
- CPPUNIT_ASSERT_EQUAL_MSG(manager.toString(),
- uint64_t(400), reducer._token->getSize());
- CPPUNIT_ASSERT_EQUAL_MSG(manager.toString(),
- uint64_t(600), put->getSize());
- }
- CPPUNIT_ASSERT_EQUAL_MSG(state.toString(true),
- uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL_MSG(state.toString(true),
- uint64_t(0), current.getUserCount());
-
- // Test merge and tracking of allocation counts with merge, by doing
- // operations with tokens and see that user count and used size
- // correctly go back to zero.
- {
- framework::MemoryToken::UP tok1(
- manager.allocate(putAlloc, 5, 5, 40));
- framework::MemoryToken::UP tok2(
- manager.allocate(putAlloc, 10, 10, 40));
- framework::MemoryToken::UP tok3(
- manager.allocate(putAlloc, 20, 20, 40));
- framework::MemoryToken::UP tok4(
- manager.allocate(putAlloc, 40, 40, 40));
- framework::MemoryToken::UP tok5(
- manager.allocate(putAlloc, 80, 80, 40));
- framework::MemoryToken::UP tok6(
- manager.allocate(putAlloc, 1, 1, 40));
- framework::MemoryToken::UP tok7(
- manager.allocate(putAlloc, 3, 3, 40));
- }
-}
-
-void
-MemoryManagerTest::testCacheAllocation()
-{
- uint64_t maxMemory = 3000;
-
- RealClock clock;
- SimpleMemoryLogic::UP logic(new PriorityMemoryLogic(clock, maxMemory));
- logic->setCacheThreshold(1.0);
-
- AllocationLogic::UP allLogic(std::move(logic));
- MemoryManager manager(std::move(allLogic));
-
- const MemoryAllocationType& putAlloc(manager.registerAllocationType(
- MemoryAllocationType("put", MemoryAllocationType::EXTERNAL_LOAD)));
- const MemoryAllocationType& cacheAlloc(manager.registerAllocationType(
- MemoryAllocationType("cache", MemoryAllocationType::CACHE)));
-
- framework::MemoryToken::UP token =
- manager.allocate(putAlloc,
- 50,
- 50,
- 127);
-
- CPPUNIT_ASSERT_EQUAL(50, (int)token->getSize());
-
- framework::MemoryToken::UP token2 =
- manager.allocate(cacheAlloc,
- 1000,
- 2000,
- 127);
-
- CPPUNIT_ASSERT_EQUAL(2000, (int)token2->getSize());
-
- token2->resize(2000, 3000);
-
- CPPUNIT_ASSERT_EQUAL(2950, (int)token2->getSize());
-}
-
-namespace {
-struct MemoryManagerLoadGiver : public document::Runnable,
- public ReduceMemoryUsageInterface
-{
- MemoryManager& _manager;
- const framework::MemoryAllocationType& _type;
- uint8_t _priority;
- uint32_t _minMem;
- uint32_t _maxMem;
- uint32_t _failed;
- uint32_t _ok;
- uint32_t _reduced;
- using MemoryTokenUP = std::unique_ptr<MemoryToken>;
- std::vector<MemoryTokenUP> _tokens;
- vespalib::Lock _cacheLock;
-
- MemoryManagerLoadGiver(
- MemoryManager& manager,
- const framework::MemoryAllocationType& type,
- uint8_t priority,
- uint32_t minMem,
- uint32_t maxMem,
- uint32_t tokensToKeep)
- : _manager(manager),
- _type(type),
- _priority(priority),
- _minMem(minMem),
- _maxMem(maxMem),
- _failed(0),
- _ok(0),
- _reduced(0),
- _tokens(tokensToKeep)
- {
- }
-
- uint64_t reduceMemoryConsumption(const MemoryToken&, uint64_t reduceBy) override {
- ++_reduced;
- return reduceBy;
- }
-
- void run() override {
- ReduceMemoryUsageInterface* reducer = 0;
- if (_type.isCache()) reducer = this;
- vespalib::RandomGen randomizer;
- while (running()) {
- vespalib::Lock lock(_cacheLock);
- framework::MemoryToken::UP token = _manager.allocate(
- _type, _minMem, _maxMem, _priority, reducer);
- if (token.get() == 0) {
- ++_failed;
- } else {
- ++_ok;
- }
- uint32_t index = randomizer.nextUint32(0, _tokens.size() - 1);
- _tokens[index] = MemoryTokenUP(token.release());
- }
- }
-};
-}
-
-void
-MemoryManagerTest::testStress()
-{
- uint64_t stressTimeMS = 1 * 1000;
- uint64_t maxMemory = 1 * 1024 * 1024;
- RealClock clock;
- AllocationLogic::UP logic(new PriorityMemoryLogic(clock, maxMemory));
- MemoryManager manager(std::move(logic));
-
- FastOS_ThreadPool pool(128 * 1024);
- std::vector<MemoryManagerLoadGiver*> loadGivers;
- for (uint32_t type = 0; type < 5; ++type) {
- const MemoryAllocationType* allocType = 0;
- uint32_t min = 1000, max = 5000;
- if (type == 0) {
- allocType = &manager.registerAllocationType(MemoryAllocationType(
- "default"));
- } else if (type == 1) {
- allocType = &manager.registerAllocationType(MemoryAllocationType(
- "external", MemoryAllocationType::EXTERNAL_LOAD));
- } else if (type == 2) {
- allocType = &manager.registerAllocationType(MemoryAllocationType(
- "forced", MemoryAllocationType::FORCE_ALLOCATE));
- } else if (type == 3) {
- allocType = &manager.registerAllocationType(MemoryAllocationType(
- "forcedExternal", MemoryAllocationType::FORCE_ALLOCATE
- | MemoryAllocationType::EXTERNAL_LOAD));
- } else if (type == 4) {
- allocType = &manager.registerAllocationType(MemoryAllocationType(
- "cache", MemoryAllocationType::CACHE));
- max = 30000;
- }
- for (int priority = 0; priority < 256; priority += 8) {
- loadGivers.push_back(new MemoryManagerLoadGiver(
- manager, *allocType, priority, min, max, 10));
- loadGivers.back()->start(pool);
- }
- FastOS_Thread::Sleep(stressTimeMS);
- }
- FastOS_Thread::Sleep(5 * stressTimeMS);
- uint64_t okTotal = 0, failedTotal = 0, reducedTotal = 0;
- for (uint32_t i = 0; i < loadGivers.size(); i++) {
- /*
- fprintf(stderr, "%d %s-%u: Failed %d, ok %d, reduced %d\n",
- i, loadGivers[i]->_type.getName().c_str(),
- uint32_t(loadGivers[i]->_priority),
- loadGivers[i]->_failed, loadGivers[i]->_ok,
- loadGivers[i]->_reduced); // */
- okTotal += loadGivers[i]->_ok;
- failedTotal += loadGivers[i]->_failed;
- reducedTotal += loadGivers[i]->_reduced;
- }
- for (uint32_t i = 0; i < loadGivers.size(); i++) loadGivers[i]->stop();
- for (uint32_t i = 0; i < loadGivers.size(); i++) loadGivers[i]->join();
- pool.Close();
-
- /*
- bool verbose = false;
- std::cerr << "\n\nMemory allocations at end of load:\n";
- manager.print(std::cerr, verbose, ""); // */
-
- for (uint32_t i = 0; i < loadGivers.size(); i++) {
- loadGivers[i]->_tokens.clear();
- }
- for (uint32_t i = 0; i < loadGivers.size(); i++) {
- delete loadGivers[i];
- }
- loadGivers.clear();
-
- //std::cerr << "\n\nMemory allocations at end of testl:\n";
- //manager.print(std::cerr, verbose, "");
-
- std::cerr << "\n Managed " << std::fixed
- << (okTotal / (stressTimeMS / 1000))
- << " ok, " << (failedTotal / (stressTimeMS / 1000))
- << " failed and " << (reducedTotal / (stressTimeMS / 1000))
- << " reduced allocations/s.\n ";
-
- MemoryState state(clock, 1);
- manager.getState(state);
- const MemoryState::SnapShot& current(state.getCurrentSnapshot());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize());
- CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSizeIgnoringCache());
-}
-
-} // defaultimplementation
-} // framework
-} // storage
diff --git a/storageframework/src/tests/memory/memorystatetest.cpp b/storageframework/src/tests/memory/memorystatetest.cpp
deleted file mode 100644
index cd565718632..00000000000
--- a/storageframework/src/tests/memory/memorystatetest.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/storageframework/defaultimplementation/clock/realclock.h>
-#include <vespa/storageframework/defaultimplementation/memory/memorystate.h>
-#include <vespa/vdstestlib/cppunit/macros.h>
-#include <vespa/vespalib/util/exceptions.h>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-struct MemoryStateTest : public CppUnit::TestFixture
-{
-
- void testBasics();
-
- CPPUNIT_TEST_SUITE(MemoryStateTest);
- CPPUNIT_TEST(testBasics); // Fails sometimes, test needs rewrite.
- CPPUNIT_TEST_SUITE_END();
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(MemoryStateTest);
-
-class SimpleMemoryManager : public framework::MemoryManagerInterface
-{
-private:
- std::map<std::string, framework::MemoryAllocationType> _types;
-
-public:
- void setMaximumMemoryUsage(uint64_t max) override { (void) max; }
-
- const framework::MemoryAllocationType&
- registerAllocationType(const framework::MemoryAllocationType& type) override {
- _types[type.getName()] = type;
- return _types[type.getName()];
- }
-
- const framework::MemoryAllocationType&
- getAllocationType(const std::string& name) const override {
- std::map<std::string, framework::MemoryAllocationType>::const_iterator iter =
- _types.find(name);
-
- if (iter == _types.end()) {
- throw vespalib::IllegalArgumentException("Allocation type not found: " + name);
- }
-
- return iter->second;
- }
-
- std::vector<const MemoryAllocationType*> getAllocationTypes() const override {
- std::vector<const MemoryAllocationType*> types;
- for(std::map<std::string, framework::MemoryAllocationType>
- ::const_iterator it = _types.begin(); it != _types.end(); ++it)
- {
- types.push_back(&it->second);
- }
- return types;
- }
-
- framework::MemoryToken::UP allocate(const framework::MemoryAllocationType&,
- uint64_t,
- uint64_t,
- uint8_t,
- framework::ReduceMemoryUsageInterface*) override
- {
- return framework::MemoryToken::UP();
- }
-
- uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override {
- (void) priority;
- return 0;
- }
-};
-
-void
-MemoryStateTest::testBasics()
-{
- SimpleMemoryManager manager;
-
- const MemoryAllocationType& putAlloc(manager.registerAllocationType(
- MemoryAllocationType("MESSAGE_PUT", MemoryAllocationType::EXTERNAL_LOAD)));
- const MemoryAllocationType& getAlloc(manager.registerAllocationType(
- MemoryAllocationType("MESSAGE_GET", MemoryAllocationType::EXTERNAL_LOAD)));
- const MemoryAllocationType& blockAlloc(manager.registerAllocationType(
- MemoryAllocationType("MESSAGE_DOCBLOCK")));
- const MemoryAllocationType& databaseAlloc(manager.registerAllocationType(
- MemoryAllocationType("DATABASE")));
- const MemoryAllocationType& cacheAlloc(manager.registerAllocationType(
- MemoryAllocationType("SLOTFILE_CACHE", MemoryAllocationType::CACHE)));
-
- uint32_t maxMemory = 1024;
-
- RealClock clock;
- MemoryState state1(clock, maxMemory);
- MemoryState state2(clock, maxMemory);
-
- state1.setMinJumpToUpdateMax(50);
-
- state1.addToEntry(putAlloc, 100, 10,
- MemoryState::GOT_MAX, false);
- state1.addToEntry(putAlloc, 100, 60,
- MemoryState::GOT_MAX, false);
- state1.addToEntry(blockAlloc,
- 200, 20,
- MemoryState::GOT_MIN, false);
- state1.addToEntry(getAlloc, 0, 15,
- MemoryState::DENIED, false, 0);
- state1.addToEntry(databaseAlloc, 150, 0,
- MemoryState::DENIED, true, 1);
- state1.addToEntry(cacheAlloc, 45, 0,
- MemoryState::GOT_MAX, true, 1);
-
- state2.addToEntry(putAlloc, 50, 10,
- MemoryState::GOT_MIN, false);
- state2.addToEntry(putAlloc, 20, 40,
- MemoryState::GOT_MIN, false);
-
- state1.removeFromEntry(databaseAlloc, 25, 0, 0);
- state1.removeFromEntry(putAlloc, 100, 60);
-
- MemoryState::SnapShot state3;
- state3 = state1.getMaxSnapshot();
- state3 += state2.getMaxSnapshot();
-
- std::string expected;
- expected =
- "\n"
- "MemoryState(Max memory: 1024) {\n"
- " Current: SnapShot(Used 470, w/o cache 425) {\n"
- " Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n"
- " DATABASE(0): Used(125 B / 1) Stats(1, 0, 0, 1, 1)\n"
- " MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n"
- " MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n"
- " MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n"
- " MESSAGE_PUT(60): Used(0 B / 0) Stats(1, 1, 0, 0, 0)\n"
- " SLOTFILE_CACHE(0): Used(45 B / 1) Stats(1, 1, 0, 0, 1)\n"
- " }\n"
- " Max: SnapShot(Used 550, w/o cache 550) {\n"
- " Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n"
- " DATABASE(0): Used(150 B / 1) Stats(1, 0, 0, 1, 1)\n"
- " MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n"
- " MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n"
- " MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n"
- " MESSAGE_PUT(60): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n"
- " }\n"
- "}";
-
- CPPUNIT_ASSERT_EQUAL(expected, "\n" + state1.toString(true));
- expected = "\n"
-"MemoryState(Max memory: 1024) {\n"
-" Current: SnapShot(Used 70, w/o cache 70) {\n"
-" Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n"
-" MESSAGE_PUT(10): Used(50 B / 1) Stats(1, 0, 1, 0, 0)\n"
-" MESSAGE_PUT(40): Used(20 B / 1) Stats(1, 0, 1, 0, 0)\n"
-" }\n"
-"}";
- CPPUNIT_ASSERT_EQUAL(expected, "\n" + state2.toString(true));
- expected = "\n"
-"SnapShot(Used 550, w/o cache 550) {\n"
-" Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n"
-" DATABASE(0): Used(150 B / 1) Stats(1, 0, 0, 1, 1)\n"
-" MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n"
-" MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n"
-" MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n"
-" MESSAGE_PUT(60): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n"
-"}";
- CPPUNIT_ASSERT_EQUAL(expected, "\n" + state3.toString(true));
-}
-
-} // defaultimplementation
-} // framework
-} // storage
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt b/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt
index 25e7a9812cf..ea6a7e15543 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt
@@ -3,7 +3,6 @@ vespa_add_library(storageframework_defaultimplementation
SOURCES
$<TARGET_OBJECTS:storageframework_clockimpl>
$<TARGET_OBJECTS:storageframework_componentimpl>
- $<TARGET_OBJECTS:storageframework_memoryimpl>
$<TARGET_OBJECTS:storageframework_threadimpl>
INSTALL lib64
DEPENDS
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
index a03f79b008d..84b091d25e3 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp
@@ -63,16 +63,6 @@ ComponentRegisterImpl::setMetricManager(metrics::MetricManager& mm)
}
void
-ComponentRegisterImpl::setMemoryManager(MemoryManagerInterface& mm)
-{
- vespalib::LockGuard lock(_componentLock);
- _memoryManager = &mm;
- for (uint32_t i=0; i<_components.size(); ++i) {
- _components[i]->setMemoryManager(mm);
- }
-}
-
-void
ComponentRegisterImpl::setClock(Clock& c)
{
vespalib::LockGuard lock(_componentLock);
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h
index 7319a1e7d57..3e3dd08e1df 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h
@@ -71,7 +71,6 @@ public:
void requestShutdown(vespalib::stringref reason) override;
void setMetricManager(metrics::MetricManager&);
- void setMemoryManager(MemoryManagerInterface&);
void setClock(Clock&);
void setThreadPool(ThreadPool&);
void setUpgradeFlag(UpgradeFlags flag);
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp
index 0f4d0ebe070..5efd638ec26 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp
@@ -7,13 +7,9 @@ namespace storage::framework::defaultimplementation {
TestComponentRegister::TestComponentRegister(ComponentRegisterImpl::UP compReg)
: _compReg(std::move(compReg)),
_clock(),
- _threadPool(_clock),
- _memoryManager()
+ _threadPool(_clock)
{
assert(_compReg.get() != 0);
- // Set a memory manager, so users can register memory types and
- // ask for memory.
- _compReg->setMemoryManager(_memoryManager);
// Set a fake clock, giving test control of clock
_compReg->setClock(_clock);
// Set a thread pool so components can make threads in tests.
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h
index 5226765dc40..4983d5dcdfc 100644
--- a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h
+++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h
@@ -15,7 +15,6 @@
#include "componentregisterimpl.h"
#include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h>
-#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h>
#include <vespa/storageframework/defaultimplementation/clock/fakeclock.h>
namespace storage::framework::defaultimplementation {
@@ -24,7 +23,6 @@ class TestComponentRegister {
ComponentRegisterImpl::UP _compReg;
FakeClock _clock;
ThreadPoolImpl _threadPool;
- NoMemoryManager _memoryManager;
public:
TestComponentRegister(ComponentRegisterImpl::UP compReg);
@@ -34,7 +32,6 @@ public:
FakeClock& getClock() { return _clock; }
ThreadPoolImpl& getThreadPoolImpl() { return _threadPool; }
FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); }
- NoMemoryManager& getMemoryManager() { return _memoryManager; }
};
}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore b/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore
deleted file mode 100644
index 7e7c0fe7fae..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/.depend
-/Makefile
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt b/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt
deleted file mode 100644
index e0aa10a6716..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_library(storageframework_memoryimpl OBJECT
- SOURCES
- memorymanager.cpp
- simplememorylogic.cpp
- memorystate.cpp
- prioritymemorylogic.cpp
- nomemorymanager.cpp
- DEPENDS
-)
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h
deleted file mode 100644
index 5af2c9ff929..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h
+++ /dev/null
@@ -1,48 +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 <storage/memorymanager/memorymanager.h>
-#include <storage/memorymanager/memorystate.h>
-
-namespace storage {
-
-class EmptyMemoryLogic : public AllocationLogic
-{
-private:
- MemoryState _state;
-
-public:
- EmptyMemoryLogic() : _state(100) {}
-
- virtual void getState(MemoryState& state, bool resetMax) {
- state = _state;
- if (resetMax) _state.resetMax();
- }
-
- virtual MemoryToken::UP allocate(
- const AllocationType& type,
- uint64_t, uint64_t max,
- storage::api::StorageMessage::Priority p,
- ReduceMemoryUsageInterface* = 0) {
- return MemoryToken::UP(
- new MemoryToken(*this, type, max, p));
- }
-
- virtual bool resize(MemoryToken& token, uint64_t min, uint64_t max) {
- setTokenSize(token, max);
- return true;
- }
-
- virtual void freeToken(MemoryToken&) {}
-
- virtual void print(std::ostream& out, bool verbose,
- const std::string& indent) const
- {
- (void) verbose; (void) indent;
- out << "EmptyMemoryLogic()";
- }
-};
-
-} // storage
-
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp
deleted file mode 100644
index 5af222d4b36..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "memorymanager.h"
-#include "memorystate.h"
-#include <vespa/vespalib/util/exceptions.h>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-MemoryTokenImpl::MemoryTokenImpl(AllocationLogic& logic,
- const MemoryAllocationType& type,
- uint64_t allocated,
- uint8_t p,
- ReduceMemoryUsageInterface* reducer)
- : _logic(logic),
- _reducer(reducer),
- _currentlyAllocated(allocated),
- _allocCount(1),
- _type(type),
- _priority(p)
-{
-}
-
-MemoryTokenImpl::~MemoryTokenImpl()
-{
- _logic.freeToken(*this);
-}
-
-bool
-MemoryTokenImpl::resize(uint64_t min, uint64_t max)
-{
- return _logic.resize(*this, min, max);
-}
-
-void
-MemoryTokenImpl::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- (void) verbose; (void) indent;
- out << "MemoryToken(" << _type.getName() << ": Allocated(" << _allocCount << " - "
- << _currentlyAllocated << ")";
-}
-
-AllocationLogic::~AllocationLogic()
-{
-}
-
-MemoryToken::UP
-AllocationLogic::allocate(const MemoryAllocationType& type,
- uint64_t min,
- uint64_t max,
- uint8_t priority,
- ReduceMemoryUsageInterface* reducer)
-{
- MemoryToken::UP token(allocate(type, priority, reducer));
- assert(token.get());
- if (!resize(*token, min, max, 1)) token.reset();
- return token;
-}
-
-bool
-AllocationLogic::resize(MemoryTokenImpl& token, uint64_t min, uint64_t max)
-{
- return resize(token, min, max, 0);
-}
-
-MemoryManager::MemoryManager(AllocationLogic::UP logic)
- : _logic(std::move(logic))
-{
- if (_logic.get() == 0) {
- throw vespalib::IllegalArgumentException(
- "Needs a real logic class to run. (Got null pointer)",
- VESPA_STRLOC);
- }
-}
-
-MemoryManager::~MemoryManager()
-{
-}
-
-void
-MemoryManager::setMaximumMemoryUsage(uint64_t max)
-{
- _logic->setMaximumMemoryUsage(max);
-}
-
-void
-MemoryManager::getState(MemoryState& state, bool resetMax)
-{
- return _logic->getState(state, resetMax);
-}
-
-const MemoryAllocationType&
-MemoryManager::registerAllocationType(const MemoryAllocationType& type)
-{
- vespalib::LockGuard lock(_typeLock);
- _types[type.getName()] = MemoryAllocationType::UP(
- new MemoryAllocationType(type));
- return *_types[type.getName()];
-}
-
-const MemoryAllocationType&
-MemoryManager::getAllocationType(const std::string& name) const
-{
- vespalib::LockGuard lock(_typeLock);
- std::map<std::string, MemoryAllocationType::UP>::const_iterator it(
- _types.find(name));
- if (it == _types.end()) {
- throw vespalib::IllegalArgumentException(
- "Allocation type not found: " + name, VESPA_STRLOC);
- }
- return *it->second;
-}
-
-std::vector<const MemoryAllocationType*>
-MemoryManager::getAllocationTypes() const
-{
- vespalib::LockGuard lock(_typeLock);
- std::vector<const MemoryAllocationType*> types;
- for(std::map<std::string, MemoryAllocationType::UP>::const_iterator it
- = _types.begin(); it != _types.end(); ++it)
- {
- types.push_back(it->second.get());
- }
- return types;
-}
-
-MemoryToken::UP
-MemoryManager::allocate(const MemoryAllocationType& type,
- uint64_t min,
- uint64_t max,
- uint8_t p,
- ReduceMemoryUsageInterface* i)
-{
- return _logic->allocate(type, min, max, p, i);
-}
-
-uint64_t
-MemoryManager::getMemorySizeFreeForPriority(uint8_t priority) const
-{
- return _logic->getMemorySizeFreeForPriority(priority);
-}
-
-void
-MemoryManager::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- (void) verbose; (void) indent;
- out << "Memory Manager {" << "\n" << indent << " ";
- _logic->print(out, verbose, indent + " ");
- out << "\n" << indent << "}";
-}
-
-} // defaultimplementation
-} // framework
-} // storage
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h
deleted file mode 100644
index 86a2920095e..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::MemoryManager
- *
- * \brief Utility for tracking memory usage on distributor and storage nodes.
- *
- * The memory manager is responsible for limiting the memory allocated by
- * various users within VDS storage and distributor nodes, such that these
- * nodes don't use more memory than they can use, to avoid swapping.
- *
- * It will produce a status page to view big memory users, and give some
- * historic data. It will track memory users and give them less and less memory
- * as closer we are to utilizing all memory we are able to use. When getting
- * close to full it will deny memory allocations to incoming commands that wants
- * to use additional memory in able to complete the operation.
- *
- * The main class here defines the interface the client has to worry about. It
- * should thus not point to the implementation in any way.
- *
- */
-
-#pragma once
-
-#include <map>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
-#include <vespa/vespalib/util/printable.h>
-#include <vespa/vespalib/util/sync.h>
-
-namespace storage::framework::defaultimplementation {
-
-class MemoryManager;
-class AllocationLogic;
-class MemoryState;
-
-class MemoryTokenImpl : public vespalib::Printable,
- public MemoryToken
-{
- friend class AllocationLogic;
-
- AllocationLogic& _logic;
- ReduceMemoryUsageInterface* _reducer;
- uint64_t _currentlyAllocated;
- uint32_t _allocCount;
- const MemoryAllocationType& _type;
- uint8_t _priority;
-
-public:
- typedef std::unique_ptr<MemoryTokenImpl> UP;
-
- MemoryTokenImpl(const MemoryTokenImpl &) = delete;
- MemoryTokenImpl & operator = (const MemoryTokenImpl &) = delete;
- MemoryTokenImpl(AllocationLogic& logic,
- const MemoryAllocationType& type,
- uint64_t allocated,
- uint8_t priority,
- ReduceMemoryUsageInterface* = 0);
-
- ~MemoryTokenImpl();
-
- uint64_t getSize() const override { return _currentlyAllocated; }
- uint64_t getAllocationCount() const { return _allocCount; }
- const MemoryAllocationType& getType() const { return _type; }
- ReduceMemoryUsageInterface* getReducer() const { return _reducer; }
-
- uint8_t getPriority() const { return _priority; }
-
- bool resize(uint64_t min, uint64_t max) override;
-
- void print(std::ostream& out, bool verbose,
- const std::string& indent) const override;
-};
-
-class AllocationLogic : public vespalib::Printable
-{
-protected:
- /**
- * MemoryTokens are friends with this class, such that logic classes
- * can use this function to alter token size, without function for that
- * being public.
- */
- void setTokenSize(MemoryTokenImpl& token, uint64_t size)
- { token._currentlyAllocated = size; }
-
- virtual MemoryToken::UP allocate(const MemoryAllocationType&,
- uint8_t priority,
- ReduceMemoryUsageInterface*) = 0;
- virtual bool resize(MemoryToken& token, uint64_t min, uint64_t max,
- uint32_t allocationCounts) = 0;
-public:
- typedef std::unique_ptr<AllocationLogic> UP;
- virtual ~AllocationLogic() = 0;
-
- virtual void setMaximumMemoryUsage(uint64_t max) = 0;
-
- virtual void getState(MemoryState&, bool resetMax = false) = 0;
-
- /**
- * Decide how much to allocate for this request. Should be between min
- * and max, unless it's of a type that can be denied (such as external
- * requests), in which case we can also deny allocation by returning a null
- * token.
- */
- MemoryToken::UP allocate(const MemoryAllocationType&,
- uint64_t min,
- uint64_t max,
- uint8_t priority,
- ReduceMemoryUsageInterface* = 0);
- /**
- * Resize the size in a token. If more memory is requested, then it might
- * fail. The sizes given in min and max is given as total min and max,
- * including any memory you may already have. If successful, the logic will
- * have added this size to the token passed in.
- */
- bool resize(MemoryTokenImpl& token, uint64_t min, uint64_t max);
-
- // Called by token destructor to free up tracked resources
- virtual void freeToken(MemoryTokenImpl& token) = 0;
-
- virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const = 0;
-
- // vespalib::Printable implementation
- virtual void print(std::ostream& out, bool verbose,
- const std::string& indent) const override = 0;
-};
-
-class MemoryManager : public vespalib::Printable,
- public MemoryManagerInterface
-{
- AllocationLogic::UP _logic;
- vespalib::Lock _typeLock;
- std::map<std::string, MemoryAllocationType::UP> _types;
-
-public:
- typedef std::unique_ptr<MemoryManager> UP;
-
- MemoryManager(AllocationLogic::UP);
- ~MemoryManager();
-
- void setMaximumMemoryUsage(uint64_t max) override;
- virtual void getState(MemoryState& state, bool resetMax = false);
-
- const MemoryAllocationType&registerAllocationType(const MemoryAllocationType& type) override;
- const MemoryAllocationType&getAllocationType(const std::string& name) const override;
-
- std::vector<const MemoryAllocationType*> getAllocationTypes() const override;
-
- MemoryToken::UP allocate(const MemoryAllocationType&, uint64_t min, uint64_t max,
- uint8_t p, ReduceMemoryUsageInterface* = 0) override;
-
- uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override;
-
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
-
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp
deleted file mode 100644
index 2351edd267c..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "memorystate.h"
-#include <sstream>
-
-#include <vespa/log/log.h>
-LOG_SETUP(".memory.state");
-
-namespace storage::framework::defaultimplementation {
-
-MemoryState::Entry::Entry()
- : _currentUsedSize(0),
- _totalUserCount(0),
- _currentUserCount(0),
- _wantedCount(0),
- _minimumCount(0),
- _deniedCount(0),
- _forcedBeyondMaximumCount(0)
-{
-}
-
-void
-MemoryState::Entry::operator+=(const Entry& other)
-{
- _currentUsedSize += other._currentUsedSize;
- _currentUserCount += other._currentUserCount;
- _totalUserCount += other._totalUserCount;
- _wantedCount += other._wantedCount;
- _minimumCount += other._minimumCount;
- _deniedCount += other._deniedCount;
- _forcedBeyondMaximumCount += other._forcedBeyondMaximumCount;
-}
-
-MemoryState::SnapShot&
-MemoryState::SnapShot::operator+=(const MemoryState::SnapShot& other)
-{
- for (AllocationMap::const_iterator it = other._allocations.begin();
- it != other._allocations.end(); ++it)
- {
- PriorityMap& map(_allocations[it->first]);
- for (PriorityMap::const_iterator it2 = it->second.begin();
- it2 != it->second.end(); ++it2)
- {
- Entry& entry(map[it2->first]);
- entry += it2->second;
- }
- }
- return *this;
-}
-
-uint64_t
-MemoryState::SnapShot::getUserCount() const
-{
- uint64_t count = 0;
- for (AllocationMap::const_iterator it = _allocations.begin();
- it != _allocations.end(); ++it)
- {
- for (PriorityMap::const_iterator it2 = it->second.begin();
- it2 != it->second.end(); ++it2)
- {
- count += it2->second._currentUserCount;
- }
- }
- return count;
-}
-
-MemoryState::MemoryState(Clock& clock, uint64_t maxMemory)
- : _clock(&clock),
- _maxMemory(maxMemory),
- _current(),
- _max(),
- _minJumpToUpdateMax(10 * 1024 * 1024)
-{
-}
-
-MemoryState::MemoryState(const MemoryState &) = default;
-MemoryState & MemoryState::operator = (const MemoryState &) = default;
-
-MemoryState::~MemoryState() {}
-
-void
-MemoryState::addToEntry(const MemoryAllocationType& type, uint64_t memory,
- uint8_t priority,
- AllocationResult result, bool forcedAllocation,
- uint64_t allocationCounts)
-{
- LOG(spam, "Allocating memory %s - %lu bytes at priority %u. "
- "Count %lu.",
- type.getName().c_str(), memory, priority, allocationCounts);
- PriorityMap& map(_current._allocations[&type]);
- Entry& e(map[priority]);
- e._currentUsedSize += memory;
- e._totalUserCount += allocationCounts;
- if (allocationCounts == 0) {
- // Resizes adding no more users still count as another total
- // allocation attempt.
- ++e._totalUserCount;
- }
- e._currentUserCount += allocationCounts;
- switch (result) {
- case GOT_MAX: ++e._wantedCount; break;
- case GOT_MIN: ++e._minimumCount; break;
- case DENIED: ++e._deniedCount; break;
- }
- if (forcedAllocation) ++e._forcedBeyondMaximumCount;
- _current._usedMemory += memory;
- if (!type.isCache()) {
- _current._usedWithoutCache += memory;
- }
- if (_current._usedWithoutCache
- > _max._usedWithoutCache + _minJumpToUpdateMax)
- {
- LOG(spam, "Updating max to current %lu bytes of memory used",
- _current._usedWithoutCache);
- _max = _current;
- _max._timeTaken = _clock->getTimeInSeconds();
- }
-}
-
-void
-MemoryState::removeFromEntry(const MemoryAllocationType& type, uint64_t memory,
- uint8_t priority,
- uint64_t allocationCounts)
-{
- LOG(spam, "Freeing memory %s - %lu bytes at priority %u. "
- "Count %lu.",
- type.getName().c_str(), memory, priority, allocationCounts);
- PriorityMap& map(_current._allocations[&type]);
- Entry& e(map[priority]);
- e._currentUsedSize -= memory;
- e._currentUserCount -= allocationCounts;
- _current._usedMemory -= memory;
- if (!type.isCache()) {
- _current._usedWithoutCache -= memory;
- }
-}
-
-void
-MemoryState::Entry::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- (void) verbose; (void) indent;
- std::ostringstream ost;
- ost << "Used(" << _currentUsedSize << " B / "
- << _currentUserCount << ") ";
- for (uint32_t i=ost.str().size(); i<20; ++i) {
- ost << " ";
- }
-
- out << ost.str()
- << "Stats(" << _totalUserCount
- << ", " << _wantedCount
- << ", " << _minimumCount
- << ", " << _deniedCount
- << ", " << _forcedBeyondMaximumCount << ")";
-}
-
-namespace {
- void printAllocations(std::ostream& out,
- const MemoryState::AllocationMap& map,
- const std::string& indent)
- {
- std::map<std::string, std::string> allocs;
-
- for (MemoryState::AllocationMap::const_iterator it = map.begin();
- it != map.end(); ++it)
- {
- for (MemoryState::PriorityMap::const_iterator it2
- = it->second.begin(); it2 != it->second.end(); ++it2)
- {
- std::ostringstream name;
- name << it->first->getName() << "("
- << static_cast<uint16_t>(it2->first) << "): ";
- for (uint32_t i=name.str().size(); i<25; ++i) {
- name << " ";
- }
-
- std::ostringstream tmp;
- it2->second.print(tmp, true, indent + " ");
-
- allocs[name.str()] = tmp.str();
- }
- }
-
- for (std::map<std::string, std::string>::const_iterator it
- = allocs.begin(); it != allocs.end(); ++it)
- {
- out << "\n" << indent << " " << it->first << it->second;
- }
- }
-}
-
-void
-MemoryState::SnapShot::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "SnapShot(Used " << _usedMemory << ", w/o cache "
- << _usedWithoutCache;
- if (verbose) {
- out << ") {";
- if (_usedMemory > 0) {
- out << "\n" << indent << " Type(Pri): Used(Size/Allocs) "
- << "Stats(Allocs, Wanted, Min, Denied, Forced)";
- }
- printAllocations(out, _allocations, indent);
- out << "\n" << indent << "}";
- }
-}
-
-void
-MemoryState::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- bool maxSet = (_max._usedWithoutCache > _current._usedWithoutCache);
- out << "MemoryState(Max memory: " << _maxMemory << ") {"
- << "\n" << indent << " Current: ";
- _current.print(out, verbose, indent + " ");
- if (maxSet) {
- out << "\n" << indent << " Max: ";
- _max.print(out, verbose, indent + " ");
- }
- out << "\n" << indent << "}";
-}
-
-}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h
deleted file mode 100644
index 3d670d43844..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::MemoryState
- *
- * \brief Shows the state of current memory users
- *
- */
-
-#pragma once
-
-#include "memorymanager.h"
-#include <vespa/storageframework/generic/clock/clock.h>
-#include <vespa/vespalib/util/sync.h>
-
-namespace storage::framework::defaultimplementation {
-
-class MemoryState : public vespalib::Printable {
-public:
- struct Entry {
- // Total number of bytes allocated to this entry right now
- uint64_t _currentUsedSize;
- // Total number of allocations done on this entry
- uint64_t _totalUserCount;
- // Total number of allocations for this entry right now
- uint32_t _currentUserCount;
- // Amount of times this entry has gotten all the memory it wanted
- uint32_t _wantedCount;
- // Amount of times this entry has gotten less than all the memory
- // it wanted
- uint32_t _minimumCount;
- // Amount of times this entry has been denied getting memory
- uint32_t _deniedCount;
- // Amount of times this entry has forced memory allocations beyond
- // the maximum
- uint32_t _forcedBeyondMaximumCount;
-
- Entry();
-
- void print(std::ostream& out, bool verbose,
- const std::string& indent) const;
-
- /**
- * Set this instances counts to the counts from the other entry.
- */
- void transferCounts(const Entry& other);
-
- void operator+=(const Entry& other);
- };
-
- typedef std::map<uint8_t, Entry> PriorityMap;
- typedef std::map<const MemoryAllocationType*, PriorityMap> AllocationMap;
-
- /**
- * A snapshot contains data for either current or max seen data.
- * When a new maximum is seen, current is copied to max.
- */
- class SnapShot : public vespalib::Printable {
- friend class MemoryState;
-
- uint64_t _usedMemory;
- uint64_t _usedWithoutCache;
- SecondTime _timeTaken;
- AllocationMap _allocations;
-
- public:
- SnapShot() : vespalib::Printable() { clear(); }
- SnapShot(const SnapShot& o) : vespalib::Printable() { (*this) = o; }
-
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
-
- void clear() {
- _usedMemory = 0;
- _usedWithoutCache = 0;
- _timeTaken.setTime(0);
- _allocations.clear();
- }
-
- SnapShot& operator=(const SnapShot& other) {
- _usedMemory = other._usedMemory;
- _usedWithoutCache = other._usedWithoutCache;
- _timeTaken = other._timeTaken;
- _allocations = other._allocations;
- return *this;
- }
-
- SnapShot& operator+=(const SnapShot& other);
-
- const AllocationMap& getAllocations() const { return _allocations; }
- uint64_t getUsedSize() const { return _usedMemory; }
- uint64_t getUsedSizeIgnoringCache() const { return _usedWithoutCache; }
- uint64_t getUserCount() const;
- };
-
-private:
- Clock* _clock;
- uint64_t _maxMemory;
- SnapShot _current;
- SnapShot _max;
- uint32_t _minJumpToUpdateMax;
-
-public:
- MemoryState(Clock& clock, uint64_t maxMemory);
- MemoryState(const MemoryState &);
- MemoryState & operator = (const MemoryState &);
- MemoryState(MemoryState &&) = default;
- MemoryState & operator = (MemoryState &&) = default;
- ~MemoryState();
-
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
-
- void setMaximumMemoryUsage(uint64_t max) { _maxMemory = max; }
- void setMinJumpToUpdateMax(uint32_t bytes) { _minJumpToUpdateMax = bytes; }
-
- enum AllocationResult { GOT_MAX, GOT_MIN, DENIED };
- void addToEntry(const MemoryAllocationType& type, uint64_t memory,
- uint8_t priority,
- AllocationResult result, bool forcedAllocation = false,
- uint64_t allocationCounts = 1);
-
- void removeFromEntry(const MemoryAllocationType& type, uint64_t memory,
- uint8_t priority,
- uint64_t allocationCounts = 1);
- void resetMax() {
- _max = _current;
- _max._timeTaken = _clock->getTimeInSeconds();
- }
-
- const SnapShot& getCurrentSnapshot() const { return _current; }
- const SnapShot& getMaxSnapshot() const { return _max; }
-
-
- uint64_t getTotalSize() const { return _maxMemory; }
- uint64_t getFreeSize() const {
- return _maxMemory > _current._usedMemory
- ? _maxMemory - _current._usedMemory : 0;
- }
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp
deleted file mode 100644
index 16e03240a4f..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "nomemorymanager.h"
-#include <vespa/vespalib/util/exceptions.h>
-
-namespace storage::framework::defaultimplementation {
-
-NoMemoryManager::~NoMemoryManager() {}
-
-const MemoryAllocationType&
-NoMemoryManager::registerAllocationType(const MemoryAllocationType& type)
-{
- vespalib::LockGuard lock(_typeLock);
- _types[type.getName()] = MemoryAllocationType::UP(
- new MemoryAllocationType(type));
- return *_types[type.getName()];
-}
-
-const MemoryAllocationType&
-NoMemoryManager::getAllocationType(const std::string& name) const
-{
- vespalib::LockGuard lock(_typeLock);
- std::map<std::string, MemoryAllocationType::UP>::const_iterator it(
- _types.find(name));
- if (it == _types.end()) {
- throw vespalib::IllegalArgumentException(
- "Allocation type not found: " + name, VESPA_STRLOC);
- }
- return *it->second;
-}
-
-std::vector<const MemoryAllocationType*>
-NoMemoryManager::getAllocationTypes() const
-{
- vespalib::LockGuard lock(_typeLock);
- std::vector<const MemoryAllocationType*> types;
- for(std::map<std::string, MemoryAllocationType::UP>::const_iterator it
- = _types.begin(); it != _types.end(); ++it)
- {
- types.push_back(it->second.get());
- }
- return types;
-}
-
-}
-
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h
deleted file mode 100644
index 7f90b1360b8..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::NoMemoryManager
- *
- * \brief Memory manager that gives out max memory to everyone.
- *
- * Memory manager to use for testing and for apps not wanting to track memory.
- * This manager will merely give out max to everyone who asks and not even keep
- * track of anything.
- */
-
-#pragma once
-
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
-#include <vespa/vespalib/util/printable.h>
-#include <vespa/vespalib/util/sync.h>
-#include <map>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-class SimpleMemoryTokenImpl : public MemoryToken
-{
- uint64_t _allocated;
-
-public:
- SimpleMemoryTokenImpl(const SimpleMemoryTokenImpl &) = delete;
- SimpleMemoryTokenImpl & operator = (const SimpleMemoryTokenImpl &) = delete;
- SimpleMemoryTokenImpl(uint64_t allocated) : _allocated(allocated) {}
-
- uint64_t getSize() const override { return _allocated; }
- bool resize(uint64_t /* min */, uint64_t max) override { _allocated = max; return true; }
-};
-
-class NoMemoryManager : public MemoryManagerInterface
-{
- vespalib::Lock _typeLock;
- std::map<std::string, MemoryAllocationType::UP> _types;
-
-public:
- typedef std::unique_ptr<NoMemoryManager> UP;
-
- ~NoMemoryManager();
-
- void setMaximumMemoryUsage(uint64_t) override {}
- const MemoryAllocationType & registerAllocationType(const MemoryAllocationType& type) override;
- const MemoryAllocationType & getAllocationType(const std::string& name) const override;
-
- MemoryToken::UP allocate(const MemoryAllocationType&, uint64_t /* min */, uint64_t max,
- uint8_t /* priority */, ReduceMemoryUsageInterface* = 0) override
- {
- return SimpleMemoryTokenImpl::UP(new SimpleMemoryTokenImpl(max));
- }
- uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override {
- (void) priority;
- return std::numeric_limits<uint64_t>().max();
- }
-
- std::vector<const MemoryAllocationType*> getAllocationTypes() const override;
-};
-
-} // defaultimplementation
-} // framework
-} // storage
-
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp
deleted file mode 100644
index 51bb7ba5019..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp
+++ /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.
-
-#include "prioritymemorylogic.h"
-
-#include <vespa/log/log.h>
-LOG_SETUP(".memory.logic.priority");
-
-namespace storage::framework::defaultimplementation {
-
-PriorityMemoryLogic::PriorityMemoryLogic(Clock& c, uint64_t maxMem)
- : SimpleMemoryLogic(c, maxMem)
-{
- LOG(debug, "Setup priority memory logic with max memory of %lu bytes", maxMem);
-}
-
-float
-PriorityMemoryLogic::getNonCacheThreshold(uint8_t priority) const
-{
- return 0.6 + ((255 - priority) / 255.0) * 0.4;
-}
-
-void
-PriorityMemoryLogic::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "PriorityMemoryLogic() : ";
- SimpleMemoryLogic::print(out, verbose, indent);
-}
-
-}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h
deleted file mode 100644
index 148c4f40aee..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-/**
- * \class storage::PriorityMemoryLogic
- *
- * \brief Priority logic deciding who should get memory and how much.
- *
- */
-
-#pragma once
-
-#include "simplememorylogic.h"
-#include <vespa/vespalib/util/sync.h>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-struct PriorityMemoryLogic : public SimpleMemoryLogic
-{
- PriorityMemoryLogic(Clock&, uint64_t maxMemory);
- float getNonCacheThreshold(uint8_t priority) const override;
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
-};
-
-} // defaultimplementation
-} // framework
-} // storage
-
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp
deleted file mode 100644
index 76d6f990a00..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "simplememorylogic.h"
-#include <vespa/vespalib/util/exceptions.h>
-
-#include <vespa/log/bufferedlogger.h>
-LOG_SETUP(".memory.logic.simple");
-
-namespace storage::framework::defaultimplementation {
-
-SimpleMemoryLogic::SimpleMemoryLogic(Clock& c, uint64_t maxMemory)
- : _cacheThreshold(0.98),
- _nonCacheThreshold(0.8),
- _state(c, maxMemory),
- _reducers()
-{
- LOG(debug, "Setup simple memory logic with max memory of %" PRIu64 " bytes", maxMemory);
-}
-
-SimpleMemoryLogic::~SimpleMemoryLogic()
-{
-}
-
-void
-SimpleMemoryLogic::setMaximumMemoryUsage(uint64_t max)
-{
- vespalib::LockGuard lock(_stateLock);
- _state.setMaximumMemoryUsage(max);
-}
-
-void
-SimpleMemoryLogic::getState(MemoryState& state, bool resetMax) {
- vespalib::LockGuard lock(_stateLock);
- state = _state;
- if (resetMax) _state.resetMax();
-}
-
-MemoryToken::UP
-SimpleMemoryLogic::allocate(const MemoryAllocationType& type,
- uint8_t priority,
- ReduceMemoryUsageInterface* reducer)
-{
- MemoryTokenImpl::UP token(
- new MemoryTokenImpl(*this, type, 0, priority, reducer));
- if (reducer != 0) {
- vespalib::LockGuard lock(_stateLock);
- _reducers.push_back(Reducer(*token, *reducer));
- }
- return std::move(token);
-}
-
-bool
-SimpleMemoryLogic::resize(MemoryToken& tok, uint64_t min, uint64_t max,
- uint32_t allocationCounts)
-{
- vespalib::LockGuard lock(_stateLock);
- MemoryTokenImpl& token(static_cast<MemoryTokenImpl&>(tok));
- LOG(spam, "Attempting to resize %s to size in the range %" PRIu64 " to "
- "%" PRIu64 ".", token.toString().c_str(), min, max);
- if (token.getSize() > max) { // Always safe to reduce size
- handleReduction(token, max, allocationCounts);
- return true;
- }
- // If not reducing size, calculate relative min/max values.
- uint64_t relMin = (min > token.getSize() ? min - token.getSize() : 0);
- uint64_t relMax = max - token.getSize();
- return resizeRelative(token, relMin, relMax, allocationCounts);
-}
-
-void
-SimpleMemoryLogic::handleReduction(MemoryTokenImpl& token, uint64_t max,
- uint32_t allocationCounts)
-{
- LOG(spam, "Reduzing size of token by %" PRIu64 ".",
- token.getSize() - max);
- _state.removeFromEntry(token.getType(), token.getSize() - max,
- token.getPriority(), allocationCounts);
- setTokenSize(token, max);
-}
-
-bool
-SimpleMemoryLogic::handleCacheMemoryRequest(
- MemoryTokenImpl& token, uint64_t min, uint64_t max,
- uint32_t allocationCounts)
-{
- uint64_t usedSize(_state.getCurrentSnapshot().getUsedSize());
- uint64_t thresholdSize = uint64_t(getCacheThreshold()
- * _state.getTotalSize());
- uint64_t toAllocate(thresholdSize > usedSize
- ? std::min(thresholdSize - usedSize, max)
- : 0);
- bool forced = false;
- if (token.getType().isAllocationsForced() && toAllocate < min) {
- toAllocate = min;
- forced = true;
- }
- if (toAllocate < min) {
- LOG(spam, "We cannot give more memory to cache without going above "
- "cache threshold (%" PRIu64 " B)", thresholdSize);
- _state.addToEntry(token.getType(), 0, token.getPriority(),
- MemoryState::DENIED, false, allocationCounts);
- return false;
- }
- LOG(spam, "Giving %" PRIu64 " bytes of memory to cache. (Cache threshold "
- "is %" PRIu64 ", used size is %" PRIu64 ", %" PRIu64 " bytes were "
- "always allocated to the token and it wanted memory between %"
- PRIu64 " and %" PRIu64 ".",
- toAllocate, thresholdSize, usedSize, token.getSize(), min, max);
- _state.addToEntry(token.getType(), toAllocate, token.getPriority(),
- static_cast<uint64_t>(toAllocate) >= max
- ? MemoryState::GOT_MAX : MemoryState::GOT_MIN,
- forced, allocationCounts);
- setTokenSize(token, token.getSize() + toAllocate);
- return true;
-}
-
-uint64_t
-SimpleMemoryLogic::getMemorySizeFreeForPriority(uint8_t priority) const
-{
- uint64_t usedSize(_state.getCurrentSnapshot().getUsedSizeIgnoringCache());
- uint64_t thresholdSize = uint64_t(getNonCacheThreshold(priority)
- * _state.getTotalSize());
- return (usedSize >= thresholdSize ? 0 : thresholdSize - usedSize);
-}
-
-bool
-SimpleMemoryLogic::resizeRelative(
- MemoryTokenImpl& token, uint64_t min, uint64_t max,
- uint32_t allocationCounts)
-{
- LOG(spam, "Relative resize change. Need another %zu-%zu byte of memory.",
- min, max);
- // If requester is cache, use cache threshold
- if (token.getType().isCache()) {
- return handleCacheMemoryRequest(token, min, max, allocationCounts);
- }
- // If we get here, requester is not cache.
- uint64_t usedSize(_state.getCurrentSnapshot().getUsedSizeIgnoringCache());
- uint64_t thresholdSize = uint64_t(getNonCacheThreshold(token.getPriority())
- * _state.getTotalSize());
- uint64_t toAllocate = 0;
- if (thresholdSize > usedSize) {
- toAllocate = std::min(max, thresholdSize - usedSize);
- }
- if (toAllocate < min) toAllocate = min;
- bool forced = false;
- if (usedSize + toAllocate > _state.getTotalSize()) {
- if (token.getType().isAllocationsForced()) {
- forced = true;
- } else {
- LOG(spam, "We cannot give more memory without going beyond max");
- _state.addToEntry(token.getType(), 0, token.getPriority(),
- MemoryState::DENIED, false, allocationCounts);
- return false;
- }
- }
- // External load should not fill up too much
- if (usedSize + toAllocate > thresholdSize
- && token.getType().isExternalLoad()
- && !token.getType().isAllocationsForced())
- {
- LOG(spam, "Not giving external load memory beyond threshold.");
- _state.addToEntry(token.getType(), 0, token.getPriority(),
- MemoryState::DENIED, false, allocationCounts);
- return false;
- }
- // If this puts us above max with cache, remove some cache.
- if (_state.getCurrentSnapshot().getUsedSize() + toAllocate
- > _state.getTotalSize())
- {
- uint64_t needed(_state.getCurrentSnapshot().getUsedSize()
- + toAllocate - _state.getTotalSize());
- for (uint32_t i=0; i<_reducers.size(); ++i) {
- MemoryTokenImpl& rtoken(*_reducers[i]._token);
- uint64_t reduceBy(std::min(needed, rtoken.getSize()));
- uint64_t reduced(_reducers[i]._reducer->reduceMemoryConsumption(
- rtoken, reduceBy));
- _state.removeFromEntry(rtoken.getType(), reduced,
- rtoken.getPriority(), 0);
- setTokenSize(rtoken, rtoken.getSize() - reduced);
- needed -= reduceBy;
- if (needed == 0) break;
- if (reduced < reduceBy) {
- LOG(debug, "Reducer refused to free the full %" PRIu64 " bytes "
- "requested. %" PRIu64 " bytes reduced in token %s.",
- reduceBy, reduced, rtoken.toString().c_str());
- }
- }
- }
- if (_state.getCurrentSnapshot().getUsedSize() + toAllocate
- > _state.getTotalSize())
- {
- LOGBP(debug, "Failed to free enough memory from cache. This puts us "
- "above max memory.");
- }
- LOG(spam, "Giving %" PRIu64 " bytes of memory", toAllocate);
- _state.addToEntry(token.getType(), toAllocate, token.getPriority(),
- static_cast<uint64_t>(toAllocate) >= max
- ? MemoryState::GOT_MAX : MemoryState::GOT_MIN,
- forced, allocationCounts);
- setTokenSize(token, token.getSize() + toAllocate);
- return true;
-}
-
-void
-SimpleMemoryLogic::freeToken(MemoryTokenImpl& token)
-{
- vespalib::LockGuard lock(_stateLock);
- _state.removeFromEntry(token.getType(), token.getSize(),
- token.getPriority(), token.getAllocationCount());
- if (token.getReducer() != 0) {
- std::vector<Reducer> reducers;
- reducers.reserve(_reducers.size() - 1);
- for (uint32_t i=0; i<_reducers.size(); ++i) {
- if (_reducers[i]._token != &token) reducers.push_back(_reducers[i]);
- }
- assert(reducers.size() + 1 == _reducers.size());
- reducers.swap(_reducers);
- }
-}
-
-void
-SimpleMemoryLogic::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "SimpleMemoryLogic() {\n"
- << indent << " ";
- vespalib::LockGuard lock(_stateLock);
- _state.print(out, verbose, indent + " ");
-}
-
-}
diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h
deleted file mode 100644
index c394db40b29..00000000000
--- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-/**
- * \class storage::SimpleMemoryLogic
- *
- * \brief Simple logic deciding who should get memory and how much.
- *
- * There is a cache threshold. By default 98%. Cache will always get memory up
- * till this fillrate.
- *
- * There is a non-cache threshold. Non-cache memory requesters will get maximum
- * memory until threshold is reached. If getting maximum memory would go beyond
- * the non-cache threshold, the requester will get enough memory to hit the
- * threshold (if more than minimum), or get the minimum memory asked for, if
- * that doesn't put usage above 100%.
- *
- * Usage above 100% is attempted avoided by freeing cache memory. If failing to
- * free enough memory, request will fail, or minimum will be get if allocation
- * is forced such that it cannot fail. In such a case, usage may go beyond 100%.
- */
-
-#pragma once
-
-#include "memorymanager.h"
-#include "memorystate.h"
-#include <vespa/vespalib/util/sync.h>
-
-namespace storage {
-namespace framework {
-namespace defaultimplementation {
-
-class SimpleMemoryLogic : public AllocationLogic
-{
- float _cacheThreshold;
- float _nonCacheThreshold;
- vespalib::Lock _stateLock;
- MemoryState _state;
- struct Reducer {
- MemoryTokenImpl* _token;
- ReduceMemoryUsageInterface* _reducer;
-
- Reducer() : _token(0), _reducer(0) {}
- Reducer(MemoryTokenImpl& t,
- ReduceMemoryUsageInterface& r)
- : _token(&t), _reducer(&r) {}
- };
- std::vector<Reducer> _reducers;
-
-protected:
- float getCacheThreshold() { return _cacheThreshold; }
-
- // Priority memory logic can override this to set a threshold based on
- // priority
- virtual float getNonCacheThreshold(uint8_t priority) const
- { (void) priority; return _nonCacheThreshold; }
-
-public:
- typedef std::unique_ptr<SimpleMemoryLogic> UP;
-
- SimpleMemoryLogic(Clock&, uint64_t maxMemory);
-
- ~SimpleMemoryLogic();
-
- SimpleMemoryLogic& setMinJumpToUpdateMax(uint32_t bytes) {
- _state.setMinJumpToUpdateMax(bytes);
- return *this;
- }
-
- void setMaximumMemoryUsage(uint64_t max) override;
-
- void setCacheThreshold(float limit) { _cacheThreshold = limit; }
- void setNonCacheThreshold(float limit) { _nonCacheThreshold = limit; }
-
- MemoryState& getState() { return _state; } // Not threadsafe. Unit testing.
- void getState(MemoryState& state, bool resetMax) override;
-
- MemoryToken::UP allocate(const MemoryAllocationType&, uint8_t priority,
- ReduceMemoryUsageInterface* = 0) override;
- bool resize(MemoryToken& token, uint64_t min, uint64_t max, uint32_t allocationCounts) override;
-
- void freeToken(MemoryTokenImpl& token) override;
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
-
- virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override;
-
-private:
- void handleReduction(MemoryTokenImpl&, uint64_t size,
- uint32_t allocationCounts);
- bool resizeRelative(MemoryTokenImpl&, uint64_t min, uint64_t max,
- uint32_t allocationCounts);
- bool handleCacheMemoryRequest(MemoryTokenImpl&, uint64_t min, uint64_t max,
- uint32_t allocationCounts);
-};
-
-} // defaultimplementation
-} // framework
-} // storage
-
diff --git a/storageframework/src/vespa/storageframework/generic/CMakeLists.txt b/storageframework/src/vespa/storageframework/generic/CMakeLists.txt
index cf225f8d5aa..5df9963304d 100644
--- a/storageframework/src/vespa/storageframework/generic/CMakeLists.txt
+++ b/storageframework/src/vespa/storageframework/generic/CMakeLists.txt
@@ -4,7 +4,6 @@ vespa_add_library(storageframework_generic
$<TARGET_OBJECTS:storageframework_component>
$<TARGET_OBJECTS:storageframework_status>
$<TARGET_OBJECTS:storageframework_thread>
- $<TARGET_OBJECTS:storageframework_memory>
$<TARGET_OBJECTS:storageframework_clock>
INSTALL lib64
DEPENDS
diff --git a/storageframework/src/vespa/storageframework/generic/memory/.gitignore b/storageframework/src/vespa/storageframework/generic/memory/.gitignore
deleted file mode 100644
index 7e7c0fe7fae..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/.depend
-/Makefile
diff --git a/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt b/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt
deleted file mode 100644
index 77aa14e4ca2..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_library(storageframework_memory OBJECT
- SOURCES
- memorytoken.cpp
- DEPENDS
-)
diff --git a/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h b/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h
deleted file mode 100644
index e91b7946454..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::framework::MemoryAllocationType
- * \ingroup memory
- *
- * \brief Allocation types used to differ between memory manager clients.
- *
- * The different memory manager clients have different properties. It is
- * important for the memory manager to distinguish between different users in
- * order to know how to prioritize memory, and also in order to create good
- * reports on memory usage.
- *
- * An allocation type holds metadata for a memory manager client, including a
- * name for the type and various properties that may affect how much memory
- * such a client will get, whether it always gets some, etc.
- */
-
-#pragma once
-
-#include <string>
-#include <memory>
-
-namespace storage::framework {
-
-struct MemoryAllocationType {
- using UP = std::unique_ptr<MemoryAllocationType>;
-
- enum Flags {
- NONE = 0x00,
- FORCE_ALLOCATE = 0x01,
- EXTERNAL_LOAD = 0x02,
- CACHE = 0x04
- };
-
- MemoryAllocationType()
- : _flags(NONE), _name("") {};
-
- MemoryAllocationType(const std::string& name, uint32_t flags = NONE)
- : _flags(flags), _name(name) {}
-
- const std::string& getName() const { return _name; }
- bool isAllocationsForced() const { return (_flags & FORCE_ALLOCATE); }
- bool isExternalLoad() const { return (_flags & EXTERNAL_LOAD); }
- bool isCache() const { return (_flags & CACHE); }
-
-private:
- uint32_t _flags;
- std::string _name;
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h b/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h
deleted file mode 100644
index a97038c2aab..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::framework::MemoryManagerInterface
- * \ingroup memory
- *
- * \brief Interface with functions clients need in order to use a memory manager
- *
- * This interface exist so clients can use a memory manager without actually
- * depending on the implementation of it.
- */
-
-#pragma once
-
-#include "memoryallocationtype.h"
-#include "memorytoken.h"
-#include "reducememoryusageinterface.h"
-#include <vector>
-
-namespace storage::framework {
-
-struct MemoryManagerInterface
-{
- typedef std::unique_ptr<MemoryManagerInterface> UP;
-
- virtual ~MemoryManagerInterface() {}
-
- virtual void setMaximumMemoryUsage(uint64_t max) = 0;
-
- /**
- * Registers the given allocation type by copying it, and returning
- * a reference to the copied object.
- */
- virtual const MemoryAllocationType&
- registerAllocationType(const MemoryAllocationType& type) = 0;
-
- /** Throws exception if failing to find type. */
- virtual const MemoryAllocationType&
- getAllocationType(const std::string& name) const = 0;
-
- /** Get an overview of all registration types. */
- virtual std::vector<const MemoryAllocationType*>
- getAllocationTypes() const = 0;
-
- /**
- * Decide how much to allocate for this request. Should be between min
- * and max, unless it's of a type that can be denied (such as external
- * requests), in which case we can also deny allocation by returning a null
- * token.
- */
- virtual MemoryToken::UP allocate(
- const MemoryAllocationType&,
- uint64_t min,
- uint64_t max,
- uint8_t priority,
- ReduceMemoryUsageInterface* = 0) = 0;
-
- /**
- * Utility function to see how much memory is available.
- */
- virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const = 0;
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp b/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp
deleted file mode 100644
index 3950810a010..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "memorytoken.h"
-
-namespace storage {
-namespace framework {
-
-MemoryToken::~MemoryToken()
-{
-}
-
-} // framework
-} // storage
diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h b/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h
deleted file mode 100644
index cd389d64549..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::framework::MemoryToken
- * \ingroup memory
- *
- * \brief Token to keep by client for current allocations.
- *
- * This class is a token a memory manager client will get from the memory
- * manager when getting memory. It can be used to know how much you currently
- * have allocated, and through it you can request more or less memory.
- */
-
-#pragma once
-
-#include <memory>
-
-namespace storage::framework {
-
-class MemoryToken {
-protected:
-public:
- typedef std::unique_ptr<MemoryToken> UP;
- virtual ~MemoryToken();
-
- virtual uint64_t getSize() const = 0;
- virtual bool resize(uint64_t min, uint64_t max) = 0;
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h b/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h
deleted file mode 100644
index cd50d33015c..00000000000
--- a/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * \class storage::framework::ReduceMemoryUsageInterface
- * \ingroup memory
- *
- * \brief The manager can take memory back when needed using this interface.
- *
- * Some memory users, typically caches, wants to use all available memory. But
- * to let them use all available memory, it must also be easy to take memory
- * back when needed for something else. An implementation of this interface can
- * be given on memory allocations to give the memory manager the ability to take
- * memory back when needed.
- */
-
-#pragma once
-
-namespace storage::framework {
-
-struct ReduceMemoryUsageInterface
-{
- virtual ~ReduceMemoryUsageInterface() {}
-
- /**
- * This callback is called when the memory manager want to reduce the usage
- * of the given memory token. Actual memory to be released should be
- * released in this function. The token itself will be adjusted by the
- * memory manager though. The memory manager may keep a lock through this
- * call, so no memory manager calls should be made inside this callback.
- *
- * It is recommended that you actually release at least as many bytes as
- * requested. Though currently it is allowed to reduce less or refuse, but
- * this might mean that some higher priority task does not get the memory it
- * needs.
- *
- * @param reduceBy Always in the range 0 < reduceBy <= token.size()
- * @return The amount of memory no longer used.
- */
- virtual uint64_t reduceMemoryConsumption(const MemoryToken&, uint64_t reduceBy) = 0;
-};
-
-}
diff --git a/storageframework/src/vespa/storageframework/generic/thread/thread.cpp b/storageframework/src/vespa/storageframework/generic/thread/thread.cpp
index 1a88b4d2044..5ed3f7dc5e6 100644
--- a/storageframework/src/vespa/storageframework/generic/thread/thread.cpp
+++ b/storageframework/src/vespa/storageframework/generic/thread/thread.cpp
@@ -17,5 +17,16 @@ Thread::interruptAndJoin(vespalib::Monitor* m)
join();
}
+void
+Thread::interruptAndJoin(std::mutex &m, std::condition_variable &cv)
+{
+ interrupt();
+ {
+ std::lock_guard<std::mutex> guard(m);
+ cv.notify_all();
+ }
+ join();
+}
+
} // framework
} // storage
diff --git a/storageframework/src/vespa/storageframework/generic/thread/thread.h b/storageframework/src/vespa/storageframework/generic/thread/thread.h
index ceeba79ebe2..72054ff725a 100644
--- a/storageframework/src/vespa/storageframework/generic/thread/thread.h
+++ b/storageframework/src/vespa/storageframework/generic/thread/thread.h
@@ -14,6 +14,8 @@
#include "runnable.h"
#include <vespa/vespalib/stllike/string.h>
+#include <mutex>
+#include <condition_variable>
namespace vespalib {
class Monitor;
@@ -58,6 +60,8 @@ public:
* through a monitor after the signalling face.
*/
void interruptAndJoin(vespalib::Monitor* m);
+
+ void interruptAndJoin(std::mutex &m, std::condition_variable &cv);
};
}
diff --git a/storageframework/src/vespa/storageframework/storageframework.h b/storageframework/src/vespa/storageframework/storageframework.h
index f9847d5fd12..4ab36fa099f 100644
--- a/storageframework/src/vespa/storageframework/storageframework.h
+++ b/storageframework/src/vespa/storageframework/storageframework.h
@@ -7,7 +7,6 @@
#include <vespa/storageframework/generic/clock/clock.h>
#include <vespa/storageframework/generic/clock/timer.h>
#include <vespa/storageframework/generic/component/component.h>
-#include <vespa/storageframework/generic/memory/memorymanagerinterface.h>
#include <vespa/storageframework/generic/metric/metricupdatehook.h>
#include <vespa/storageframework/generic/status/htmlstatusreporter.h>
#include <vespa/storageframework/generic/status/statusreportermap.h>
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp b/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp
index 64c2c9a6429..7ebb71f1855 100644
--- a/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp
@@ -15,9 +15,7 @@ __thread SearchEnvironment::EnvMap * SearchEnvironment::_localEnvMap=0;
SearchEnvironment::Env::Env(const vespalib::string & muffens, const config::ConfigUri & configUri, Fast_NormalizeWordFolder & wf) :
_configId(configUri.getConfigId()),
- _configurer(config::SimpleConfigRetriever::UP(
- new config::SimpleConfigRetriever(createKeySet(configUri.getConfigId()), configUri.getContext())),
- this),
+ _configurer(std::make_unique<config::SimpleConfigRetriever>(createKeySet(configUri.getConfigId()), configUri.getContext()), this),
_vsmAdapter(new VSMAdapter(muffens, _configId, wf)),
_rankManager(new RankManager(_vsmAdapter.get()))
{
@@ -63,13 +61,15 @@ SearchEnvironment::~SearchEnvironment()
_threadLocals.clear();
}
-SearchEnvironment::Env & SearchEnvironment::getEnv(const vespalib::string & searchCluster)
+SearchEnvironment::Env &
+SearchEnvironment::getEnv(const vespalib::string & searchCluster)
{
config::ConfigUri searchClusterUri(_configUri.createWithNewId(searchCluster));
if (_localEnvMap == NULL) {
- _localEnvMap = new EnvMap;
+ EnvMapUP envMap = std::make_unique<EnvMap>();
+ _localEnvMap = envMap.get();
vespalib::LockGuard guard(_lock);
- _threadLocals.push_back(EnvMapUP(_localEnvMap));
+ _threadLocals.emplace_back(std::move(envMap));
}
EnvMap::iterator localFound = _localEnvMap->find(searchCluster);
if (localFound == _localEnvMap->end()) {
@@ -77,7 +77,8 @@ SearchEnvironment::Env & SearchEnvironment::getEnv(const vespalib::string & sear
EnvMap::iterator found = _envMap.find(searchCluster);
if (found == _envMap.end()) {
LOG(debug, "Init VSMAdapter with config id = '%s'", searchCluster.c_str());
- _envMap[searchCluster].reset(new Env("*", searchClusterUri, _wordFolder));
+ Env::SP env = std::make_shared<Env>("*", searchClusterUri, _wordFolder);
+ _envMap[searchCluster] = std::move(env);
found = _envMap.find(searchCluster);
}
_localEnvMap->insert(*found);
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
index 51c01a40fac..e3483e8beac 100644
--- a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
@@ -31,6 +31,8 @@ using search::attribute::IAttributeVector;
using search::aggregation::HitsAggregationResult;
using search::expression::ConfigureStaticParams;
using vdslib::Parameters;
+using document::PositionDataType;
+using document::DataType;
class ForceWordfolderInit
{
@@ -52,25 +54,25 @@ static ForceWordfolderInit _G_forceNormWordFolderInit;
AttributeVector::SP
createMultiValueAttribute(const vespalib::string & name, const document::FieldValue & fv, bool arrayType)
{
- const document::DataType * ndt = fv.getDataType();
+ const DataType * ndt = fv.getDataType();
if (ndt->inherits(document::CollectionDataType::classId)) {
ndt = &(static_cast<const document::CollectionDataType *>(ndt))->getNestedType();
}
LOG(debug, "Create %s attribute '%s' with data type '%s' (%s)",
arrayType ? "array" : "weighted set", name.c_str(), ndt->getName().c_str(), fv.getClass().name());
AttributeVector::SP attr;
- if (ndt->getId() == document::DataType::T_BYTE ||
- ndt->getId() == document::DataType::T_INT ||
- ndt->getId() == document::DataType::T_LONG)
+ if (ndt->getId() == DataType::T_BYTE ||
+ ndt->getId() == DataType::T_INT ||
+ ndt->getId() == DataType::T_LONG)
{
attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiIntegerExtAttribute(name))
: static_cast<AttributeVector *>(new search::WeightedSetIntegerExtAttribute(name)));
- } else if (ndt->getId() == document::DataType::T_DOUBLE ||
- ndt->getId() == document::DataType::T_FLOAT)
+ } else if (ndt->getId() == DataType::T_DOUBLE ||
+ ndt->getId() == DataType::T_FLOAT)
{
attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiFloatExtAttribute(name))
: static_cast<AttributeVector *>(new search::WeightedSetFloatExtAttribute(name)));
- } else if (ndt->getId() == document::DataType::T_STRING) {
+ } else if (ndt->getId() == DataType::T_STRING) {
attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiStringExtAttribute(name))
: static_cast<AttributeVector *>(new search::WeightedSetStringExtAttribute(name)));
} else {
@@ -204,27 +206,26 @@ void SearchVisitor::init(const Parameters & params)
_attrMan.add(_rankAttributeBacking);
Parameters::ValueRef valueRef;
if ( params.get("summaryclass", valueRef) ) {
- _summaryClass = vespalib::string(static_cast<const char *>(valueRef.data()),
- static_cast<unsigned>(valueRef.size()));
+ _summaryClass = vespalib::string(valueRef.data(), valueRef.size());
LOG(debug, "Received summary class: %s", _summaryClass.c_str());
}
size_t wantedSummaryCount(10);
if (params.get("summarycount", valueRef) ) {
- vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size());
+ vespalib::string tmp(valueRef.data(), valueRef.size());
wantedSummaryCount = strtoul(tmp.c_str(), NULL, 0);
LOG(debug, "Received summary count: %ld", wantedSummaryCount);
}
_queryResult->getSearchResult().setWantedHitCount(wantedSummaryCount);
if (params.get("rankprofile", valueRef) ) {
- vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size());
+ vespalib::string tmp(valueRef.data(), valueRef.size());
_rankController.setRankProfile(tmp);
LOG(debug, "Received rank profile: %s", _rankController.getRankProfile().c_str());
}
if (params.get("queryflags", valueRef) ) {
- vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size());
+ vespalib::string tmp(valueRef.data(), valueRef.size());
LOG(debug, "Received query flags: 0x%lx", strtoul(tmp.c_str(), NULL, 0));
uint32_t queryFlags = strtoul(tmp.c_str(), NULL, 0);
_rankController.setDumpFeatures((queryFlags & search::fs4transport::QFLAG_DUMP_FEATURES) != 0);
@@ -234,7 +235,7 @@ void SearchVisitor::init(const Parameters & params)
if (params.get("rankproperties", valueRef) && valueRef.size() > 0) {
LOG(spam, "Received rank properties of %zd bytes", valueRef.size());
uint32_t len = static_cast<uint32_t>(valueRef.size());
- char * data = const_cast<char *>(static_cast<const char *>(valueRef.data()));
+ char * data = const_cast<char *>(valueRef.data());
FNET_DataBuffer src(data, len);
uint32_t cnt = src.ReadInt32();
len -= sizeof(uint32_t);
@@ -259,7 +260,7 @@ void SearchVisitor::init(const Parameters & params)
}
if (params.get("rankprofile", valueRef)) {
- vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size());
+ vespalib::string tmp(valueRef.data(), valueRef.size());
_summaryGenerator.getDocsumState()._args.SetRankProfile(tmp);
}
@@ -270,7 +271,7 @@ void SearchVisitor::init(const Parameters & params)
vespalib::string location;
if (params.get("location", valueRef)) {
- location = vespalib::string(static_cast<const char *>(valueRef.data()), valueRef.size());
+ location = vespalib::string(valueRef.data(), valueRef.size());
LOG(debug, "Location = '%s'", location.c_str());
_summaryGenerator.getDocsumState()._args.SetLocation(valueRef.size(), (const char*)valueRef.data());
}
@@ -278,14 +279,12 @@ void SearchVisitor::init(const Parameters & params)
Parameters::ValueRef searchClusterBlob;
if (params.get("searchcluster", searchClusterBlob)) {
LOG(spam, "Received searchcluster blob of %zd bytes", searchClusterBlob.size());
- vespalib::string searchCluster(static_cast<const char *>(searchClusterBlob.data()), searchClusterBlob.size());
+ vespalib::string searchCluster(searchClusterBlob.data(), searchClusterBlob.size());
_vsmAdapter = _env.getVSMAdapter(searchCluster);
if ( params.get("sort", valueRef) ) {
search::uca::UcaConverterFactory ucaFactory;
- _sortSpec = search::common::SortSpec(vespalib::string(static_cast<const char *>(valueRef.data()),
- static_cast<unsigned>(valueRef.size())),
- ucaFactory);
+ _sortSpec = search::common::SortSpec(vespalib::string(valueRef.data(), valueRef.size()), ucaFactory);
LOG(debug, "Received sort specification: '%s'", _sortSpec.getSpec().c_str());
}
@@ -388,16 +387,16 @@ SearchVisitor::AttributeInserter::onPrimitive(uint32_t, const Content & c)
}
}
-SearchVisitor::AttributeInserter::AttributeInserter(search::AttributeVector & attribute, search::AttributeVector::DocId docId) :
+SearchVisitor::AttributeInserter::AttributeInserter(AttributeVector & attribute, AttributeVector::DocId docId) :
_attribute(attribute),
_docId(docId)
{
}
-SearchVisitor::PositionInserter::PositionInserter(search::AttributeVector & attribute, search::AttributeVector::DocId docId) :
+SearchVisitor::PositionInserter::PositionInserter(AttributeVector & attribute, AttributeVector::DocId docId) :
AttributeInserter(attribute, docId),
- _fieldX(document::PositionDataType::getInstance().getField(document::PositionDataType::FIELD_X)),
- _fieldY(document::PositionDataType::getInstance().getField(document::PositionDataType::FIELD_Y))
+ _fieldX(PositionDataType::getInstance().getField(PositionDataType::FIELD_X)),
+ _fieldY(PositionDataType::getInstance().getField(PositionDataType::FIELD_Y))
{
}
@@ -605,7 +604,7 @@ void
SearchVisitor::SyntheticFieldsController::onDocumentMatch(StorageDocument & document,
const vespalib::string & documentId)
{
- document.setField(_documentIdFId, document::FieldValue::UP(new document::StringFieldValue(documentId)));
+ document.setField(_documentIdFId, std::make_unique<document::StringFieldValue>(documentId));
}
void
@@ -617,8 +616,8 @@ SearchVisitor::registerAdditionalFields(const std::vector<vsm::DocsumTools::Fiel
const std::vector<vespalib::string> & inputNames = spec.getInputNames();
for (size_t j = 0; j < inputNames.size(); ++j) {
fieldList.push_back(inputNames[j]);
- if (document::PositionDataType::isZCurveFieldName(inputNames[j])) {
- fieldList.push_back(document::PositionDataType::cutZCurveFieldName(inputNames[j]));
+ if (PositionDataType::isZCurveFieldName(inputNames[j])) {
+ fieldList.push_back(PositionDataType::cutZCurveFieldName(inputNames[j]));
}
}
}
@@ -991,16 +990,16 @@ SearchVisitor::fillAttributeVectors(const vespalib::string & documentId, const S
{
for (const AttrInfo & finfo : _attributeFields) {
const AttributeGuard &finfoGuard(*finfo._attr);
- bool isPosition = finfoGuard->getClass().inherits(search::IntegerAttribute::classId) && document::PositionDataType::isZCurveFieldName(finfoGuard->getName());
+ bool isPosition = finfoGuard->getClass().inherits(search::IntegerAttribute::classId) && PositionDataType::isZCurveFieldName(finfoGuard->getName());
LOG(debug, "Filling attribute '%s', isPosition='%s'", finfoGuard->getName().c_str(), isPosition ? "true" : "false");
uint32_t fieldId = finfo._field;
if (isPosition) {
- vespalib::stringref org = document::PositionDataType::cutZCurveFieldName(finfoGuard->getName());
+ vespalib::stringref org = PositionDataType::cutZCurveFieldName(finfoGuard->getName());
fieldId = _fieldsUnion.find(org)->second;
}
const StorageDocument::SubDocument & subDoc = document.getComplexField(fieldId);
- search::AttributeVector & attrV = const_cast<search::AttributeVector & >(*finfoGuard);
- search::AttributeVector::DocId docId(0);
+ AttributeVector & attrV = const_cast<AttributeVector & >(*finfoGuard);
+ AttributeVector::DocId docId(0);
attrV.addDoc(docId);
if (subDoc.getFieldValue() != NULL) {
LOG(debug, "value = '%s'", subDoc.getFieldValue()->toString().c_str());
diff --git a/testutil/pom.xml b/testutil/pom.xml
index 9f98f40c2d0..00f606860a4 100644
--- a/testutil/pom.xml
+++ b/testutil/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>testutil</artifactId>
<packaging>jar</packaging>
@@ -23,6 +24,7 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>provided</scope>
+ <classifier>no_aop</classifier>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
diff --git a/travis/travis-build-full.sh b/travis/travis-build-full.sh
index fc0efb843aa..53e174b534b 100755
--- a/travis/travis-build-full.sh
+++ b/travis/travis-build-full.sh
@@ -6,7 +6,7 @@ export SOURCE_DIR=/source
export NUM_THREADS=6
export MALLOC_ARENA_MAX=1
export MAVEN_OPTS="-Xms128m -Xmx2g"
-source /etc/profile.d/devtoolset-6.sh || true
+source /etc/profile.d/devtoolset-7.sh || true
ccache --max-size=1250M
ccache --set-config=compression=true
diff --git a/vbench/src/vbench/core/dispatcher.h b/vbench/src/vbench/core/dispatcher.h
index 6336df17965..212cf04a06e 100644
--- a/vbench/src/vbench/core/dispatcher.h
+++ b/vbench/src/vbench/core/dispatcher.h
@@ -6,6 +6,7 @@
#include "provider.h"
#include "closeable.h"
#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <vector>
namespace vbench {
diff --git a/vdslib/pom.xml b/vdslib/pom.xml
index 48681b8ce83..5063489ed66 100644
--- a/vdslib/pom.xml
+++ b/vdslib/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vdslib</artifactId>
<packaging>container-plugin</packaging>
diff --git a/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt b/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt
index 86406937dd2..44693b42fe8 100644
--- a/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt
+++ b/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt
@@ -2,6 +2,7 @@
vespa_add_library(vdslib_distribution OBJECT
SOURCES
distribution.cpp
+ distribution_config_util.cpp
group.cpp
idealnodecalculatorimpl.cpp
redundancygroupdistribution.cpp
diff --git a/vdslib/src/vespa/vdslib/distribution/distribution.cpp b/vdslib/src/vespa/vdslib/distribution/distribution.cpp
index 908de49e311..ff84687210d 100644
--- a/vdslib/src/vespa/vdslib/distribution/distribution.cpp
+++ b/vdslib/src/vespa/vdslib/distribution/distribution.cpp
@@ -1,18 +1,19 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "distribution.h"
+#include <vespa/vdslib/distribution/distribution_config_util.h>
#include <vespa/vdslib/state/clusterstate.h>
#include <vespa/vdslib/state/random.h>
-#include <vespa/vespalib/text/stringtokenizer.h>
#include <vespa/vespalib/util/bobhash.h>
#include <vespa/vespalib/stllike/asciistream.h>
-#include <boost/lexical_cast.hpp>
#include <vespa/config/config.h>
#include <vespa/config/print/asciiconfigwriter.h>
#include <vespa/config/print/asciiconfigreader.h>
#include <vespa/vespalib/util/exceptions.h>
#include <vespa/config-stor-distribution.h>
#include <list>
+#include <algorithm>
+#include <cmath>
#include <vespa/log/bufferedlogger.h>
LOG_SETUP(".vdslib.distribution");
@@ -115,17 +116,6 @@ Distribution::operator=(const Distribution& d)
Distribution::~Distribution() { }
namespace {
- std::vector<uint16_t> getGroupPath(const vespalib::stringref & path) {
- vespalib::StringTokenizer st(path, ".", "");
- std::vector<uint16_t> result(st.size());
- for (uint32_t i=0, n=result.size(); i<n; ++i) {
- result[i] = boost::lexical_cast<uint16_t>(st[i]);
- }
- return result;
- }
-}
-
-namespace {
using ConfigDiskDistribution = vespa::config::content::StorDistributionConfig::DiskDistribution;
Distribution::DiskDistribution fromConfig(ConfigDiskDistribution cfg) {
switch (cfg) {
@@ -155,8 +145,8 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co
for (uint32_t i=0, n=config.group.size(); i<n; ++i) {
const ConfigGroup& cg(config.group[i]);
std::vector<uint16_t> path;
- if (nodeGraph.get() != 0) {
- path = getGroupPath(cg.index);
+ if (nodeGraph.get() != nullptr) {
+ path = DistributionConfigUtil::getGroupPath(cg.index);
}
bool isLeafGroup = (cg.nodes.size() > 0);
std::unique_ptr<Group> group;
@@ -179,7 +169,7 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co
if (path.empty()) {
nodeGraph = std::move(group);
} else {
- assert(nodeGraph.get() != 0);
+ assert(nodeGraph.get() != nullptr);
Group* parent = nodeGraph.get();
for (uint32_t j=0; j<path.size() - 1; ++j) {
parent = parent->getSubGroups()[path[j]];
@@ -187,7 +177,7 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co
parent->addSubGroup(std::move(group));
}
}
- if (nodeGraph.get() == 0) {
+ if (nodeGraph.get() == nullptr) {
throw vespalib::IllegalStateException(
"Got config that didn't seem to specify even a root group. Must "
"have a root group at minimum:\n"
@@ -462,14 +452,14 @@ Distribution::getIdealGroups(const document::BucketId& bucket,
tmpResults.pop_back();
}
}
- while (tmpResults.back()._group == 0) {
+ while (tmpResults.back()._group == nullptr) {
tmpResults.pop_back();
}
for (uint32_t i=0, n=tmpResults.size(); i<n; ++i) {
ScoredGroup& group(tmpResults[i]);
// This should never happen. Config should verify that each group
// has enough groups beneath them.
- assert(group._group != 0);
+ assert(group._group != nullptr);
getIdealGroups(bucket, clusterState, *group._group,
redundancyArray[i], results);
}
@@ -478,14 +468,11 @@ Distribution::getIdealGroups(const document::BucketId& bucket,
const Group*
Distribution::getIdealDistributorGroup(const document::BucketId& bucket,
const ClusterState& clusterState,
- const Group& parent,
- uint16_t redundancy) const
+ const Group& parent) const
{
if (parent.isLeafGroup()) {
return &parent;
}
- const Group::Distribution& redundancyArray(
- parent.getDistribution(redundancy));
ScoredGroup result(0, 0);
uint32_t seed(getGroupSeed(bucket, clusterState, parent));
RandomGen random(seed);
@@ -497,8 +484,8 @@ Distribution::getIdealDistributorGroup(const document::BucketId& bucket,
while (it->first < currentIndex++) random.nextDouble();
double score = random.nextDouble();
if (it->second->getCapacity() != 1) {
- // Capacity shouldn't possibly be 0.
- // Verified in Group::setCapacity()
+ // Capacity shouldn't possibly be 0.
+ // Verified in Group::setCapacity()
score = std::pow(score, 1.0 / it->second->getCapacity().getValue());
}
if (score > result._score) {
@@ -509,11 +496,10 @@ Distribution::getIdealDistributorGroup(const document::BucketId& bucket,
}
}
}
- if (result._group == 0) {
- return 0;
+ if (result._group == nullptr) {
+ return nullptr;
}
- return getIdealDistributorGroup(
- bucket, clusterState, *result._group, redundancyArray[0]);
+ return getIdealDistributorGroup(bucket, clusterState, *result._group);
}
bool
@@ -567,9 +553,8 @@ Distribution::getIdealNodes(const NodeType& nodeType,
_groupDistribution);
} else {
seed = getDistributorSeed(bucket, clusterState);
- const Group* group(getIdealDistributorGroup(
- bucket, clusterState, *_nodeGraph, redundancy));
- if (group == 0) {
+ const Group* group(getIdealDistributorGroup(bucket, clusterState, *_nodeGraph));
+ if (group == nullptr) {
vespalib::asciistream ss;
ss << "There is no legal distributor target in state with version "
<< clusterState.getVersion();
@@ -679,7 +664,7 @@ Distribution::getIdealDistributorNode(
std::vector<uint16_t> nodes;
getIdealNodes(NodeType::DISTRIBUTOR, state, bucket, nodes, upStates);
assert(nodes.size() <= 1);
- if (nodes.size() == 0) {
+ if (nodes.empty()) {
vespalib::asciistream ss;
ss << "There is no legal distributor target in state with version "
<< state.getVersion();
@@ -695,7 +680,7 @@ Distribution::splitNodesIntoLeafGroups(IndexList nodeList) const
std::map<uint16_t, IndexList> nodes;
for (uint32_t i=0, n=nodeList.size(); i<n; ++i) {
const Group* group(_nodeGraph->getGroupForNode(nodeList[i]));
- if (group == 0) {
+ if (group == nullptr) {
LOGBP(warning, "Node %u is not assigned to a group. "
"Should not happen?", nodeList[i]);
} else {
diff --git a/vdslib/src/vespa/vdslib/distribution/distribution.h b/vdslib/src/vespa/vdslib/distribution/distribution.h
index c828dbff9b8..6da60e084bb 100644
--- a/vdslib/src/vespa/vdslib/distribution/distribution.h
+++ b/vdslib/src/vespa/vdslib/distribution/distribution.h
@@ -13,17 +13,10 @@
#include <vespa/vdslib/state/nodetype.h>
#include <vespa/vespalib/util/exception.h>
-namespace vespa {
- namespace config {
- namespace content {
- namespace internal {
- class InternalStorDistributionType;
- }
- }
- }
+namespace vespa::config::content::internal {
+ class InternalStorDistributionType;
}
-namespace storage {
-namespace lib {
+namespace storage::lib {
VESPA_DEFINE_EXCEPTION(NoDistributorsAvailableException, vespalib::Exception);
VESPA_DEFINE_EXCEPTION(TooFewBucketBitsInUseException, vespalib::Exception);
@@ -101,8 +94,7 @@ private:
const Group* getIdealDistributorGroup(const document::BucketId& bucket,
const ClusterState& clusterState,
- const Group& parent,
- uint16_t redundancy) const;
+ const Group& parent) const;
/**
* Since distribution object may be used often in ideal state calculations
@@ -205,6 +197,5 @@ public:
static bool allDistributorsDown(const Group&, const ClusterState&);
};
-} // lib
-} // storage
+} // storage::lib
diff --git a/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp
new file mode 100644
index 00000000000..e700dd4e379
--- /dev/null
+++ b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "distribution_config_util.h"
+#include <vespa/vespalib/text/stringtokenizer.h>
+#include <boost/lexical_cast.hpp>
+
+namespace storage::lib {
+
+std::vector<uint16_t> DistributionConfigUtil::getGroupPath(vespalib::stringref path) {
+ vespalib::StringTokenizer st(path, ".", "");
+ std::vector<uint16_t> result(st.size());
+ for (uint32_t i=0, n=result.size(); i<n; ++i) {
+ result[i] = boost::lexical_cast<uint16_t>(st[i]);
+ }
+ return result;
+}
+
+}
diff --git a/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h
new file mode 100644
index 00000000000..3466b16f275
--- /dev/null
+++ b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h
@@ -0,0 +1,15 @@
+// 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>
+#include <vector>
+
+namespace storage::lib {
+
+struct DistributionConfigUtil {
+ // Converts an input string of the form "1.2.3" to a returned vector {1, 2, 3}
+ static std::vector<uint16_t> getGroupPath(vespalib::stringref path);
+};
+
+}
diff --git a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp
index 97bde0ca54f..f620dc15928 100644
--- a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp
+++ b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp
@@ -14,10 +14,10 @@ namespace {
void verifyLegal(vespalib::StringTokenizer& st,
vespalib::stringref serialized)
{
- // First, verify sanity of the serialized string
- uint32_t firstAsterix = st.size();
+ // First, verify sanity of the serialized string
+ uint32_t firstAsterisk = st.size();
for (uint32_t i=0; i<st.size(); ++i) {
- if (i > firstAsterix) {
+ if (i > firstAsterisk) {
if (st[i] != "*") {
throw vespalib::IllegalArgumentException(
"Illegal distribution spec \"" + serialized + "\". "
@@ -26,8 +26,8 @@ namespace {
}
continue;
}
- if (i < firstAsterix && st[i] == "*") {
- firstAsterix = i;
+ if (i < firstAsterisk && st[i] == "*") {
+ firstAsterisk = i;
continue;
}
uint32_t number = atoi(st[i].c_str());
@@ -76,21 +76,21 @@ RedundancyGroupDistribution::RedundancyGroupDistribution(
const RedundancyGroupDistribution& spec,
uint16_t redundancy)
{
- uint16_t firstAsterix = spec.getFirstAsterixIndex();
- // If redundancy is less than the group size, we only get one copy
- // in redundancy groups.
+ uint16_t firstAsterisk = spec.getFirstAsteriskIndex();
+ // If redundancy is less than the group size, we only get one copy
+ // in redundancy groups.
if (redundancy <= spec.size()) {
_values = std::vector<uint16_t>(redundancy, 1);
return;
}
- // If not we will have one copy at least for every wanted group.
+ // If not we will have one copy at least for every wanted group.
_values = std::vector<uint16_t>(spec.size(), 1);
redundancy -= spec.size();
- // Distribute extra copies to non-asterix entries first
- redundancy = divideSpecifiedCopies(0, firstAsterix, redundancy, spec._values);
- // Distribute remaining copies to asterix entries
- divideSpecifiedCopies(firstAsterix, spec.size(), redundancy, spec._values);
- // Lastly sort, so the most copies will end up first in ideal state
+ // Distribute extra copies to non-asterisk entries first
+ redundancy = divideSpecifiedCopies(0, firstAsterisk, redundancy, spec._values);
+ // Distribute remaining copies to asterisk entries
+ divideSpecifiedCopies(firstAsterisk, spec.size(), redundancy, spec._values);
+ // Lastly sort, so the most copies will end up first in ideal state
std::sort(_values.begin(), _values.end());
std::reverse(_values.begin(), _values.end());
assert(_values.front() >= _values.back());
@@ -111,18 +111,18 @@ RedundancyGroupDistribution::print(std::ostream& out,
}
uint16_t
-RedundancyGroupDistribution::getFirstAsterixIndex() const
+RedundancyGroupDistribution::getFirstAsteriskIndex() const
{
if (_values.empty() || _values.back() != 0) {
throw vespalib::IllegalArgumentException(
"Invalid spec given. No asterisk entries found.",
VESPA_STRLOC);
}
- uint16_t firstAsterix = _values.size() - 1;
- while (firstAsterix > 0 && _values[firstAsterix - 1] == 0) {
- --firstAsterix;
+ uint16_t firstAsterisk = _values.size() - 1;
+ while (firstAsterisk > 0 && _values[firstAsterisk - 1] == 0) {
+ --firstAsterisk;
}
- return firstAsterix;
+ return firstAsterisk;
}
uint16_t
diff --git a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h
index 5dde7cf378d..33f895cadf0 100644
--- a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h
+++ b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h
@@ -41,7 +41,7 @@ public:
void print(std::ostream&, bool verbose, const std::string& indent) const override;
private:
- uint16_t getFirstAsterixIndex() const;
+ uint16_t getFirstAsteriskIndex() const;
uint16_t divideSpecifiedCopies(
uint16_t start, uint16_t end,
uint16_t redundancy, const std::vector<uint16_t>& maxValues);
diff --git a/vespa-application-maven-plugin/pom.xml b/vespa-application-maven-plugin/pom.xml
index 2464c8208ad..980c8eee60f 100644
--- a/vespa-application-maven-plugin/pom.xml
+++ b/vespa-application-maven-plugin/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa-application-maven-plugin</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespa-documentgen-plugin/pom.xml b/vespa-documentgen-plugin/pom.xml
index 4e96a0475f7..718b19ae994 100644
--- a/vespa-documentgen-plugin/pom.xml
+++ b/vespa-documentgen-plugin/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa-documentgen-plugin</artifactId>
<packaging>maven-plugin</packaging>
diff --git a/vespa-hadoop/pom.xml b/vespa-hadoop/pom.xml
index b9fbd589525..62d9010b9fd 100644
--- a/vespa-hadoop/pom.xml
+++ b/vespa-hadoop/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa-hadoop</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespa-http-client/pom.xml b/vespa-http-client/pom.xml
index 15d72b7c1c2..c336c641352 100644
--- a/vespa-http-client/pom.xml
+++ b/vespa-http-client/pom.xml
@@ -7,6 +7,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa-http-client</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespa_feed_perf/pom.xml b/vespa_feed_perf/pom.xml
index f54b2df9b8a..436dcc06f3d 100644
--- a/vespa_feed_perf/pom.xml
+++ b/vespa_feed_perf/pom.xml
@@ -13,6 +13,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa_feed_perf</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespa_jersey2/pom.xml b/vespa_jersey2/pom.xml
index 3250cd8a41f..781075fd041 100644
--- a/vespa_jersey2/pom.xml
+++ b/vespa_jersey2/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa_jersey2</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespabase/conf/default-env.txt.in b/vespabase/conf/default-env.txt.in
index 5b144e4c301..38a4d0cded5 100644
--- a/vespabase/conf/default-env.txt.in
+++ b/vespabase/conf/default-env.txt.in
@@ -1,3 +1,4 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
fallback VESPA_HOME @CMAKE_INSTALL_PREFIX@
override VESPA_USER vespa
+override cloudconfig_server__disable_filedistributor true
diff --git a/vespaclient-container-plugin/pom.xml b/vespaclient-container-plugin/pom.xml
index 59895438038..3472f3fcdc5 100644
--- a/vespaclient-container-plugin/pom.xml
+++ b/vespaclient-container-plugin/pom.xml
@@ -9,6 +9,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespaclient-container-plugin</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespaclient-core/pom.xml b/vespaclient-core/pom.xml
index bdb15825fbb..2e427364bd6 100644
--- a/vespaclient-core/pom.xml
+++ b/vespaclient-core/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespaclient-core</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespaclient-java/pom.xml b/vespaclient-java/pom.xml
index 8cde41d53d7..d692a6ed57f 100644
--- a/vespaclient-java/pom.xml
+++ b/vespaclient-java/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespaclient-java</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml
index 1f98a5e4c02..81c385c96ab 100644
--- a/vespajlib/pom.xml
+++ b/vespajlib/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespajlib</artifactId>
<packaging>container-plugin</packaging>
diff --git a/vespajlib/src/main/java/com/yahoo/io/IOUtils.java b/vespajlib/src/main/java/com/yahoo/io/IOUtils.java
index 2572842b213..febe02cb33e 100644
--- a/vespajlib/src/main/java/com/yahoo/io/IOUtils.java
+++ b/vespajlib/src/main/java/com/yahoo/io/IOUtils.java
@@ -3,6 +3,7 @@ package com.yahoo.io;
import java.io.*;
+import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.List;
@@ -155,33 +156,22 @@ public abstract class IOUtils {
/**
* Copies a file to another file.
* If the out file exists it will be overwritten.
- * NOTE: Not an optimal implementation currently.
*
* @throws IOException if copying fails
*/
public static void copy(String inFile, String outFile) throws IOException {
- BufferedReader reader=null;
- BufferedWriter writer=null;
-
- try {
- reader = createReader(inFile);
- writer = createWriter(outFile, false);
- int c;
- while (-1 != (c = reader.read()) )
- writer.write(c);
- } finally {
- closeReader(reader);
- closeWriter(writer);
- }
+ copy(new File(inFile), new File(outFile));
}
/**
* Copies a file to another file.
* If the out file exists it will be overwritten.
- * NOTE: Not an optimal implementation currently.
*/
public static void copy(File inFile, File outFile) throws IOException {
- copy(inFile.toString(),outFile.toString());
+ try (FileChannel sourceChannel = new FileInputStream(inFile).getChannel();
+ FileChannel destChannel = new FileOutputStream(outFile).getChannel()) {
+ destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
+ }
}
/**
@@ -275,8 +265,8 @@ public abstract class IOUtils {
}
/**
- * Returns the number of line in a file.
- * If the files does not exists, 0 is returned
+ * Returns the number of lines in a file.
+ * If the file does not exists, 0 is returned
*/
public static int countLines(String file) {
BufferedReader reader = null;
@@ -292,7 +282,6 @@ public abstract class IOUtils {
} finally {
closeReader(reader);
}
-
}
/**
diff --git a/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java b/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java
index 3a8b0dde1c1..8955bd9ea05 100644
--- a/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java
+++ b/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java
@@ -1,15 +1,23 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.io;
+import org.junit.Test;
+
import java.io.*;
import java.util.Arrays;
import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
/**
* @author bratseth
*/
-public class IOUtilsTestCase extends junit.framework.TestCase {
+public class IOUtilsTestCase {
+ @Test
public void testCloseNUllDoesNotFail() {
IOUtils.closeWriter(null);
IOUtils.closeReader(null);
@@ -17,12 +25,14 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
IOUtils.closeOutputStream(null);
}
+ @Test
public void testFileWriter() throws IOException {
IOUtils.writeFile("temp1.txt", "hello",false);
assertEquals("hello", IOUtils.readFile(new File("temp1.txt")));
new File("temp1.txt").delete();
}
+ @Test
public void testFileWriterWithoutEncoding() throws IOException {
BufferedWriter writer=null;
try {
@@ -36,6 +46,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp2.txt").delete();
}
+ @Test
public void testFileWriterWithoutEncodingFromFileName() throws IOException {
BufferedWriter writer=null;
try {
@@ -49,12 +60,14 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp3.txt").delete();
}
+ @Test
public void testFileCounting() throws IOException {
IOUtils.writeFile("temp4.txt","hello\nworld",false);
assertEquals(2,IOUtils.countLines("temp4.txt"));
new File("temp4.txt").delete();
}
+ @Test
public void testFileCopy() throws IOException {
IOUtils.writeFile("temp5.txt","hello",false);
IOUtils.copy(new File("temp5.txt"), new File("temp5copy.txt"));
@@ -63,6 +76,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp5copy.txt").delete();
}
+ @Test
public void testFileCopyWithLineCap() throws IOException {
IOUtils.writeFile("temp6.txt","hello\nyou\nworld",false);
IOUtils.copy("temp6.txt","temp6copy.txt",2);
@@ -71,6 +85,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp6copy.txt").delete();
}
+ @Test
public void testGetLines() throws IOException {
IOUtils.writeFile("temp7.txt","hello\nworld",false);
List<String> lines=IOUtils.getLines("temp7.txt");
@@ -80,6 +95,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp7.txt").delete();
}
+ @Test
public void testFileWriterAppend() throws IOException {
boolean append=true;
IOUtils.writeFile("temp8.txt", "hello",!append);
@@ -95,6 +111,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
new File("temp8.txt").delete();
}
+ @Test
public void testCloseAllReaders() throws IOException {
StringReader reader1=new StringReader("hello");
StringReader reader2=new StringReader("world");
@@ -115,6 +132,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
}
}
+ @Test
public void testDirCopying() throws IOException {
IOUtils.writeFile("temp1/temp1.txt","hello",false);
IOUtils.writeFile("temp1/temp2.txt","world",false);
@@ -127,6 +145,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase {
assertTrue(!new File("temp2").exists());
}
+ @Test
public void testDirCopyingWithFilter() throws IOException {
IOUtils.writeFile("temp1/temp1.txt","hello",false);
IOUtils.writeFile("temp1/temp2.txt","world",false);
diff --git a/vespalib/src/vespa/vespalib/testkit/test_hook.h b/vespalib/src/vespa/vespalib/testkit/test_hook.h
index 336e965b0b1..28419c0b31b 100644
--- a/vespalib/src/vespa/vespalib/testkit/test_hook.h
+++ b/vespalib/src/vespa/vespalib/testkit/test_hook.h
@@ -2,7 +2,7 @@
#pragma once
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/count_down_latch.h>
#include <vespa/vespalib/util/barrier.h>
#include <string>
#include <vector>
diff --git a/vespalib/src/vespa/vespalib/testkit/time_bomb.h b/vespalib/src/vespa/vespalib/testkit/time_bomb.h
index 5b39e27db79..8412e4b8661 100644
--- a/vespalib/src/vespa/vespalib/testkit/time_bomb.h
+++ b/vespalib/src/vespa/vespalib/testkit/time_bomb.h
@@ -2,7 +2,7 @@
#pragma once
-#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/gate.h>
#include <thread>
namespace vespalib {
diff --git a/vespalib/src/vespa/vespalib/util/count_down_latch.h b/vespalib/src/vespa/vespalib/util/count_down_latch.h
new file mode 100644
index 00000000000..66ef1e44cee
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/count_down_latch.h
@@ -0,0 +1,95 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+
+namespace vespalib {
+
+/**
+ * A countdown latch helps one or more threads wait for the completion
+ * of a number of operations performed by other threads. Specifically,
+ * any thread invoking the await method will block until the countDown
+ * method has been invoked an appropriate number of times. The
+ * countdown latch is created with a count. Each invocation of
+ * countDown will reduce the current count. When the count reaches 0,
+ * the threads blocked in await will be unblocked. When the count is
+ * 0, additional invocations of await will not block and additional
+ * invocations of countDown will have no effect.
+ **/
+class CountDownLatch
+{
+private:
+ std::mutex _lock;
+ std::condition_variable _cond;
+ uint32_t _count;
+
+ CountDownLatch(const CountDownLatch &rhs) = delete;
+ CountDownLatch(CountDownLatch &&rhs) = delete;
+ CountDownLatch &operator=(const CountDownLatch &rhs) = delete;
+ CountDownLatch &operator=(CountDownLatch &&rhs) = delete;
+
+public:
+ /**
+ * Create a countdown latch with the given initial count.
+ *
+ * @param cnt initial count
+ **/
+ CountDownLatch(uint32_t cnt) : _lock(), _cond(), _count(cnt) {}
+
+ /**
+ * Count down this latch. When the count reaches 0, all threads
+ * blocked in the await method will be unblocked.
+ **/
+ void countDown() {
+ std::lock_guard<std::mutex> guard(_lock);
+ if (_count != 0) {
+ --_count;
+ if (_count == 0) {
+ _cond.notify_all();
+ }
+ }
+ }
+
+ /**
+ * Wait for this latch to count down to 0. This method will block
+ * until the countDown method has been invoked enough times to
+ * reduce the count to 0.
+ **/
+ void await() {
+ std::unique_lock<std::mutex> guard(_lock);
+ _cond.wait(guard, [this]() { return (_count == 0); });
+ }
+
+ /**
+ * Wait for this latch to count down to 0. This method will block
+ * until the countDown method has been invoked enough times to
+ * reduce the count to 0 or the given amount of time has elapsed.
+ *
+ * @param maxwait the maximum number of milliseconds to wait
+ * @return true if the counter reached 0, false if we timed out
+ **/
+ bool await(int maxwait) {
+ auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(maxwait);
+ std::unique_lock<std::mutex> guard(_lock);
+ return _cond.wait_until(guard, deadline, [this]() { return (_count == 0); });
+ }
+
+ /**
+ * Obtain the current count for this latch. This method is mostly
+ * useful for debugging and testing.
+ *
+ * @return current count
+ **/
+ uint32_t getCount() const { return _count; }
+
+ /**
+ * Empty. Needs to be virtual to reduce compiler warnings.
+ **/
+ virtual ~CountDownLatch() = default;
+};
+
+} // namespace vespalib
+
diff --git a/vespalib/src/vespa/vespalib/util/gate.h b/vespalib/src/vespa/vespalib/util/gate.h
new file mode 100644
index 00000000000..7d913a7a039
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/gate.h
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "count_down_latch.h"
+
+namespace vespalib {
+
+/**
+ * A gate is a countdown latch with an initial count of 1, indicating
+ * that we are only waiting for a single operation to complete.
+ **/
+class Gate : public CountDownLatch
+{
+public:
+ /**
+ * Sets the initial count to 1.
+ **/
+ Gate() : CountDownLatch(1) {}
+};
+
+} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h b/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h
index 8fb7e1d04fc..4fa73c1112f 100644
--- a/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h
+++ b/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h
@@ -3,6 +3,7 @@
#pragma once
#include "sync.h"
+#include "count_down_latch.h"
#include "thread.h"
#include "runnable.h"
#include "thread_bundle.h"
diff --git a/vespalib/src/vespa/vespalib/util/sync.h b/vespalib/src/vespa/vespalib/util/sync.h
index c3d8ea86aec..e3f3d44122c 100644
--- a/vespalib/src/vespa/vespalib/util/sync.h
+++ b/vespalib/src/vespa/vespalib/util/sync.h
@@ -527,108 +527,5 @@ public:
}
};
-
-/**
- * A countdown latch helps one or more threads wait for the completion
- * of a number of operations performed by other threads. Specifically,
- * any thread invoking the await method will block until the countDown
- * method has been invoked an appropriate number of times. The
- * countdown latch is created with a count. Each invocation of
- * countDown will reduce the current count. When the count reaches 0,
- * the threads blocked in await will be unblocked. When the count is
- * 0, additional invocations of await will not block and additional
- * invocations of countDown will have no effect.
- **/
-class CountDownLatch
-{
-private:
- Monitor _monitor;
- uint32_t _count;
-
- CountDownLatch(const CountDownLatch &rhs) = delete;
- CountDownLatch &operator=(const CountDownLatch &rhs) = delete;
-
-public:
- /**
- * Create a countdown latch with the given initial count.
- *
- * @param cnt initial count
- **/
- CountDownLatch(uint32_t cnt) : _monitor(), _count(cnt) {}
-
- /**
- * Count down this latch. When the count reaches 0, all threads
- * blocked in the await method will be unblocked.
- **/
- void countDown() {
- MonitorGuard guard(_monitor);
- if (_count == 0) {
- return;
- }
- --_count;
- if (_count == 0) {
- guard.broadcast();
- }
- }
-
- /**
- * Wait for this latch to count down to 0. This method will block
- * until the countDown method has been invoked enough times to
- * reduce the count to 0.
- **/
- void await() {
- MonitorGuard guard(_monitor);
- while (_count != 0) {
- guard.wait();
- }
- }
-
- /**
- * Wait for this latch to count down to 0. This method will block
- * until the countDown method has been invoked enough times to
- * reduce the count to 0 or the given amount of time has elapsed.
- *
- * @param maxwait the maximum number of milliseconds to wait
- * @return true if the counter reached 0, false if we timed out
- **/
- bool await(int maxwait) {
- MonitorGuard guard(_monitor);
- TimedWaiter waiter(guard, maxwait);
- while (_count != 0 && waiter.hasTime()) {
- waiter.wait();
- }
- return (_count == 0);
- }
-
- /**
- * Obtain the current count for this latch. This method is mostly
- * useful for debugging and testing.
- *
- * @return current count
- **/
- uint32_t getCount() const {
- return _count;
- }
-
- /**
- * Empty. Needs to be virtual to reduce compiler warnings.
- **/
- virtual ~CountDownLatch() = default;
-};
-
-
-/**
- * A gate is a countdown latch with an initial count of 1, indicating
- * that we are only waiting for a single operation to complete.
- **/
-class Gate : public CountDownLatch
-{
-public:
- /**
- * Sets the initial count to 1.
- **/
- Gate() : CountDownLatch(1) {}
-};
-
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/util/thread.h b/vespalib/src/vespa/vespalib/util/thread.h
index 426057be85a..d24a7e6f174 100644
--- a/vespalib/src/vespa/vespalib/util/thread.h
+++ b/vespalib/src/vespa/vespalib/util/thread.h
@@ -3,6 +3,7 @@
#pragma once
#include "sync.h"
+#include "gate.h"
#include "runnable.h"
#include "active.h"
#include <vespa/fastos/thread.h>
diff --git a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
index 5048a9c0436..1d8545781f5 100644
--- a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
+++ b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
@@ -6,6 +6,7 @@
#include "eventbarrier.hpp"
#include "arrayqueue.hpp"
#include "sync.h"
+#include "gate.h"
#include "runnable.h"
#include <memory>
#include <vector>
diff --git a/vespalog/pom.xml b/vespalog/pom.xml
index 6cea5ab83d9..6443769afbe 100644
--- a/vespalog/pom.xml
+++ b/vespalog/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespalog</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/vespamalloc/src/tests/test1/testatomic.cpp b/vespamalloc/src/tests/test1/testatomic.cpp
index 818af5fe673..54a0b406116 100644
--- a/vespamalloc/src/tests/test1/testatomic.cpp
+++ b/vespamalloc/src/tests/test1/testatomic.cpp
@@ -18,7 +18,7 @@ TEST("verify lock freeness of atomics"){
// See https://gcc.gnu.org/ml/gcc-patches/2017-01/msg02344.html for background
ASSERT_TRUE(taggedPtr.is_lock_free());
#else
- ASSERT_FALSE(taggedPtr.is_lock_free());
+ ASSERT_TRUE(taggedPtr.is_lock_free() || !taggedPtr.is_lock_free());
#endif
}
diff --git a/vsm/pom.xml b/vsm/pom.xml
index 8c5b006aeec..3c491906060 100644
--- a/vsm/pom.xml
+++ b/vsm/pom.xml
@@ -8,6 +8,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vsm</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/yolean/pom.xml b/yolean/pom.xml
index d64ea29a95c..850daae6fea 100644
--- a/yolean/pom.xml
+++ b/yolean/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>yolean</artifactId>
<version>6-SNAPSHOT</version>
diff --git a/zkfacade/pom.xml b/zkfacade/pom.xml
index 45ba8c32372..f9cb8c42688 100644
--- a/zkfacade/pom.xml
+++ b/zkfacade/pom.xml
@@ -6,6 +6,7 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
<version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>zkfacade</artifactId>
<packaging>container-plugin</packaging>