summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2019-01-13 15:24:00 +0100
committerGitHub <noreply@github.com>2019-01-13 15:24:00 +0100
commit44c89edf64fcae684ab39c42b59fe8b22183f173 (patch)
tree6bfe2ca36265bcd642106e77e37e2c0335b565da
parent03a344eba3265b5fc5d99d849e9d52ba05a31832 (diff)
parent028fd60d61854d074d2d8e5a4fb8b416abc7a62c (diff)
Merge branch 'master' into jvenstad/remove-feature-flag-for-cache-invalidation-strategy
-rwxr-xr-xconfig-lib/src/main/java/com/yahoo/config/FileReference.java2
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java4
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/Search.java2
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java12
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/ImportedFields.java40
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java25
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedComplexSDField.java29
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java3
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableSDField.java2
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedComplexField.java49
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedField.java3
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedFields.java5
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedSimpleField.java18
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java4
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java18
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java18
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java66
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java1
-rw-r--r--config-model/src/test/derived/imported_position_field_summary/index-info.cfg16
-rw-r--r--config-model/src/test/derived/imported_struct_fields/index-info.cfg144
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsTestCase.java27
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java5
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java1
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/UrlDownloadRpcServer.java148
-rw-r--r--config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java39
-rw-r--r--config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java7
-rw-r--r--config/src/main/java/com/yahoo/vespa/config/UrlDownloader.java98
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java3
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java26
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/Metrics.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java6
-rw-r--r--configserver/src/main/resources/logd/logd.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java10
-rwxr-xr-xcontainer-core/src/main/java/com/yahoo/container/Container.java9
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java2
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java1
-rw-r--r--container-search/abi-spec.json1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/Discloser.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/TextualQueryRepresentation.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/Query.java33
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Model.java25
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Select.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/SelectParser.java130
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java40
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java12
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/semantics/test/PhraseMatchTestCase.java5
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/semantics/test/rulebases/phrasematch.sr2
-rw-r--r--container-search/src/test/java/com/yahoo/search/handler/test/JSONSearchHandlerTestCase.java65
-rw-r--r--container-search/src/test/java/com/yahoo/search/handler/test/SearchHandlerTestCase.java13
-rw-r--r--container-search/src/test/java/com/yahoo/search/handler/test/config/chains.cfg10
-rw-r--r--container-search/src/test/java/com/yahoo/search/query/profile/config/test/QueryProfileIntegrationTestCase.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/query/profile/test/QueryProfileSubstitutionTestCase.java40
-rw-r--r--container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java43
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java27
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled-no-op.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-pin-cancelled.json1
-rw-r--r--document/abi-spec.json12
-rw-r--r--document/src/main/java/com/yahoo/document/datatypes/BoolFieldValue.java1
-rw-r--r--document/src/main/java/com/yahoo/document/json/DocumentUpdateJsonSerializer.java6
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java10
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonWriter.java6
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/FieldReader.java27
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/FieldWriter.java11
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializer42.java6
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializerHead.java6
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializer42.java15
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializerHead.java7
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/XmlDocumentWriter.java6
-rw-r--r--document/src/main/java/com/yahoo/vespaxmlparser/VespaXMLFieldReader.java13
-rw-r--r--document/src/test/java/com/yahoo/document/DocumentSerializationTestCase.java11
-rw-r--r--document/src/test/java/com/yahoo/document/DocumentTestCase.java11
-rw-r--r--document/src/tests/data/crossplatform-java-cpp-document.cfg5
-rw-r--r--document/src/tests/data/serializejava-compressed.datbin377 -> 384 bytes
-rw-r--r--document/src/tests/data/serializejava.datbin398 -> 405 bytes
-rw-r--r--document/src/vespa/document/datatype/datatype.cpp4
-rw-r--r--document/src/vespa/document/datatype/datatype.h4
-rw-r--r--document/src/vespa/document/datatype/primitivedatatype.cpp23
-rw-r--r--document/src/vespa/document/fieldvalue/CMakeLists.txt1
-rw-r--r--document/src/vespa/document/fieldvalue/boolfieldvalue.cpp85
-rw-r--r--document/src/vespa/document/fieldvalue/boolfieldvalue.h47
-rw-r--r--document/src/vespa/document/fieldvalue/document.cpp1
-rw-r--r--document/src/vespa/document/fieldvalue/fieldvalues.h29
-rw-r--r--document/src/vespa/document/fieldvalue/fieldvaluevisitor.h3
-rw-r--r--document/src/vespa/document/fieldvalue/numericfieldvalue.hpp3
-rw-r--r--document/src/vespa/document/fieldvalue/predicatefieldvalue.h3
-rw-r--r--document/src/vespa/document/serialization/vespadocumentdeserializer.cpp17
-rw-r--r--document/src/vespa/document/serialization/vespadocumentdeserializer.h2
-rw-r--r--document/src/vespa/document/serialization/vespadocumentserializer.cpp6
-rw-r--r--document/src/vespa/document/serialization/vespadocumentserializer.h8
-rw-r--r--document/src/vespa/document/util/identifiableid.h2
-rw-r--r--flags/pom.xml6
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java8
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/file/FlagDbFile.java111
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java5
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java19
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java37
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/file/FlagDbFileTest.java76
-rw-r--r--jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/RetryingJaxRsStrategy.java8
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/Request.java2
-rw-r--r--node-admin/pom.xml6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java15
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java28
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java3
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java19
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java43
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java108
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java33
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgent.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java11
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextFactory.java12
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java65
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManager.java102
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextSupplier.java21
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentFactory.java10
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java259
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentScheduler.java21
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java41
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java24
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java11
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java106
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java51
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManagerTest.java142
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java220
-rw-r--r--node-repository/src/main/config/node-repository.xml5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java83
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Maintainer.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java14
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabase.java47
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java23
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java52
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java78
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java46
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java55
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java20
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java96
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java40
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java24
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json43
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json33
-rw-r--r--searchcore/src/tests/proton/docsummary/docsummary.cpp97
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/document_field_extractor.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/docsummary/documentstoreadapter.cpp74
-rw-r--r--searchcore/src/vespa/searchcore/proton/docsummary/summarymanager.cpp21
-rw-r--r--searchlib/src/tests/fef/termfieldmodel/termfieldmodel_test.cpp28
-rw-r--r--searchlib/src/vespa/searchlib/datastore/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/datastore/entryref.cpp17
-rw-r--r--searchlib/src/vespa/searchlib/datastore/entryref.h8
-rw-r--r--searchlib/src/vespa/searchlib/datastore/entryref.hpp18
-rw-r--r--searchlib/src/vespa/searchlib/util/sigbushandler.cpp5
-rw-r--r--searchsummary/src/tests/docsumformat/docsum-pack.cpp306
-rw-r--r--searchsummary/src/tests/docsummary/positionsdfw_test.cpp8
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp8
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h5
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp32
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attributedfw.h2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp40
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h43
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp39
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp102
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.cpp3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.h3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp18
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h5
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.h3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/resultclass.h1
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/resultconfig.cpp62
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/resultconfig.h29
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/resultpacker.cpp35
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp13
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.h4
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/summaryfieldconverter.cpp89
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/textextractordfw.cpp9
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/urlresult.cpp545
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/urlresult.h48
-rw-r--r--staging_vespalib/src/tests/state_server/state_server_test.cpp46
-rw-r--r--staging_vespalib/src/vespa/vespalib/net/http_server.cpp2
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/xmlserializable.cpp6
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/xmlserializable.h6
-rw-r--r--vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java21
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java4
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java3
-rw-r--r--vespalib/src/tests/portal/http_request/http_request_test.cpp55
-rw-r--r--vespalib/src/tests/portal/portal_test.cpp36
-rw-r--r--vespalib/src/vespa/vespalib/portal/http_request.cpp82
-rw-r--r--vespalib/src/vespa/vespalib/portal/http_request.h6
-rw-r--r--vespalib/src/vespa/vespalib/portal/portal.cpp30
-rw-r--r--vespalib/src/vespa/vespalib/portal/portal.h4
-rw-r--r--vsm/src/vespa/vsm/vsm/docsumconfig.cpp2
234 files changed, 3876 insertions, 2511 deletions
diff --git a/config-lib/src/main/java/com/yahoo/config/FileReference.java b/config-lib/src/main/java/com/yahoo/config/FileReference.java
index 7d455c58b30..3b95c2fbd4c 100755
--- a/config-lib/src/main/java/com/yahoo/config/FileReference.java
+++ b/config-lib/src/main/java/com/yahoo/config/FileReference.java
@@ -44,7 +44,7 @@ public final class FileReference {
}
public static List<String> toValues(Collection<FileReference> references) {
- List<String> ret = new ArrayList<String>();
+ List<String> ret = new ArrayList<>();
for (FileReference r: references) {
ret.add(r.value());
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
index d94d072e75c..bc49c40e4e1 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
@@ -7,6 +7,7 @@ import com.yahoo.search.query.profile.QueryProfileRegistry;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
import com.yahoo.search.query.ranking.Diversity;
+import com.yahoo.searchdefinition.document.Attribute;
import com.yahoo.searchdefinition.document.ImmutableSDField;
import com.yahoo.searchdefinition.expressiontransforms.ExpressionTransforms;
import com.yahoo.searchdefinition.expressiontransforms.RankProfileTransformContext;
@@ -773,9 +774,10 @@ public class RankProfile implements Serializable, Cloneable {
}
private void addAttributeFeatureTypes(ImmutableSDField field, MapEvaluationTypeContext context) {
+ Attribute attribute = field.getAttribute();
field.getAttributes().forEach((k, a) -> {
String name = k;
- if (k.equals(field.getBackingField().getName())) // this attribute should take the fields name
+ if (attribute == a) // this attribute should take the fields name
name = field.getName(); // switch to that - it is separate for imported fields
context.setType(FeatureNames.asAttributeFeature(name),
a.tensorType().orElse(TensorType.empty));
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java
index 7fd996edc39..ba2421bf5bb 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java
@@ -179,7 +179,7 @@ public class Search implements Serializable, ImmutableSearch {
return importedFields
.map(fields -> fields.fields().values().stream())
.orElse(Stream.empty())
- .map(ImmutableImportedSDField::new);
+ .map(field -> field.asImmutableSDField());
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
index 9ea17dab2ba..76dff404568 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
@@ -53,9 +53,7 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
if (unsupportedFieldType(field)) {
return; // Ignore complex struct and map fields for indexed search (only supported for streaming search)
}
- if (field.isImportedField()) {
- deriveImportedAttributes(field);
- } else if (isArrayOfSimpleStruct(field)) {
+ if (isArrayOfSimpleStruct(field)) {
deriveArrayOfSimpleStruct(field);
} else if (isMapOfSimpleStruct(field)) {
deriveMapOfSimpleStruct(field);
@@ -84,6 +82,10 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
/** Derives one attribute. TODO: Support non-default named attributes */
private void deriveAttributes(ImmutableSDField field) {
+ if (field.isImportedField()) {
+ deriveImportedAttributes(field);
+ return;
+ }
for (Attribute fieldAttribute : field.getAttributes().values()) {
deriveAttribute(field, fieldAttribute);
}
@@ -125,6 +127,10 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
}
private void deriveAttributeAsArrayType(ImmutableSDField field) {
+ if (field.isImportedField()) {
+ deriveImportedAttributes(field);
+ return;
+ }
Attribute attribute = field.getAttributes().get(field.getName());
if (attribute != null) {
attributes.put(attribute.getName(), attribute.convertToArray());
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/ImportedFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/ImportedFields.java
index 82b56f9c961..b44522e9771 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/ImportedFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/ImportedFields.java
@@ -1,14 +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.searchdefinition.derived;
+import com.yahoo.document.DataType;
+import com.yahoo.document.PositionDataType;
import com.yahoo.searchdefinition.Search;
import com.yahoo.searchdefinition.document.Attribute;
import com.yahoo.searchdefinition.document.ImmutableSDField;
+import com.yahoo.searchdefinition.document.ImportedComplexField;
import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.vespa.config.search.ImportedFieldsConfig;
import java.util.Optional;
+import static com.yahoo.searchdefinition.document.ComplexAttributeFieldUtils.isArrayOfSimpleStruct;
+import static com.yahoo.searchdefinition.document.ComplexAttributeFieldUtils.isMapOfPrimitiveType;
+import static com.yahoo.searchdefinition.document.ComplexAttributeFieldUtils.isMapOfSimpleStruct;
+
/**
* This class derives imported fields from search definition and produces imported-fields.cfg as needed by the search backend.
*
@@ -44,6 +51,37 @@ public class ImportedFields extends Derived implements ImportedFieldsConfig.Prod
}
private static void considerField(ImportedFieldsConfig.Builder builder, ImportedField field) {
+ if (field instanceof ImportedComplexField) {
+ considerComplexField(builder, (ImportedComplexField) field);
+ } else {
+ considerSimpleField(builder, field);
+ }
+ }
+
+ private static void considerComplexField(ImportedFieldsConfig.Builder builder, ImportedComplexField field) {
+ ImmutableSDField targetField = field.targetField();
+ if (targetField.getDataType().equals(PositionDataType.INSTANCE) ||
+ targetField.getDataType().equals(DataType.getArray(PositionDataType.INSTANCE))) {
+
+ } else if (isArrayOfSimpleStruct(targetField)) {
+ considerNestedFields(builder, field);
+ } else if (isMapOfSimpleStruct(targetField)) {
+ considerSimpleField(builder, field.getNestedField("key"));
+ considerNestedFields(builder, field.getNestedField("value"));
+ } else if (isMapOfPrimitiveType(targetField)) {
+ considerSimpleField(builder, field.getNestedField("key"));
+ considerSimpleField(builder, field.getNestedField("value"));
+ }
+ }
+
+ private static void considerNestedFields(ImportedFieldsConfig.Builder builder, ImportedField field) {
+ if (field instanceof ImportedComplexField) {
+ ImportedComplexField complexField = (ImportedComplexField) field;
+ complexField.getNestedFields().forEach(nestedField -> considerSimpleField(builder, nestedField));
+ }
+ }
+
+ private static void considerSimpleField(ImportedFieldsConfig.Builder builder, ImportedField field) {
ImmutableSDField targetField = field.targetField();
String targetFieldName = targetField.getName();
if (!isNestedFieldName(targetFieldName)) {
@@ -51,7 +89,7 @@ public class ImportedFields extends Derived implements ImportedFieldsConfig.Prod
builder.attribute.add(createAttributeBuilder(field));
}
} else {
- Attribute attribute = targetField.getAttributes().get(targetFieldName);
+ Attribute attribute = targetField.getAttribute();
if (attribute != null) {
builder.attribute.add(createAttributeBuilder(field));
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java
index 4f71bd90830..1e4a4a2a5d2 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java
@@ -74,9 +74,6 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
addIndexCommand(summaryField.getName(), CMD_HIGHLIGHT);
}
}
- search.importedFields().map(fields -> fields.complexFields().values().stream()).
- orElse(Stream.empty()).
- forEach(field -> deriveImportedComplexField(field));
}
@Override
@@ -86,19 +83,6 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
}
}
- private void deriveImportedComplexField(ImportedField field) {
- String fieldName = field.fieldName();
- if (isPositionField(field.targetField())) {
- addIndexCommand(fieldName, CMD_DEFAULT_POSITION);
- if (isPositionArrayField(field.targetField())) {
- addIndexCommand(fieldName, CMD_MULTIVALUE);
- }
- } else {
- addIndexCommand(fieldName, CMD_MULTIVALUE);
- }
- addIndexCommand(fieldName, CMD_INDEX);
- }
-
private String toSpaceSeparated(Collection c) {
StringBuffer b = new StringBuffer();
for (Iterator i = c.iterator(); i.hasNext();) {
@@ -163,7 +147,7 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
addIndexCommand(field, CMD_MULTIVALUE);
}
- Attribute attribute = getAttribute(field);
+ Attribute attribute = field.getAttribute();
if ((field.doesAttributing() || (attribute != null && !inPosition)) && !field.doesIndexing()) {
addIndexCommand(field.getName(), CMD_ATTRIBUTE);
if (attribute != null && attribute.isFastSearch())
@@ -195,13 +179,6 @@ public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
}
- private static Attribute getAttribute(ImmutableSDField field) {
- while (field.isImportedField()) {
- field = field.getBackingField();
- }
- return field.getAttributes().get(field.getName());
- }
-
static String stemCmd(ImmutableSDField field, Search search) {
return CMD_STEM + ":" + field.getStemming(search).toStemMode();
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedComplexSDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedComplexSDField.java
new file mode 100644
index 00000000000..2f13c0078ee
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedComplexSDField.java
@@ -0,0 +1,29 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.util.Collection;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Wraps {@link ImportedComplexField} as {@link ImmutableSDField}.
+ */
+public class ImmutableImportedComplexSDField extends ImmutableImportedSDField {
+ private final ImportedComplexField importedComplexField;
+
+ public ImmutableImportedComplexSDField(ImportedComplexField importedField) {
+ super(importedField);
+ importedComplexField = importedField;
+ }
+
+ @Override
+ public ImmutableSDField getStructField(String name) {
+ ImportedField field = importedComplexField.getNestedField(name);
+ return (field != null) ? field.asImmutableSDField() : null;
+ }
+
+ @Override
+ public Collection<? extends ImmutableSDField> getStructFields() {
+ return importedComplexField.getNestedFields().stream().map(field -> field.asImmutableSDField()).collect(toList());
+ }
+}
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 e1253d14747..be5f135f819 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
@@ -101,6 +101,9 @@ public class ImmutableImportedSDField implements ImmutableSDField {
}
@Override
+ public Attribute getAttribute() { return importedField.targetField().getAttribute(); }
+
+ @Override
public Map<String, String> getAliasToName() {
return Collections.emptyMap();
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableSDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableSDField.java
index 21ef60cf0b9..15e75ad8314 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableSDField.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableSDField.java
@@ -55,6 +55,8 @@ public interface ImmutableSDField {
Map<String, Attribute> getAttributes();
+ Attribute getAttribute();
+
Map<String, String> getAliasToName();
ScriptExpression getIndexingScript();
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedComplexField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedComplexField.java
new file mode 100644
index 00000000000..56ef7527025
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedComplexField.java
@@ -0,0 +1,49 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.searchdefinition.DocumentReference;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A complex field that is imported from a concrete field in a referenced document type and given an alias name.
+ */
+public class ImportedComplexField extends ImportedField {
+
+ private Map<String, ImportedField> nestedFields;
+
+ public ImportedComplexField(String fieldName, DocumentReference reference, ImmutableSDField targetField) {
+ super(fieldName, reference, targetField);
+ nestedFields = new java.util.LinkedHashMap<>(0);
+ }
+
+ @Override
+ public ImmutableSDField asImmutableSDField() {
+ return new ImmutableImportedComplexSDField(this);
+ }
+
+ public void addNestedField(ImportedField importedField) {
+ String prefix = fieldName() + ".";
+ assert(importedField.fieldName().substring(0, prefix.length()).equals(prefix));
+ String suffix = importedField.fieldName().substring(prefix.length());
+ nestedFields.put(suffix, importedField);
+ }
+
+ public Collection<ImportedField> getNestedFields() {
+ return nestedFields.values();
+ }
+
+ public ImportedField getNestedField(String name) {
+ if (name.contains(".")) {
+ String superFieldName = name.substring(0,name.indexOf("."));
+ String subFieldName = name.substring(name.indexOf(".")+1);
+ ImportedField superField = nestedFields.get(superFieldName);
+ if (superField != null && superField instanceof ImportedComplexField) {
+ return ((ImportedComplexField)superField).getNestedField(subFieldName);
+ }
+ return null;
+ }
+ return nestedFields.get(name);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedField.java
index 1a6a24275ac..ab108213ac8 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedField.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedField.java
@@ -8,7 +8,7 @@ import com.yahoo.searchdefinition.DocumentReference;
*
* @author geirst
*/
-public class ImportedField {
+public abstract class ImportedField {
private final String fieldName;
private final DocumentReference reference;
@@ -34,4 +34,5 @@ public class ImportedField {
return targetField;
}
+ public abstract ImmutableSDField asImmutableSDField();
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedFields.java
index 7c67fc422d4..2192a7e7bb1 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedFields.java
@@ -12,15 +12,12 @@ import java.util.Map;
public class ImportedFields {
private final Map<String, ImportedField> fields;
- private final Map<String, ImportedField> complexFields;
- public ImportedFields(Map<String, ImportedField> fields, Map<String, ImportedField> complexFields) {
+ public ImportedFields(Map<String, ImportedField> fields) {
this.fields = fields;
- this.complexFields = complexFields;
}
public Map<String, ImportedField> fields() {
return Collections.unmodifiableMap(fields);
}
- public Map<String, ImportedField> complexFields() { return Collections.unmodifiableMap(complexFields); }
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedSimpleField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedSimpleField.java
new file mode 100644
index 00000000000..63f7f99c772
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImportedSimpleField.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.searchdefinition.DocumentReference;
+
+/**
+ * A simple field that is imported from a concrete field in a referenced document type and given an alias name.
+ */
+public class ImportedSimpleField extends ImportedField {
+ public ImportedSimpleField(String fieldName, DocumentReference reference, ImmutableSDField targetField) {
+ super(fieldName, reference, targetField);
+ }
+
+ @Override
+ public ImmutableSDField asImmutableSDField() {
+ return new ImmutableImportedSDField(this);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java
index 16e1e2e4e1d..049f5392c04 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java
@@ -634,6 +634,10 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer,
@Override
public Map<String, Attribute> getAttributes() { return attributes; }
+ public Attribute getAttribute() {
+ return attributes.get(getName());
+ }
+
public void addAttribute(Attribute attribute) {
String name = attribute.getName();
if (name == null || "".equals(name)) {
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java
index e324944549c..59dc4275e15 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java
@@ -4,7 +4,9 @@ package com.yahoo.searchdefinition.processing;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.searchdefinition.RankProfileRegistry;
import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.ImmutableImportedComplexSDField;
import com.yahoo.searchdefinition.document.ImmutableSDField;
+import com.yahoo.searchdefinition.document.ImportedComplexField;
import com.yahoo.vespa.documentmodel.SummaryField;
import com.yahoo.vespa.documentmodel.SummaryTransform;
import com.yahoo.vespa.model.container.search.QueryProfiles;
@@ -30,19 +32,21 @@ public class AddAttributeTransformToSummaryOfImportedFields extends Processor {
@Override
public void process(boolean validate, boolean documentsOnly) {
search.allImportedFields()
- .flatMap(this::getSummaryFieldsForImportedField)
- .forEach(AddAttributeTransformToSummaryOfImportedFields::setAttributeTransform);
- search.importedFields().map(fields -> fields.complexFields().values().stream()).
- orElse(Stream.empty()).
- map(ImmutableImportedSDField::new).
- flatMap(this::getSummaryFieldsForImportedField).
- forEach(AddAttributeTransformToSummaryOfImportedFields::setAttributeCombinerTransform);
+ .forEach(field -> setTransform(field));
}
private Stream<SummaryField> getSummaryFieldsForImportedField(ImmutableSDField importedField) {
return search.getSummaryFields(importedField).values().stream();
}
+ private void setTransform(ImmutableSDField field) {
+ if (field instanceof ImmutableImportedComplexSDField) {
+ getSummaryFieldsForImportedField(field).forEach(AddAttributeTransformToSummaryOfImportedFields::setAttributeCombinerTransform);
+ } else {
+ getSummaryFieldsForImportedField(field).forEach(AddAttributeTransformToSummaryOfImportedFields::setAttributeTransform);
+ }
+ }
+
private static void setAttributeTransform(SummaryField summaryField) {
if (summaryField.getTransform() == SummaryTransform.NONE) {
summaryField.setTransform(SummaryTransform.ATTRIBUTE);
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java
index 0bc9a517d2e..e59fcdf3dd0 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java
@@ -42,7 +42,7 @@ public class AdjustPositionSummaryFields extends Processor {
if (isPositionDataType(summaryField.getDataType())) {
String originalSource = summaryField.getSingleSource();
if (originalSource.indexOf('.') == -1) { // Eliminate summary fields with pos.x or pos.y as source
- ImmutableSDField sourceField = getSourceField(originalSource);
+ ImmutableSDField sourceField = search.getField(originalSource);
if (sourceField != null) {
String zCurve = null;
if (sourceField.getDataType().equals(summaryField.getDataType())) {
@@ -95,26 +95,12 @@ public class AdjustPositionSummaryFields extends Processor {
summary.add(oldField);
}
- private ImmutableSDField getSourceField(String name) {
- ImmutableSDField field = search.getField(name);
- if (field == null && search.importedFields().isPresent()) {
- ImportedField importedField = search.importedFields().get().complexFields().get(name);
- if (importedField != null) {
- field = new ImmutableImportedSDField(importedField);
- }
- }
- return field;
- }
-
private boolean hasPositionAttribute(String name) {
Attribute attribute = search.getAttribute(name);
if (attribute == null) {
ImmutableSDField field = search.getField(name);
if (field != null && field.isImportedField()) {
- while (field.isImportedField()) {
- field = field.getBackingField();
- }
- attribute = field.getAttributes().get(field.getName());
+ attribute = field.getAttribute();
}
}
return attribute != null && attribute.isPosition();
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java
index c8ecc31103a..d6c334ee80b 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolver.java
@@ -2,19 +2,18 @@
package com.yahoo.searchdefinition.processing;
import com.yahoo.config.application.api.DeployLogger;
-import com.yahoo.document.ArrayDataType;
import com.yahoo.document.DataType;
-import com.yahoo.document.MapDataType;
import com.yahoo.document.PositionDataType;
-import com.yahoo.document.StructDataType;
import com.yahoo.searchdefinition.DocumentReference;
import com.yahoo.searchdefinition.DocumentReferences;
import com.yahoo.searchdefinition.RankProfileRegistry;
import com.yahoo.searchdefinition.Search;
import com.yahoo.searchdefinition.document.Attribute;
import com.yahoo.searchdefinition.document.ImmutableSDField;
+import com.yahoo.searchdefinition.document.ImportedComplexField;
import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.searchdefinition.document.ImportedFields;
+import com.yahoo.searchdefinition.document.ImportedSimpleField;
import com.yahoo.searchdefinition.document.TemporaryImportedField;
import com.yahoo.vespa.model.container.search.QueryProfiles;
@@ -34,7 +33,6 @@ import static com.yahoo.searchdefinition.document.ComplexAttributeFieldUtils.isM
public class ImportedFieldsResolver extends Processor {
private final Map<String, ImportedField> importedFields = new LinkedHashMap<>();
- private final Map<String, ImportedField> importedComplexFields = new LinkedHashMap<>();
private final Optional<DocumentReferences> references;
public ImportedFieldsResolver(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
@@ -45,7 +43,7 @@ public class ImportedFieldsResolver extends Processor {
@Override
public void process(boolean validate, boolean documentsOnly) {
search.temporaryImportedFields().get().fields().forEach((name, field) -> resolveImportedField(field, validate));
- search.setImportedFields(new ImportedFields(importedFields, importedComplexFields));
+ search.setImportedFields(new ImportedFields(importedFields));
}
private void resolveImportedField(TemporaryImportedField importedField, boolean validate) {
@@ -71,34 +69,41 @@ public class ImportedFieldsResolver extends Processor {
reference.referenceField().getName(), PositionDataType.getZCurveFieldName(targetField.getName()));
ImmutableSDField targetZCurveField = getTargetField(importedZCurveField, reference);
resolveImportedNormalField(importedZCurveField, reference, targetZCurveField, validate);
- makeImportedComplexField(importedField, reference, targetField);
+ ImportedComplexField importedStructField = new ImportedComplexField(importedField.fieldName(), reference, targetField);
+ registerImportedField(importedField, null, importedStructField);
}
private void resolveImportedArrayOfStructField(TemporaryImportedField importedField, DocumentReference reference,
ImmutableSDField targetField, boolean validate) {
- resolveImportedNestedStructField(importedField, reference, targetField, validate);
- makeImportedComplexField(importedField, reference, targetField);
+ ImportedComplexField importedStructField = new ImportedComplexField(importedField.fieldName(), reference, targetField);
+ resolveImportedNestedStructField(importedField, reference, importedStructField, targetField, validate);
+ registerImportedField(importedField, null, importedStructField);
}
private void resolveImportedMapOfStructField(TemporaryImportedField importedField, DocumentReference reference,
ImmutableSDField targetField, boolean validate) {
- resolveImportedNestedField(importedField, reference, targetField.getStructField("key"), validate);
- resolveImportedNestedStructField(importedField, reference, targetField.getStructField("value"), validate);
- makeImportedComplexField(importedField, reference, targetField);
+ ImportedComplexField importedMapField = new ImportedComplexField(importedField.fieldName(), reference, targetField);
+ ImportedComplexField importedStructField = new ImportedComplexField(importedField.fieldName() + ".value", reference, targetField.getStructField("value"));
+ importedMapField.addNestedField(importedStructField);
+ resolveImportedNestedField(importedField, reference, importedMapField, targetField.getStructField("key"), validate);
+ resolveImportedNestedStructField(importedField, reference, importedStructField, importedStructField.targetField(), validate);
+ registerImportedField(importedField, null, importedMapField);
}
- private void makeImportedNormalField(TemporaryImportedField importedField, String name, DocumentReference reference,
- ImmutableSDField targetField) {
- if (importedFields.get(name) != null) {
- fail(importedField, name, targetFieldAsString(targetField.getName(), reference) +": Field already imported");
- }
- importedFields.put(name, new ImportedField(name, reference, targetField));
+ private void makeImportedNormalField(TemporaryImportedField importedField, ImportedComplexField owner, String name, DocumentReference reference, ImmutableSDField targetField) {
+ ImportedField importedSimpleField = new ImportedSimpleField(name, reference, targetField);
+ registerImportedField(importedField, owner, importedSimpleField);
}
- private void makeImportedComplexField(TemporaryImportedField importedField, DocumentReference reference,
- ImmutableSDField targetField) {
- String name = importedField.fieldName();
- importedComplexFields.put(name, new ImportedField(name, reference, targetField));
+ private void registerImportedField(TemporaryImportedField temporaryImportedField, ImportedComplexField owner, ImportedField importedField) {
+ if (owner != null) {
+ owner.addNestedField(importedField);
+ } else {
+ if (importedFields.get(importedField.fieldName()) != null) {
+ fail(temporaryImportedField, importedField.fieldName(), targetFieldAsString(importedField.targetField().getName(), importedField.reference()) + ": Field already imported");
+ }
+ importedFields.put(importedField.fieldName(), importedField);
+ }
}
private static String makeImportedNestedFieldName(TemporaryImportedField importedField, ImmutableSDField targetNestedField) {
@@ -106,11 +111,11 @@ public class ImportedFieldsResolver extends Processor {
}
private boolean resolveImportedNestedField(TemporaryImportedField importedField, DocumentReference reference,
- ImmutableSDField targetNestedField, boolean requireAttribute) {
- Attribute attribute = targetNestedField.getAttributes().get(targetNestedField.getName());
+ ImportedComplexField owner, ImmutableSDField targetNestedField, boolean requireAttribute) {
+ Attribute attribute = targetNestedField.getAttribute();
String importedNestedFieldName = makeImportedNestedFieldName(importedField, targetNestedField);
if (attribute != null) {
- makeImportedNormalField(importedField, importedNestedFieldName, reference, targetNestedField);
+ makeImportedNormalField(importedField, owner, importedNestedFieldName, reference, targetNestedField);
} else if (requireAttribute) {
fail(importedField, importedNestedFieldName, targetFieldAsString(targetNestedField.getName(), reference) +
": Is not an attribute field. Only attribute fields supported");
@@ -119,10 +124,10 @@ public class ImportedFieldsResolver extends Processor {
}
private void resolveImportedNestedStructField(TemporaryImportedField importedField, DocumentReference reference,
- ImmutableSDField targetNestedField, boolean validate) {
+ ImportedComplexField ownerField, ImmutableSDField targetNestedField, boolean validate) {
boolean foundAttribute = false;
for (ImmutableSDField targetStructField : targetNestedField.getStructFields()) {
- if (resolveImportedNestedField(importedField, reference, targetStructField, false)) {
+ if (resolveImportedNestedField(importedField, reference, ownerField, targetStructField, false)) {
foundAttribute = true;
};
}
@@ -135,9 +140,10 @@ public class ImportedFieldsResolver extends Processor {
private void resolveImportedMapOfPrimitiveField(TemporaryImportedField importedField, DocumentReference reference,
ImmutableSDField targetField, boolean validate) {
- resolveImportedNestedField(importedField, reference, targetField.getStructField("key"), validate);
- resolveImportedNestedField(importedField, reference, targetField.getStructField("value"), validate);
- makeImportedComplexField(importedField, reference, targetField);
+ ImportedComplexField importedMapField = new ImportedComplexField(importedField.fieldName(), reference, targetField);
+ resolveImportedNestedField(importedField, reference, importedMapField, targetField.getStructField("key"), validate);
+ resolveImportedNestedField(importedField, reference, importedMapField, targetField.getStructField("value"), validate);
+ registerImportedField(importedField, null, importedMapField);
}
private void resolveImportedNormalField(TemporaryImportedField importedField, DocumentReference reference,
@@ -145,7 +151,7 @@ public class ImportedFieldsResolver extends Processor {
if (validate) {
validateTargetField(importedField, targetField, reference);
}
- makeImportedNormalField(importedField, importedField.fieldName(), reference, targetField);
+ makeImportedNormalField(importedField, null, importedField.fieldName(), reference, targetField);
}
private DocumentReference validateDocumentReference(TemporaryImportedField importedField) {
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java
index 008b3182d8f..c87801685bb 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java
@@ -51,7 +51,6 @@ public class SummaryFieldsMustHaveValidSource extends Processor {
return isDocumentField(source) ||
(isNotInThisSummaryClass(summary, source) && isSummaryField(source)) ||
(isInThisSummaryClass(summary, source) && !source.equals(summaryField.getName())) ||
- (search.importedFields().map(fields -> fields.complexFields().get(source) != null).orElse(false)) ||
(SummaryClass.DOCUMENT_ID_FIELD.equals(source));
}
diff --git a/config-model/src/test/derived/imported_position_field_summary/index-info.cfg b/config-model/src/test/derived/imported_position_field_summary/index-info.cfg
index c87553d6537..4c8dafdf59b 100644
--- a/config-model/src/test/derived/imported_position_field_summary/index-info.cfg
+++ b/config-model/src/test/derived/imported_position_field_summary/index-info.cfg
@@ -9,18 +9,6 @@ indexinfo[].command[].indexname "parent_ref"
indexinfo[].command[].command "attribute"
indexinfo[].command[].indexname "parent_ref"
indexinfo[].command[].command "word"
-indexinfo[].command[].indexname "my_pos.x"
-indexinfo[].command[].command "index"
-indexinfo[].command[].indexname "my_pos.x"
-indexinfo[].command[].command "numerical"
-indexinfo[].command[].indexname "my_pos.y"
-indexinfo[].command[].command "index"
-indexinfo[].command[].indexname "my_pos.y"
-indexinfo[].command[].command "numerical"
-indexinfo[].command[].indexname "my_pos"
-indexinfo[].command[].command "default-position"
-indexinfo[].command[].indexname "my_pos"
-indexinfo[].command[].command "index"
indexinfo[].command[].indexname "my_pos.distance"
indexinfo[].command[].command "index"
indexinfo[].command[].indexname "my_pos.distance"
@@ -41,3 +29,7 @@ indexinfo[].command[].indexname "my_pos_zcurve"
indexinfo[].command[].command "fast-search"
indexinfo[].command[].indexname "my_pos_zcurve"
indexinfo[].command[].command "numerical"
+indexinfo[].command[].indexname "my_pos"
+indexinfo[].command[].command "default-position"
+indexinfo[].command[].indexname "my_pos"
+indexinfo[].command[].command "index"
diff --git a/config-model/src/test/derived/imported_struct_fields/index-info.cfg b/config-model/src/test/derived/imported_struct_fields/index-info.cfg
index b32b487fe0f..8d7f4d4bece 100644
--- a/config-model/src/test/derived/imported_struct_fields/index-info.cfg
+++ b/config-model/src/test/derived/imported_struct_fields/index-info.cfg
@@ -1,71 +1,73 @@
-indexinfo[0].name "child"
-indexinfo[0].command[0].indexname "sddocname"
-indexinfo[0].command[0].command "index"
-indexinfo[0].command[1].indexname "sddocname"
-indexinfo[0].command[1].command "word"
-indexinfo[0].command[2].indexname "parent_ref"
-indexinfo[0].command[2].command "index"
-indexinfo[0].command[3].indexname "parent_ref"
-indexinfo[0].command[3].command "attribute"
-indexinfo[0].command[4].indexname "parent_ref"
-indexinfo[0].command[4].command "word"
-indexinfo[0].command[5].indexname "documentid"
-indexinfo[0].command[5].command "index"
-indexinfo[0].command[6].indexname "rankfeatures"
-indexinfo[0].command[6].command "index"
-indexinfo[0].command[7].indexname "summaryfeatures"
-indexinfo[0].command[7].command "index"
-indexinfo[0].command[8].indexname "my_elem_array.name"
-indexinfo[0].command[8].command "index"
-indexinfo[0].command[9].indexname "my_elem_array.name"
-indexinfo[0].command[9].command "attribute"
-indexinfo[0].command[10].indexname "my_elem_array.name"
-indexinfo[0].command[10].command "fast-search"
-indexinfo[0].command[11].indexname "my_elem_array.weight"
-indexinfo[0].command[11].command "index"
-indexinfo[0].command[12].indexname "my_elem_array.weight"
-indexinfo[0].command[12].command "attribute"
-indexinfo[0].command[13].indexname "my_elem_array.weight"
-indexinfo[0].command[13].command "numerical"
-indexinfo[0].command[14].indexname "my_elem_map.key"
-indexinfo[0].command[14].command "index"
-indexinfo[0].command[15].indexname "my_elem_map.key"
-indexinfo[0].command[15].command "attribute"
-indexinfo[0].command[16].indexname "my_elem_map.key"
-indexinfo[0].command[16].command "fast-search"
-indexinfo[0].command[17].indexname "my_elem_map.value.name"
-indexinfo[0].command[17].command "index"
-indexinfo[0].command[18].indexname "my_elem_map.value.name"
-indexinfo[0].command[18].command "attribute"
-indexinfo[0].command[19].indexname "my_elem_map.value.name"
-indexinfo[0].command[19].command "fast-search"
-indexinfo[0].command[20].indexname "my_elem_map.value.weight"
-indexinfo[0].command[20].command "index"
-indexinfo[0].command[21].indexname "my_elem_map.value.weight"
-indexinfo[0].command[21].command "attribute"
-indexinfo[0].command[22].indexname "my_elem_map.value.weight"
-indexinfo[0].command[22].command "numerical"
-indexinfo[0].command[23].indexname "my_str_int_map.key"
-indexinfo[0].command[23].command "index"
-indexinfo[0].command[24].indexname "my_str_int_map.key"
-indexinfo[0].command[24].command "attribute"
-indexinfo[0].command[25].indexname "my_str_int_map.key"
-indexinfo[0].command[25].command "fast-search"
-indexinfo[0].command[26].indexname "my_str_int_map.value"
-indexinfo[0].command[26].command "index"
-indexinfo[0].command[27].indexname "my_str_int_map.value"
-indexinfo[0].command[27].command "attribute"
-indexinfo[0].command[28].indexname "my_str_int_map.value"
-indexinfo[0].command[28].command "numerical"
-indexinfo[0].command[29].indexname "my_elem_array"
-indexinfo[0].command[29].command "multivalue"
-indexinfo[0].command[30].indexname "my_elem_array"
-indexinfo[0].command[30].command "index"
-indexinfo[0].command[31].indexname "my_elem_map"
-indexinfo[0].command[31].command "multivalue"
-indexinfo[0].command[32].indexname "my_elem_map"
-indexinfo[0].command[32].command "index"
-indexinfo[0].command[33].indexname "my_str_int_map"
-indexinfo[0].command[33].command "multivalue"
-indexinfo[0].command[34].indexname "my_str_int_map"
-indexinfo[0].command[34].command "index" \ No newline at end of file
+indexinfo[].name "child"
+indexinfo[].command[].indexname "sddocname"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "sddocname"
+indexinfo[].command[].command "word"
+indexinfo[].command[].indexname "parent_ref"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "parent_ref"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "parent_ref"
+indexinfo[].command[].command "word"
+indexinfo[].command[].indexname "documentid"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "rankfeatures"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "summaryfeatures"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_array.name"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_array.name"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_elem_array.name"
+indexinfo[].command[].command "fast-search"
+indexinfo[].command[].indexname "my_elem_array.weight"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_array.weight"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_elem_array.weight"
+indexinfo[].command[].command "numerical"
+indexinfo[].command[].indexname "my_elem_array"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_array"
+indexinfo[].command[].command "multivalue"
+indexinfo[].command[].indexname "my_elem_map.value.name"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_map.value.name"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_elem_map.value.name"
+indexinfo[].command[].command "fast-search"
+indexinfo[].command[].indexname "my_elem_map.value.weight"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_map.value.weight"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_elem_map.value.weight"
+indexinfo[].command[].command "numerical"
+indexinfo[].command[].indexname "my_elem_map.value"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_map.key"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_map.key"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_elem_map.key"
+indexinfo[].command[].command "fast-search"
+indexinfo[].command[].indexname "my_elem_map"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_elem_map"
+indexinfo[].command[].command "multivalue"
+indexinfo[].command[].indexname "my_str_int_map.key"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_str_int_map.key"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_str_int_map.key"
+indexinfo[].command[].command "fast-search"
+indexinfo[].command[].indexname "my_str_int_map.value"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_str_int_map.value"
+indexinfo[].command[].command "attribute"
+indexinfo[].command[].indexname "my_str_int_map.value"
+indexinfo[].command[].command "numerical"
+indexinfo[].command[].indexname "my_str_int_map"
+indexinfo[].command[].command "index"
+indexinfo[].command[].indexname "my_str_int_map"
+indexinfo[].command[].command "multivalue"
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java
index 3735b997073..defaf565a8b 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java
@@ -8,6 +8,7 @@ import com.yahoo.searchdefinition.DocumentReference;
import com.yahoo.searchdefinition.Search;
import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.searchdefinition.document.ImportedFields;
+import com.yahoo.searchdefinition.document.ImportedSimpleField;
import com.yahoo.searchdefinition.document.SDDocumentType;
import com.yahoo.searchdefinition.document.SDField;
import com.yahoo.vespa.documentmodel.DocumentSummary;
@@ -53,8 +54,8 @@ public class AddAttributeTransformToSummaryOfImportedFieldsTest {
Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty());
SDField targetField = new SDField("target_field", DataType.INT);
DocumentReference documentReference = new DocumentReference(new Field("reference_field"), targetSearch);
- ImportedField importedField = new ImportedField(fieldName, documentReference, targetField);
- return new ImportedFields(Collections.singletonMap(fieldName, importedField), Collections.emptyMap());
+ ImportedField importedField = new ImportedSimpleField(fieldName, documentReference, targetField);
+ return new ImportedFields(Collections.singletonMap(fieldName, importedField));
}
private static DocumentSummary createDocumentSummary(String fieldName) {
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsTestCase.java
index 48b79dade1f..724c15c3ef4 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsTestCase.java
@@ -3,6 +3,7 @@ package com.yahoo.searchdefinition.processing;
import com.yahoo.searchdefinition.Search;
import com.yahoo.searchdefinition.SearchBuilder;
+import com.yahoo.searchdefinition.document.ImportedComplexField;
import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.searchdefinition.parser.ParseException;
import org.junit.Rule;
@@ -91,7 +92,7 @@ public class ImportedFieldsTestCase {
" import field parent_ref.elem_map as my_elem_map {}",
" import field parent_ref.str_int_map as my_str_int_map {}",
"}"));
- assertEquals(parentBuilder.countAttrs(), search.importedFields().get().fields().size());
+ assertEquals(3, search.importedFields().get().fields().size());
checkImportedField("my_elem_array.name", "parent_ref", "parent", "elem_array.name", search, parentBuilder.elem_array_name_attr);
checkImportedField("my_elem_array.weight", "parent_ref", "parent", "elem_array.weight", search, parentBuilder.elem_array_weight_attr);
checkImportedField("my_elem_map.key", "parent_ref", "parent", "elem_map.key", search, parentBuilder.elem_map_key_attr);
@@ -99,6 +100,9 @@ public class ImportedFieldsTestCase {
checkImportedField("my_elem_map.value.weight", "parent_ref", "parent", "elem_map.value.weight", search, parentBuilder.elem_map_value_weight_attr);
checkImportedField("my_str_int_map.key", "parent_ref", "parent", "str_int_map.key", search, parentBuilder.str_int_map_key_attr);
checkImportedField("my_str_int_map.value", "parent_ref", "parent", "str_int_map.value", search, parentBuilder.str_int_map_value_attr);
+ checkImportedField("my_elem_array", "parent_ref", "parent", "elem_array", search, true);
+ checkImportedField("my_elem_map", "parent_ref", "parent", "elem_map", search, true);
+ checkImportedField("my_str_int_map", "parent_ref", "parent", "str_int_map", search, true);
}
@Test
@@ -275,8 +279,9 @@ public class ImportedFieldsTestCase {
private static void checkPosImport(ParentPosSdBuilder parentBuilder, ChildPosSdBuilder childBuilder) throws ParseException {
Search search = buildChildSearch(parentBuilder.build(), childBuilder.build());
- assertEquals(1, search.importedFields().get().fields().size());
+ assertEquals(2, search.importedFields().get().fields().size());
assertSearchContainsImportedField("my_pos_zcurve", "parent_ref", "parent", "pos_zcurve", search);
+ assertSearchContainsImportedField("my_pos", "parent_ref", "parent", "pos", search);
}
@Test
@@ -291,8 +296,22 @@ public class ImportedFieldsTestCase {
checkPosImport(new ParentPosSdBuilder(), new ChildPosSdBuilder().import_pos_zcurve_before(true));
}
+ private static ImportedField getImportedField(String name, Search search) {
+ if (name.contains(".")) {
+ assertNull(search.importedFields().get().fields().get(name));
+ String superFieldName = name.substring(0,name.indexOf("."));
+ String subFieldName = name.substring(name.indexOf(".")+1);
+ ImportedField superField = search.importedFields().get().fields().get(superFieldName);
+ if (superField != null && superField instanceof ImportedComplexField) {
+ return ((ImportedComplexField)superField).getNestedField(subFieldName);
+ }
+ return null;
+ }
+ return search.importedFields().get().fields().get(name);
+ }
+
private static void assertSearchNotContainsImportedField(String fieldName, Search search) {
- ImportedField importedField = search.importedFields().get().fields().get(fieldName);
+ ImportedField importedField = getImportedField(fieldName, search);
assertNull(importedField);
}
@@ -301,7 +320,7 @@ public class ImportedFieldsTestCase {
String referenceDocType,
String targetFieldName,
Search search) {
- ImportedField importedField = search.importedFields().get().fields().get(fieldName);
+ ImportedField importedField = getImportedField(fieldName, search);
assertNotNull(importedField);
assertEquals(fieldName, importedField.fieldName());
assertEquals(referenceFieldName, importedField.reference().referenceField().getName());
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java
index e4c23f407c8..cec313f98d8 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java
@@ -8,6 +8,7 @@ import com.yahoo.searchdefinition.DocumentReference;
import com.yahoo.searchdefinition.Search;
import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.searchdefinition.document.ImportedFields;
+import com.yahoo.searchdefinition.document.ImportedSimpleField;
import com.yahoo.searchdefinition.document.SDDocumentType;
import com.yahoo.searchdefinition.document.SDField;
import com.yahoo.vespa.documentmodel.DocumentSummary;
@@ -54,8 +55,8 @@ public class ValidateFieldTypesTest {
Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty());
SDField targetField = new SDField("target_field", dataType);
DocumentReference documentReference = new DocumentReference(new Field("reference_field"), targetSearch);
- ImportedField importedField = new ImportedField(fieldName, documentReference, targetField);
- return new ImportedFields(Collections.singletonMap(fieldName, importedField), Collections.emptyMap());
+ ImportedField importedField = new ImportedSimpleField(fieldName, documentReference, targetField);
+ return new ImportedFields(Collections.singletonMap(fieldName, importedField));
}
private static DocumentSummary createDocumentSummary(String fieldName, DataType dataType) {
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java
index 6390c9ca165..6274ec77e01 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java
@@ -89,6 +89,7 @@ public class ProxyServer implements Runnable {
this.configClient = createClient(clientUpdater, delayedResponses, source, timingValues, memoryCache, configClient);
this.fileDownloader = new FileDownloader(new JRTConnectionPool(source));
new FileDistributionRpcServer(supervisor, fileDownloader);
+ new UrlDownloadRpcServer(supervisor);
}
static ProxyServer createTestServer(ConfigSourceSet source) {
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/UrlDownloadRpcServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/UrlDownloadRpcServer.java
new file mode 100644
index 00000000000..d8688d5cc36
--- /dev/null
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/UrlDownloadRpcServer.java
@@ -0,0 +1,148 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.proxy;
+
+import com.yahoo.concurrent.DaemonThreadFactory;
+import com.yahoo.jrt.Method;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.log.LogLevel;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.defaults.Defaults;
+import net.jpountz.xxhash.XXHashFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+import static com.yahoo.vespa.config.UrlDownloader.DOES_NOT_EXIST;
+import static com.yahoo.vespa.config.UrlDownloader.HTTP_ERROR;
+import static com.yahoo.vespa.config.UrlDownloader.INTERNAL_ERROR;
+
+/**
+ * An RPC server that handles URL download requests.
+ *
+ * @author lesters
+ */
+public class UrlDownloadRpcServer {
+ private final static Logger log = Logger.getLogger(UrlDownloadRpcServer.class.getName());
+
+ private static final String CONTENTS_FILE_NAME = "contents";
+ private static final String LAST_MODFIED_FILE_NAME = "lastmodified";
+
+ private final File downloadBaseDir;
+ private final ExecutorService rpcDownloadExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),
+ new DaemonThreadFactory("Rpc download executor"));
+
+ public UrlDownloadRpcServer(Supervisor supervisor) {
+ supervisor.addMethod(new Method("url.waitFor", "s", "s", this, "download")
+ .methodDesc("get path to url download")
+ .paramDesc(0, "url", "url")
+ .returnDesc(0, "path", "path to file"));
+ downloadBaseDir = new File(Defaults.getDefaults().underVespaHome("var/db/vespa/download"));
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public final void download(Request req) {
+ req.detach();
+ rpcDownloadExecutor.execute(() -> downloadFile(req));
+ }
+
+ private void downloadFile(Request req) {
+ String url = req.parameters().get(0).asString();
+ File downloadDir = new File(this.downloadBaseDir, urlToDirName(url));
+
+ try {
+ URL website = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) website.openConnection();
+ setIfModifiedSince(connection, downloadDir); // don't download if we already have the file
+
+ if (connection.getResponseCode() == 200) {
+ log.log(LogLevel.INFO, "Downloading URL '" + url + "'");
+ downloadFile(req, connection, downloadDir);
+
+ } else if (connection.getResponseCode() == 304) {
+ log.log(LogLevel.INFO, "URL '" + url + "' already downloaded (server response: 304)");
+ req.returnValues().add(new StringValue(new File(downloadDir, CONTENTS_FILE_NAME).getAbsolutePath()));
+
+ } else {
+ log.log(LogLevel.ERROR, "Download of URL '" + url + "' got server response: " + connection.getResponseCode());
+ req.setError(HTTP_ERROR, String.valueOf(connection.getResponseCode()));
+ }
+
+ } catch (Throwable e) {
+ log.log(LogLevel.ERROR, "Download of URL '" + url + "' got exception: " + e.getMessage());
+ req.setError(INTERNAL_ERROR, "Download of URL '" + url + "' internal error: " + e.getMessage());
+ }
+ req.returnRequest();
+ }
+
+ private static void downloadFile(Request req, HttpURLConnection connection, File downloadDir) throws IOException {
+ long start = System.currentTimeMillis();
+ String url = connection.getURL().toString();
+ Files.createDirectories(downloadDir.toPath());
+ File contentsPath = new File(downloadDir, CONTENTS_FILE_NAME);
+ try (ReadableByteChannel rbc = Channels.newChannel(connection.getInputStream())) {
+ try (FileOutputStream fos = new FileOutputStream((contentsPath.getAbsolutePath()))) {
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+
+ if (contentsPath.exists() && contentsPath.length() > 0) {
+ writeLastModifiedTimestamp(downloadDir, connection.getLastModified());
+ req.returnValues().add(new StringValue(contentsPath.getAbsolutePath()));
+ log.log(LogLevel.DEBUG, () -> "URL '" + url + "' available at " + contentsPath);
+ } else {
+ log.log(LogLevel.ERROR, "Downloaded URL '" + url + "' not found, returning error");
+ req.setError(DOES_NOT_EXIST, "Downloaded '" + url + "' not found");
+ }
+ }
+ }
+ long end = System.currentTimeMillis();
+ log.log(LogLevel.INFO, String.format("Download of URL '%s' done in %.3f seconds", url, (end-start) / 1000.0));
+ }
+
+ private static String urlToDirName(String uri) {
+ return String.valueOf(XXHashFactory.nativeInstance().hash64().hash(ByteBuffer.wrap(Utf8.toBytes(uri)), 0));
+ }
+
+ private static void setIfModifiedSince(HttpURLConnection connection, File downloadDir) throws IOException {
+ File contents = new File(downloadDir, CONTENTS_FILE_NAME);
+ if (contents.exists() && contents.length() > 0) {
+ long lastModified = readLastModifiedTimestamp(downloadDir);
+ if (lastModified > 0) {
+ connection.setIfModifiedSince(lastModified);
+ }
+ }
+ }
+
+ private static long readLastModifiedTimestamp(File downloadDir) throws IOException {
+ File lastModified = new File(downloadDir, LAST_MODFIED_FILE_NAME);
+ if (lastModified.exists() && lastModified.length() > 0) {
+ try (BufferedReader br = new BufferedReader(new FileReader(lastModified))) {
+ String timestamp = br.readLine();
+ return Long.parseLong(timestamp);
+ }
+ }
+ return 0;
+ }
+
+ private static void writeLastModifiedTimestamp(File downloadDir, long timestamp) throws IOException {
+ File lastModified = new File(downloadDir, LAST_MODFIED_FILE_NAME);
+ try (BufferedWriter lastModifiedWriter = new BufferedWriter(new FileWriter(lastModified.getAbsolutePath()))) {
+ lastModifiedWriter.write(Long.toString(timestamp));
+ }
+ }
+
+}
diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java
index 8a12405d505..40d79afc854 100644
--- a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java
+++ b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java
@@ -20,7 +20,11 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.nio.file.Path;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
import java.util.logging.Logger;
/**
@@ -36,15 +40,17 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
private final ConfigInstance.Builder rootBuilder;
private final ConfigTransformer.PathAcquirer pathAcquirer;
+ private final UrlDownloader urlDownloader;
private final Stack<NamedBuilder> stack = new Stack<>();
public ConfigPayloadApplier(T builder) {
- this(builder, new IdentityPathAcquirer());
+ this(builder, new IdentityPathAcquirer(), null);
}
- public ConfigPayloadApplier(T builder, ConfigTransformer.PathAcquirer pathAcquirer) {
+ public ConfigPayloadApplier(T builder, ConfigTransformer.PathAcquirer pathAcquirer, UrlDownloader urlDownloader) {
this.rootBuilder = builder;
this.pathAcquirer = pathAcquirer;
+ this.urlDownloader = urlDownloader;
debug("rootBuilder=" + rootBuilder);
}
@@ -207,6 +213,12 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
if (isPathField(builder, methodName)) {
FileReference wrappedPath = resolvePath((String)value);
invokeSetter(builder, methodName, key, wrappedPath);
+
+ // Need to convert url into actual file if 'url' type is used
+ } else if (isUrlField(builder, methodName)) {
+ UrlReference url = resolveUrl((String)value);
+ invokeSetter(builder, methodName, key, url);
+
} else {
invokeSetter(builder, methodName, key, value);
}
@@ -258,7 +270,8 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
// Need to convert url into actual file if 'url' type is used
} else if (isUrlField(builder, methodName)) {
- throw new UnsupportedOperationException("'url' type is not yet implemented");
+ UrlReference url = resolveUrl(Utf8.toString(value.asUtf8()));
+ invokeSetter(builder, methodName, url);
} else {
Object object = getValueFromInspector(value);
@@ -276,6 +289,14 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
return newFileReference(path.toString());
}
+ private UrlReference resolveUrl(String url) {
+ if (urlDownloader == null || !urlDownloader.isValid()) {
+ throw new RuntimeException("Resolving url field failed due to missing or invalid URL downloader.");
+ }
+ File file = urlDownloader.waitFor(new UrlReference(url), 60 * 60);
+ return new UrlReference(file.getAbsolutePath());
+ }
+
private FileReference newFileReference(String fileReference) {
try {
Constructor<FileReference> constructor = FileReference.class.getDeclaredConstructor(String.class);
@@ -343,18 +364,19 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
* Checks whether or not this field is of type 'path', in which
* case some special handling might be needed. Caches the result.
*/
+ private Set<String> pathFieldSet = new HashSet<>();
private boolean isPathField(Object builder, String methodName) {
// Paths are stored as FileReference in Builder.
- return isFieldType(builder, methodName, FileReference.class);
+ return isFieldType(pathFieldSet, builder, methodName, FileReference.class);
}
+ private Set<String> urlFieldSet = new HashSet<>();
private boolean isUrlField(Object builder, String methodName) {
// Urls are stored as UrlReference in Builder.
- return isFieldType(builder, methodName, UrlReference.class);
+ return isFieldType(urlFieldSet, builder, methodName, UrlReference.class);
}
- private Set<String> fieldSet = new HashSet<>();
- private boolean isFieldType(Object builder, String methodName, java.lang.reflect.Type type) {
+ private boolean isFieldType(Set<String> fieldSet, Object builder, String methodName, java.lang.reflect.Type type) {
String key = fieldKey(builder, methodName);
if (fieldSet.contains(key)) {
return true;
@@ -515,4 +537,5 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> {
return new File(fileReference.value()).toPath();
}
}
+
}
diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java b/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java
index ac3f490182a..163e010cdd6 100644
--- a/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java
+++ b/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java
@@ -26,6 +26,7 @@ public class ConfigTransformer<T extends ConfigInstance> {
private final Class<T> clazz;
private static volatile PathAcquirer pathAcquirer = new IdentityPathAcquirer();
+ private static volatile UrlDownloader urlDownloader;
/**
* For internal use only *
@@ -36,6 +37,10 @@ public class ConfigTransformer<T extends ConfigInstance> {
pathAcquirer;
}
+ public static void setUrlDownloader(UrlDownloader urlDownloader) {
+ ConfigTransformer.urlDownloader = urlDownloader;
+ }
+
/**
* Create a transformer capable of converting payloads to clazz
*
@@ -53,7 +58,7 @@ public class ConfigTransformer<T extends ConfigInstance> {
*/
public ConfigInstance.Builder toConfigBuilder(ConfigPayload payload) {
ConfigInstance.Builder builder = getRootBuilder();
- ConfigPayloadApplier<?> creator = new ConfigPayloadApplier<>(builder, pathAcquirer);
+ ConfigPayloadApplier<?> creator = new ConfigPayloadApplier<>(builder, pathAcquirer, urlDownloader);
creator.applyPayload(payload);
return builder;
}
diff --git a/config/src/main/java/com/yahoo/vespa/config/UrlDownloader.java b/config/src/main/java/com/yahoo/vespa/config/UrlDownloader.java
new file mode 100644
index 00000000000..4947b618f50
--- /dev/null
+++ b/config/src/main/java/com/yahoo/vespa/config/UrlDownloader.java
@@ -0,0 +1,98 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.UrlReference;
+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 com.yahoo.vespa.defaults.Defaults;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+/**
+ * @author lesters
+ */
+public class UrlDownloader {
+
+ private static final Logger log = Logger.getLogger(UrlDownloader.class.getName());
+
+ private static final int BASE_ERROR_CODE = 0x10000;
+ public static final int DOES_NOT_EXIST = BASE_ERROR_CODE + 1;
+ public static final int INTERNAL_ERROR = BASE_ERROR_CODE + 2;
+ public static final int HTTP_ERROR = BASE_ERROR_CODE + 3;
+
+ private final Supervisor supervisor = new Supervisor(new Transport());
+ private final Spec spec;
+ private Target target;
+
+ public UrlDownloader() {
+ spec = new Spec(Defaults.getDefaults().vespaHostname(), Defaults.getDefaults().vespaConfigProxyRpcPort());
+ connect();
+ }
+
+ public void shutdown() {
+ supervisor.transport().shutdown().join();
+ }
+
+ private void connect() {
+ int timeRemaining = 5000;
+ try {
+ while (timeRemaining > 0) {
+ target = supervisor.connectSync(spec);
+ if (target.isValid()) {
+ log.log(LogLevel.DEBUG, "Successfully connected to '" + spec + "', this = " + System.identityHashCode(this));
+ return;
+ }
+ Thread.sleep(500);
+ timeRemaining -= 500;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean isValid() {
+ return target.isValid();
+ }
+
+ private boolean temporaryError(Request req) {
+ return false; // Currently, none of the errors are considered temporary
+ }
+
+ public File waitFor(UrlReference urlReference, long timeout) {
+ long start = System.currentTimeMillis() / 1000;
+ long timeLeft = timeout;
+ do {
+ Request request = new Request("url.waitFor");
+ request.parameters().add(new StringValue(urlReference.value()));
+
+ double rpcTimeout = Math.min(timeLeft, 60 * 60.0);
+ log.log(LogLevel.DEBUG, "InvokeSync waitFor " + urlReference + " with " + rpcTimeout + " seconds timeout");
+ target.invokeSync(request, rpcTimeout);
+
+ if (request.checkReturnTypes("s")) {
+ return new File(request.returnValues().get(0).asString());
+ } else if (!request.isError()) {
+ throw new RuntimeException("Invalid response: " + request.returnValues());
+ } else if (temporaryError(request)) {
+ log.log(LogLevel.INFO, "Retrying waitFor for " + urlReference + ": " + request.errorCode() + " -- " + request.errorMessage());
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted sleep between retries of waitFor", e);
+ }
+ } else {
+ throw new RuntimeException("Wait for " + urlReference + " failed: " + request.errorMessage() + " (" + request.errorCode() + ")");
+ }
+ timeLeft = start + timeout - System.currentTimeMillis() / 1000;
+ } while (timeLeft > 0);
+
+ throw new RuntimeException("Timed out waiting for " + urlReference + " after " + timeout);
+ }
+
+}
diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java
index 9768c42b477..d452d8a4aad 100644
--- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.configserver.flags;
import com.google.inject.Inject;
-import com.yahoo.vespa.configserver.flags.db.FlagsDbImpl;
import com.yahoo.vespa.configserver.flags.db.ZooKeeperFlagSource;
import com.yahoo.vespa.flags.FileFlagSource;
import com.yahoo.vespa.flags.OrderedFlagSource;
@@ -12,7 +11,7 @@ import com.yahoo.vespa.flags.OrderedFlagSource;
*/
public class ConfigServerFlagSource extends OrderedFlagSource {
@Inject
- public ConfigServerFlagSource(FlagsDbImpl flagsDb) {
+ public ConfigServerFlagSource(FlagsDb flagsDb) {
super(new FileFlagSource(), new ZooKeeperFlagSource(flagsDb));
}
}
diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java
index bd99ac6eca9..4a9d604b4bd 100644
--- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java
@@ -1,6 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.configserver.flags.db;
+import com.yahoo.vespa.configserver.flags.FlagsDb;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.FlagSource;
@@ -12,9 +13,9 @@ import java.util.Optional;
* @author hakonhall
*/
public class ZooKeeperFlagSource implements FlagSource {
- private final FlagsDbImpl flagsDb;
+ private final FlagsDb flagsDb;
- public ZooKeeperFlagSource(FlagsDbImpl flagsDb) {
+ public ZooKeeperFlagSource(FlagsDb flagsDb) {
this.flagsDb = flagsDb;
}
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 2a8abacd24e..f710eec27ba 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
@@ -23,6 +23,7 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.logging.Logger;
public class FileDirectory {
+
private static final Logger log = Logger.getLogger(FileDirectory.class.getName());
private final File root;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java
index ae72660ce9f..b33fc7c2b04 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java
@@ -9,6 +9,7 @@ import com.yahoo.jdisc.Response;
import com.yahoo.vespa.config.server.http.HttpConfigResponse;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.json.FlagData;
+import com.yahoo.vespa.flags.json.wire.WireFlagDataList;
import java.io.OutputStream;
import java.util.Map;
@@ -23,31 +24,32 @@ public class FlagDataListResponse extends HttpResponse {
private static ObjectMapper mapper = new ObjectMapper();
private final String flagsV1Uri;
- private final Map<FlagId, FlagData> flags;
+ private final TreeMap<FlagId, FlagData> flags;
private final boolean recursive;
public FlagDataListResponse(String flagsV1Uri, Map<FlagId, FlagData> flags, boolean recursive) {
super(Response.Status.OK);
this.flagsV1Uri = flagsV1Uri;
- this.flags = flags;
+ this.flags = new TreeMap<>(flags);
this.recursive = recursive;
}
@Override
public void render(OutputStream outputStream) {
- ObjectNode rootNode = mapper.createObjectNode();
- ArrayNode flagsArray = rootNode.putArray("flags");
- // Order flags by ID
- new TreeMap<>(this.flags).forEach((flagId, flagData) -> {
- if (recursive) {
- flagsArray.add(flagData.toJsonNode());
- } else {
+ if (recursive) {
+ WireFlagDataList list = new WireFlagDataList();
+ flags.values().forEach(flagData -> list.flags.add(flagData.toWire()));
+ list.serializeToOutputStream(outputStream);
+ } else {
+ ObjectNode rootNode = mapper.createObjectNode();
+ ArrayNode flagsArray = rootNode.putArray("flags");
+ flags.forEach((flagId, flagData) -> {
ObjectNode object = flagsArray.addObject();
object.put("id", flagId.toString());
object.put("url", flagsV1Uri + "/data/" + flagId.toString());
- }
- });
- uncheck(() -> mapper.writeValue(outputStream, rootNode));
+ });
+ uncheck(() -> mapper.writeValue(outputStream, rootNode));
+ }
}
@Override
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
index e5efc74fb75..1b1595eb897 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
@@ -64,7 +64,8 @@ public class FlagsHandler extends HttpHandler {
private String flagsV1Uri(HttpRequest request) {
URI uri = request.getUri();
- return uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort() + "/flags/v1";
+ String port = uri.getPort() < 0 ? "" : ":" + uri.getPort();
+ return uri.getScheme() + "://" + uri.getHost() + port + "/flags/v1";
}
private HttpResponse getFlagDataList(HttpRequest request) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/Metrics.java b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/Metrics.java
index 5813c3eb04a..87e9fa287a4 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/Metrics.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/Metrics.java
@@ -48,8 +48,8 @@ public class Metrics extends TimerTask implements MetricUpdaterFactory {
log.log(LogLevel.DEBUG, "Metric update interval is " + healthMonitorConfig.snapshot_interval() + " seconds");
long intervalMs = (long) (healthMonitorConfig.snapshot_interval() * 1000);
- timer.scheduleAtFixedRate(this, 5000, intervalMs);
- zkMetricUpdater = new ZKMetricUpdater(zkServerConfig, 4500, intervalMs);
+ timer.scheduleAtFixedRate(this, 20000, intervalMs);
+ zkMetricUpdater = new ZKMetricUpdater(zkServerConfig, 19500, intervalMs);
}
public static Metrics createTestMetrics() {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java
index d61f8a7b9fe..408bf44e733 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java
@@ -43,10 +43,10 @@ public class ZKMetricUpdater extends TimerTask {
private final Timer timer = new Timer();
private final int zkPort;
- public ZKMetricUpdater(ZookeeperServerConfig zkServerConfig, long delay, long interval) {
+ public ZKMetricUpdater(ZookeeperServerConfig zkServerConfig, long delayMS, long intervalMS) {
this.zkPort = zkServerConfig.clientPort();
- if (interval > 0) {
- timer.scheduleAtFixedRate(this, delay, interval);
+ if (intervalMS > 0) {
+ timer.scheduleAtFixedRate(this, delayMS, intervalMS);
}
}
diff --git a/configserver/src/main/resources/logd/logd.cfg b/configserver/src/main/resources/logd/logd.cfg
index 137f3742ef2..a422a185dd5 100644
--- a/configserver/src/main/resources/logd/logd.cfg
+++ b/configserver/src/main/resources/logd/logd.cfg
@@ -1,3 +1,4 @@
+stateport 19089
logserver.use false
loglevel.fatal.forward false
loglevel.error.forward false
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
index 71caed77dbe..11958454a70 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
@@ -89,6 +89,10 @@ public class FlagsHandlerTest {
verifySuccessfulRequest(Method.GET, "/data/",
"", "{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com:4443/flags/v1/data/id1\"}]}");
+ // Verify absent port => absent in response
+ assertThat(handleWithPort(Method.GET, -1, "/data", "", 200),
+ is("{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com/flags/v1/data/id1\"}]}"));
+
// PUT id2
verifySuccessfulRequest(Method.PUT, "/data/" + FLAG2.id(),
"{\n" +
@@ -175,7 +179,11 @@ public class FlagsHandlerTest {
}
private String handle(Method method, String pathSuffix, String requestBody, int expectedStatus) {
- String uri = FLAGS_V1_URL + pathSuffix;
+ return handleWithPort(method, 4443, pathSuffix, requestBody, expectedStatus);
+ }
+
+ private String handleWithPort(Method method, int port, String pathSuffix, String requestBody, int expectedStatus) {
+ String uri = "https://foo.com" + (port < 0 ? "" : ":" + port) + "/flags/v1" + pathSuffix;
HttpRequest request = HttpRequest.createTestRequest(uri, method, makeInputStream(requestBody));
HttpResponse response = handler.handle(request);
assertEquals(expectedStatus, response.getStatus());
diff --git a/container-core/src/main/java/com/yahoo/container/Container.java b/container-core/src/main/java/com/yahoo/container/Container.java
index bb4b57e8983..e84c8b340a4 100755
--- a/container-core/src/main/java/com/yahoo/container/Container.java
+++ b/container-core/src/main/java/com/yahoo/container/Container.java
@@ -11,6 +11,7 @@ import com.yahoo.jdisc.service.ClientProvider;
import com.yahoo.jdisc.service.ServerProvider;
import com.yahoo.osgi.Osgi;
import com.yahoo.vespa.config.ConfigTransformer;
+import com.yahoo.vespa.config.UrlDownloader;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -31,6 +32,7 @@ public class Container {
private volatile ComponentRegistry<ServerProvider> serverProviderRegistry;
private volatile ComponentRegistry<AbstractComponent> componentRegistry;
private volatile FileAcquirer fileAcquirer;
+ private volatile UrlDownloader urlDownloader;
private volatile BundleLoader bundleLoader;
@@ -50,6 +52,8 @@ public class Container {
public void shutdown() {
if (fileAcquirer != null)
fileAcquirer.shutdown();
+ if (urlDownloader != null)
+ urlDownloader.shutdown();
}
//Used to acquire files originating from the application package.
@@ -147,4 +151,9 @@ public class Container {
});
}
+ public void setupUrlDownloader() {
+ this.urlDownloader = new UrlDownloader();
+ ConfigTransformer.setUrlDownloader(urlDownloader);
+ }
+
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
index 557f331395b..0a97a4d5d2f 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
@@ -177,7 +177,7 @@ public class BundleLoader {
private String installedBundlesMessage() {
StringBuilder sb = new StringBuilder("Installed bundles: {" );
for (Bundle b : osgi.getBundles())
- sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ", ");
+ sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
sb.setLength(sb.length() - 2);
sb.append("}");
return sb.toString();
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
index f4fae683877..1cb4e1d4555 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
@@ -191,6 +191,7 @@ public final class ConfiguredApplication implements Application {
private static void hackToInitializeServer(QrConfig config) {
try {
Container.get().setupFileAcquirer(config.filedistributor());
+ Container.get().setupUrlDownloader();
com.yahoo.container.Server.get().initialize(config);
} catch (Exception e) {
log.log(LogLevel.ERROR, "Caught exception when initializing server. Exiting.", e);
diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json
index 7e5fc873803..93a7320f8d1 100644
--- a/container-search/abi-spec.json
+++ b/container-search/abi-spec.json
@@ -2095,6 +2095,7 @@
"fields": [
"public static final com.yahoo.processing.request.CompoundName OFFSET",
"public static final com.yahoo.processing.request.CompoundName HITS",
+ "public static final com.yahoo.processing.request.CompoundName QUERY_PROFILE",
"public static final com.yahoo.processing.request.CompoundName SEARCH_CHAIN",
"public static final com.yahoo.processing.request.CompoundName TRACE_LEVEL",
"public static final com.yahoo.processing.request.CompoundName NO_CACHE",
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
index 131d4fcc9da..b87de5f019a 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
@@ -49,6 +49,7 @@ public class WeightedSetItem extends SimpleTaggableItem {
* Add weighted token.
* If token is already in the set, the maximum weight is kept.
* NOTE: The weight must be 1 or more; negative values (and zero) are not allowed.
+ *
* @return weight of added token (might be old value, if kept)
*/
public Integer addToken(String token, int weight) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
index 652e1ca60b8..1157d2763e0 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
@@ -15,7 +15,7 @@ import com.yahoo.compress.IntegerCompressor;
* A set words with differing exactness scores to be used for literal boost
* ranking.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
public class WordAlternativesItem extends TermItem {
@@ -23,6 +23,7 @@ public class WordAlternativesItem extends TermItem {
private int maxIndex;
public static final class Alternative {
+
public final String word;
public final double exactness;
@@ -38,6 +39,7 @@ public class WordAlternativesItem extends TermItem {
builder.append("Alternative [word=").append(word).append(", exactness=").append(exactness).append("]");
return builder.toString();
}
+
}
public WordAlternativesItem(String indexName, boolean isFromQuery, Substring origin, Collection<Alternative> terms) {
@@ -51,7 +53,7 @@ public class WordAlternativesItem extends TermItem {
}
private static ImmutableList<Alternative> uniqueAlternatives(Collection<Alternative> terms) {
- List<Alternative> uniqueTerms = new ArrayList<Alternative>(terms.size());
+ List<Alternative> uniqueTerms = new ArrayList<>(terms.size());
for (Alternative term : terms) {
int i = Collections.binarySearch(uniqueTerms, term, (t0, t1) -> t0.word.compareTo(t1.word));
if (i >= 0) {
@@ -104,7 +106,7 @@ public class WordAlternativesItem extends TermItem {
@Override
public void setValue(String value) {
- throw new UnsupportedOperationException("semantics for setting to a string would be brittle, use setAlternatives()");
+ throw new UnsupportedOperationException("Semantics for setting to a string would be brittle, use setAlternatives()");
}
@Override
@@ -180,4 +182,5 @@ public class WordAlternativesItem extends TermItem {
newTerms.add(new Alternative(term, exactness));
setAlternatives(newTerms);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/Discloser.java b/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/Discloser.java
index 1d7372a2497..cbe1c0f8ab9 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/Discloser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/Discloser.java
@@ -9,9 +9,11 @@ import com.yahoo.prelude.query.Item;
* @author Tony Vaagenes
*/
public interface Discloser {
+
void addProperty(String key, Object value);
//A given item should either call setValue or addChild, not both.
void setValue(Object value);
void addChild(Item item);
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/TextualQueryRepresentation.java b/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/TextualQueryRepresentation.java
index 56f106a43f4..e299ccb5674 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/TextualQueryRepresentation.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/textualrepresentation/TextualQueryRepresentation.java
@@ -8,11 +8,12 @@ import java.util.*;
import java.util.regex.Pattern;
/**
- * Creates a detailed, QED inspired representation of a query tree.
+ * Creates a detailed representation of a query tree.
*
* @author Tony Vaagenes
*/
public class TextualQueryRepresentation {
+
private Map<Item, Integer> itemReferences = new IdentityHashMap<>();
private int nextItemReference = 0;
@@ -207,4 +208,5 @@ public class TextualQueryRepresentation {
public String toString() {
return rootDiscloser.toString();
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/Query.java b/container-search/src/main/java/com/yahoo/search/Query.java
index 8e5e14a3aac..bf0920a2aa5 100644
--- a/container-search/src/main/java/com/yahoo/search/Query.java
+++ b/container-search/src/main/java/com/yahoo/search/Query.java
@@ -107,14 +107,14 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
private final int intValue;
private final String stringValue;
- Type(int intValue,String stringValue) {
+ Type(int intValue, String stringValue) {
this.intValue = intValue;
this.stringValue = stringValue;
}
/** Converts a type argument value into a query type */
public static Type getType(String typeString) {
- for (Type type:Type.values())
+ for (Type type : Type.values())
if (type.stringValue.equals(typeString))
return type;
return ALL;
@@ -187,6 +187,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
public static final CompoundName OFFSET = new CompoundName("offset");
public static final CompoundName HITS = new CompoundName("hits");
+ public static final CompoundName QUERY_PROFILE = new CompoundName("queryProfile");
public static final CompoundName SEARCH_CHAIN = new CompoundName("searchChain");
public static final CompoundName TRACE_LEVEL = new CompoundName("traceLevel");
public static final CompoundName NO_CACHE = new CompoundName("noCache");
@@ -202,6 +203,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
argumentType.addField(new FieldDescription(OFFSET.toString(), "integer", "offset start"));
argumentType.addField(new FieldDescription(HITS.toString(), "integer", "hits count"));
// TODO: Should this be added to com.yahoo.search.query.properties.QueryProperties? If not, why not?
+ argumentType.addField(new FieldDescription(QUERY_PROFILE.toString(), "string"));
argumentType.addField(new FieldDescription(SEARCH_CHAIN.toString(), "string"));
argumentType.addField(new FieldDescription(TRACE_LEVEL.toString(), "integer", "tracelevel"));
argumentType.addField(new FieldDescription(NO_CACHE.toString(), "boolean", "nocache"));
@@ -329,8 +331,6 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
init(requestMap, queryProfile);
}
-
-
private void init(Map<String, String> requestMap, CompiledQueryProfile queryProfile) {
startTime = System.currentTimeMillis();
if (queryProfile != null) {
@@ -411,10 +411,10 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
}
}
} else {
- Object value=originalProperties.get(fullName,context);
- if (value!=null) {
+ Object value = originalProperties.get(fullName,context);
+ if (value != null) {
try {
- properties().set(fullName,value,context);
+ properties().set(fullName, value, context);
} catch (IllegalArgumentException e) {
throw new QueryException("Invalid request parameter", e);
}
@@ -427,7 +427,6 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
private void setPropertiesFromRequestMap(Map<String, String> requestMap, Properties properties) {
for (Map.Entry<String, String> entry : requestMap.entrySet()) {
try {
- if (entry.getKey().equals("queryProfile")) continue;
properties.set(entry.getKey(), entry.getValue(), requestMap);
}
catch (IllegalArgumentException e) {
@@ -446,12 +445,12 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
*/
private void traceProperties() {
if (traceLevel == 0) return;
- CompiledQueryProfile profile=null;
- QueryProfileProperties profileProperties=properties().getInstance(QueryProfileProperties.class);
- if (profileProperties!=null)
- profile=profileProperties.getQueryProfile();
+ CompiledQueryProfile profile = null;
+ QueryProfileProperties profileProperties = properties().getInstance(QueryProfileProperties.class);
+ if (profileProperties != null)
+ profile = profileProperties.getQueryProfile();
- if (profile==null)
+ if (profile == null)
trace("No query profile is used", false, 1);
else
trace("Using " + profile.toString(), false, 1);
@@ -466,7 +465,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
b.append(requestProperty.getKey());
b.append("=");
- b.append(String.valueOf(resolvedValue)); // (may be null)
+ b.append(resolvedValue); // (may be null)
b.append(" (");
if (profile != null && ! profile.isOverridable(new CompoundName(requestProperty.getKey()), requestProperties()))
@@ -476,8 +475,8 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
b.append(")\n");
mentioned.add(requestProperty.getKey());
}
- if (profile!=null) {
- appendQueryProfileProperties(profile,mentioned,b);
+ if (profile != null) {
+ appendQueryProfileProperties(profile, mentioned, b);
}
trace(b.toString(),false,4);
}
@@ -487,7 +486,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
}
private void appendQueryProfileProperties(CompiledQueryProfile profile,Set<String> mentioned,StringBuilder b) {
- for (Map.Entry<String,Object> property : profile.listValues("",requestProperties()).entrySet()) {
+ for (Map.Entry<String,Object> property : profile.listValues("", requestProperties()).entrySet()) {
if ( ! mentioned.contains(property.getKey()))
b.append(property.getKey() + "=" + property.getValue() + " (value from query profile)<br/>\n");
}
diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
index fc1da407ae7..6f965944bdf 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
@@ -579,7 +579,8 @@ public class SearchHandler extends LoggingRequestHandler {
byte[] byteArray = IOUtils.readBytes(request.getData(), 1 << 20);
inspector = SlimeUtils.jsonToSlime(byteArray).get();
if (inspector.field("error_message").valid()){
- throw new QueryException("Illegal query: "+inspector.field("error_message").asString() + ", at: "+ new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8));
+ throw new QueryException("Illegal query: " + inspector.field("error_message").asString() + ", at: " +
+ new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8));
}
} catch (IOException e) {
@@ -631,7 +632,7 @@ public class SearchHandler extends LoggingRequestHandler {
map.put(qualifiedKey, value.asString());
break;
case OBJECT:
- if (qualifiedKey.equals("select.where") || qualifiedKey.equals("select.grouping")){
+ if (qualifiedKey.equals("select.where") || qualifiedKey.equals("select.grouping")) {
map.put(qualifiedKey, value.toString());
break;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Model.java b/container-search/src/main/java/com/yahoo/search/query/Model.java
index 4baa651fa01..fd52618ad85 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Model.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Model.java
@@ -7,7 +7,6 @@ import com.yahoo.language.LocaleFactory;
import com.yahoo.prelude.query.CompositeItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.TaggableItem;
-import com.yahoo.prelude.query.textualrepresentation.TextualQueryRepresentation;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.query.parser.Parsable;
@@ -18,7 +17,13 @@ import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
import com.yahoo.search.searchchain.Execution;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
import static com.yahoo.text.Lowercase.toLowerCase;
@@ -169,9 +174,9 @@ public class Model implements Cloneable {
public void setLanguage(Language language) { this.language = language; }
/**
- * <p>Explicitly sets the language to be used during parsing. The argument is first normalized by replacing
+ * Explicitly sets the language to be used during parsing. The argument is first normalized by replacing
* underscores with hyphens (to support locale strings being used as RFC 5646 language tags), and then forwarded to
- * {@link #setLocale(String)} so that the Locale information of the tag is preserved.</p>
+ * {@link #setLocale(String)} so that the Locale information of the tag is preserved.
*
* @param language The language string to parse.
* @see #getLanguage()
@@ -182,9 +187,9 @@ public class Model implements Cloneable {
}
/**
- * <p>Returns the explicitly set parsing locale of this query model, or null if none.</p>
+ * Returns the explicitly set parsing locale of this query model, or null if none.
*
- * @return The locale of this.
+ * @return the locale of this
* @see #setLocale(Locale)
*/
public Locale getLocale() {
@@ -195,7 +200,7 @@ public class Model implements Cloneable {
* <p>Explicitly sets the locale to be used during parsing. This method also calls {@link #setLanguage(Language)}
* with the corresponding {@link Language} instance.</p>
*
- * @param locale The locale to set.
+ * @param locale the locale to set
* @see #getLocale()
* @see #setLanguage(Language)
*/
@@ -205,10 +210,10 @@ public class Model implements Cloneable {
}
/**
- * <p>Explicitly sets the locale to be used during parsing. This creates a Locale instance from the given language
- * tag, and passes that to {@link #setLocale(Locale)}.</p>
+ * Explicitly sets the locale to be used during parsing. This creates a Locale instance from the given language
+ * tag, and passes that to {@link #setLocale(Locale)}.
*
- * @param languageTag The language tag to parse.
+ * @param languageTag the language tag to parse
* @see #setLocale(Locale)
*/
public void setLocale(String languageTag) {
diff --git a/container-search/src/main/java/com/yahoo/search/query/Select.java b/container-search/src/main/java/com/yahoo/search/query/Select.java
index bbc152c6391..f2a534a014e 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Select.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Select.java
@@ -88,8 +88,7 @@ public class Select implements Cloneable {
}
/** Returns the where clause string previously assigned, or an empty string if none */
- public String getWhereString(){ return where; }
-
+ public String getWhereString() { return where; }
/**
* Sets the grouping operation of the query.
@@ -120,7 +119,6 @@ public class Select implements Cloneable {
*/
public List<GroupingRequest> getGrouping() { return groupingRequests; }
-
@Override
public String toString() {
return "where: [" + where + "], grouping: [" + grouping+ "]";
diff --git a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
index 13ebacb62ef..e4e44985b53 100644
--- a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
+++ b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
@@ -68,8 +68,6 @@ import static com.yahoo.slime.Type.STRING;
*
* @author henrhoi
*/
-
-
public class SelectParser implements Parser {
Parsable query;
@@ -77,13 +75,9 @@ public class SelectParser implements Parser {
private final Map<Integer, TaggableItem> identifiedItems = LazyMap.newHashMap();
private final List<ConnectedItem> connectedItems = new ArrayList<>();
private final Normalizer normalizer;
- private final ParserEnvironment environment;
private IndexFacts.Session indexFactsSession;
-
-
- /** YQL parameters and functions */
-
+ // YQL parameters and functions
private static final String DESCENDING_HITS_ORDER = "descending";
private static final String ASCENDING_HITS_ORDER = "ascending";
private static final Integer DEFAULT_TARGET_NUM_HITS = 10;
@@ -139,18 +133,11 @@ public class SelectParser implements Parser {
private static final String CALL = "call";
private static final List<String> FUNCTION_CALLS = Arrays.asList(WAND, WEIGHTED_SET, DOT_PRODUCT, PREDICATE, RANK, WEAK_AND);
- /**************************************/
-
-
-
public SelectParser(ParserEnvironment environment) {
indexFacts = environment.getIndexFacts();
normalizer = environment.getLinguistics().getNormalizer();
-
- this.environment = environment;
}
-
@Override
public QueryTree parse(Parsable query) {
indexFactsSession = indexFacts.newSession(query.getSources(), query.getRestrict());
@@ -161,8 +148,6 @@ public class SelectParser implements Parser {
return buildTree();
}
-
-
private QueryTree buildTree() {
Inspector inspector = SlimeUtils.jsonToSlime(this.query.getSelect().getWhereString().getBytes()).get();
if (inspector.field("error_message").valid()){
@@ -176,14 +161,12 @@ public class SelectParser implements Parser {
return newTree;
}
-
private Item walkJson(Inspector inspector){
- final Item[] item = {null};
+ Item[] item = {null};
inspector.traverse((ObjectTraverser) (key, value) -> {
String type = (FUNCTION_CALLS.contains(key)) ? CALL : key;
switch (type) {
-
case AND:
item[0] = buildAnd(key, value);
break;
@@ -215,7 +198,6 @@ public class SelectParser implements Parser {
return item[0];
}
-
public List<VespaGroupingStep> getGroupingSteps(String grouping){
List<VespaGroupingStep> groupingSteps = new ArrayList<>();
List<String> groupingOperations = getOperations(grouping);
@@ -242,10 +224,8 @@ public class SelectParser implements Parser {
});
return operations;
-
}
-
@NonNull
private Item buildFunctionCall(String key, Inspector value) {
switch (key) {
@@ -266,7 +246,6 @@ public class SelectParser implements Parser {
}
}
-
private void addItemsFromInspector(CompositeItem item, Inspector inspector){
if (inspector.type() == ARRAY){
inspector.traverse((ArrayTraverser) (index, new_value) -> {
@@ -283,7 +262,6 @@ public class SelectParser implements Parser {
}
}
-
private Inspector getChildren(Inspector inspector){
if (inspector.type() == ARRAY){
return inspector;
@@ -299,25 +277,23 @@ public class SelectParser implements Parser {
return null;
}
-
private HashMap<Integer, Inspector> getChildrenMap(Inspector inspector){
HashMap<Integer, Inspector> children = new HashMap<>();
- if (inspector.type() == ARRAY){
- inspector.traverse((ArrayTraverser) (index, new_value) -> {
+ if (inspector.type() == ARRAY){
+ inspector.traverse((ArrayTraverser) (index, new_value) -> {
+ children.put(index, new_value);
+ });
+
+ } else if (inspector.type() == OBJECT){
+ if (inspector.field("children").valid()){
+ inspector.field("children").traverse((ArrayTraverser) (index, new_value) -> {
children.put(index, new_value);
});
-
- } else if (inspector.type() == OBJECT){
- if (inspector.field("children").valid()){
- inspector.field("children").traverse((ArrayTraverser) (index, new_value) -> {
- children.put(index, new_value);
- });
- }
}
+ }
return children;
}
-
private Inspector getAnnotations(Inspector inspector){
if (inspector.type() == OBJECT && inspector.field("attributes").valid()){
return inspector.field("attributes");
@@ -325,7 +301,6 @@ public class SelectParser implements Parser {
return null;
}
-
private HashMap<String, Inspector> getAnnotationMapFromAnnotationInspector(Inspector annotation){
HashMap<String, Inspector> attributes = new HashMap<>();
if (annotation.type() == OBJECT){
@@ -336,7 +311,6 @@ public class SelectParser implements Parser {
return attributes;
}
-
private HashMap<String, Inspector> getAnnotationMap(Inspector inspector){
HashMap<String, Inspector> attributes = new HashMap<>();
if (inspector.type() == OBJECT && inspector.field("attributes").valid()){
@@ -347,12 +321,10 @@ public class SelectParser implements Parser {
return attributes;
}
-
private <T> T getAnnotation(String annotationName, HashMap<String, Inspector> annotations, Class<T> expectedClass, T defaultValue) {
return (annotations.get(annotationName) == null) ? defaultValue : expectedClass.cast(annotations.get(annotationName).asString());
}
-
private Boolean getBoolAnnotation(String annotationName, HashMap<String, Inspector> annotations, Boolean defaultValue) {
if (annotations != null){
Inspector annotation = annotations.getOrDefault(annotationName, null);
@@ -363,7 +335,6 @@ public class SelectParser implements Parser {
return defaultValue;
}
-
private Integer getIntegerAnnotation(String annotationName, HashMap<String, Inspector> annotations, Integer defaultValue) {
if (annotations != null){
Inspector annotation = annotations.getOrDefault(annotationName, null);
@@ -374,7 +345,6 @@ public class SelectParser implements Parser {
return defaultValue;
}
-
private Double getDoubleAnnotation(String annotationName, HashMap<String, Inspector> annotations, Double defaultValue) {
if (annotations != null){
Inspector annotation = annotations.getOrDefault(annotationName, null);
@@ -385,12 +355,10 @@ public class SelectParser implements Parser {
return defaultValue;
}
-
private Inspector getAnnotationAsInspectorOrNull(String annotationName, HashMap<String, Inspector> annotations) {
return annotations.get(annotationName);
}
-
@NonNull
private CompositeItem buildAnd(String key, Inspector value) {
AndItem andItem = new AndItem();
@@ -399,7 +367,6 @@ public class SelectParser implements Parser {
return andItem;
}
-
@NonNull
private CompositeItem buildNotAnd(String key, Inspector value) {
NotItem notItem = new NotItem();
@@ -408,7 +375,6 @@ public class SelectParser implements Parser {
return notItem;
}
-
@NonNull
private CompositeItem buildOr(String key, Inspector value) {
OrItem orItem = new OrItem();
@@ -416,7 +382,6 @@ public class SelectParser implements Parser {
return orItem;
}
-
@NonNull
private CompositeItem buildWeakAnd(String key, Inspector value) {
WeakAndItem weakAnd = new WeakAndItem();
@@ -437,7 +402,6 @@ public class SelectParser implements Parser {
return weakAnd;
}
-
@NonNull
private <T extends TaggableItem> T leafStyleSettings(Inspector annotations, @NonNull T out) {
{
@@ -521,9 +485,8 @@ public class SelectParser implements Parser {
return out;
}
-
private Integer getCappedRangeSearchParameter(Inspector annotations) {
- final Integer[] hitLimit = {null};
+ Integer[] hitLimit = {null};
annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
if (HIT_LIMIT.equals(annotation_name)) {
if (annotation_value != null) {
@@ -531,8 +494,8 @@ public class SelectParser implements Parser {
}
}
});
- final Boolean[] ascending = {null};
- final Boolean[] descending = {null};
+ Boolean[] ascending = {null};
+ Boolean[] descending = {null};
if (hitLimit[0] != null) {
annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
@@ -554,13 +517,12 @@ public class SelectParser implements Parser {
return hitLimit[0];
}
-
@NonNull
private Item buildRange(String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
Inspector annotations = getAnnotations(value);
- final boolean[] equals = {false};
+ boolean[] equals = {false};
String field;
Inspector boundInspector;
@@ -572,8 +534,8 @@ public class SelectParser implements Parser {
boundInspector = children.get(0);
}
- final Number[] bounds = {null, null};
- final String[] operators = {null, null};
+ Number[] bounds = {null, null};
+ String[] operators = {null, null};
boundInspector.traverse((ObjectTraverser) (operator, bound) -> {
if (bound.type() == STRING) {
throw new IllegalArgumentException("Expected operator LITERAL, got READ_FIELD.");
@@ -625,26 +587,22 @@ public class SelectParser implements Parser {
}
-
@NonNull
private IntItem buildLessThanOrEquals(String field, String bound) {
return new IntItem("[;" + bound + "]", field);
}
-
@NonNull
private IntItem buildGreaterThan(String field, String bound) {
return new IntItem(">" + bound, field);
}
-
@NonNull
private IntItem buildLessThan(String field, String bound) {
return new IntItem("<" + bound, field);
}
-
@NonNull
private IntItem instantiateRangeItem(Number lowerBound, Number upperBound, String field, boolean bounds_left_open, boolean bounds_right_open) {
Preconditions.checkArgument(lowerBound != null && upperBound != null && field != null,
@@ -675,7 +633,6 @@ public class SelectParser implements Parser {
return buildRange(key, value);
}
-
@NonNull
private Item buildWand(String key, Inspector value) {
HashMap<String, Inspector> annotations = getAnnotationMap(value);
@@ -699,7 +656,6 @@ public class SelectParser implements Parser {
return fillWeightedSet(value, children, out);
}
-
@NonNull
private WeightedSetItem fillWeightedSet(Inspector value, HashMap<Integer, Inspector> children, @NonNull WeightedSetItem out) {
addItems(children, out);
@@ -721,7 +677,6 @@ public class SelectParser implements Parser {
}
}
-
private static void addStringItems(HashMap<Integer, Inspector> children, WeightedSetItem out) {
//{"a":1, "b":2}
children.get(1).traverse((ObjectTraverser) (key, value) -> {
@@ -732,9 +687,7 @@ public class SelectParser implements Parser {
});
}
-
private static void addLongItems(HashMap<Integer, Inspector> children, WeightedSetItem out) {
- //[[11,1], [37,2]]
children.get(1).traverse((ArrayTraverser) (index, pair) -> {
List<Integer> pairValues = new ArrayList<>();
pair.traverse((ArrayTraverser) (pairIndex, pairValue) -> {
@@ -746,7 +699,6 @@ public class SelectParser implements Parser {
});
}
-
@NonNull
private Item buildRegExpSearch(String key, Inspector value) {
assertHasOperator(key, MATCHES);
@@ -757,7 +709,6 @@ public class SelectParser implements Parser {
return leafStyleSettings(getAnnotations(value), regExp);
}
-
@NonNull
private Item buildWeightedSet(String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
@@ -766,7 +717,6 @@ public class SelectParser implements Parser {
return fillWeightedSet(value, children, new WeightedSetItem(field));
}
-
@NonNull
private Item buildDotProduct(String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
@@ -775,7 +725,6 @@ public class SelectParser implements Parser {
return fillWeightedSet(value, children, new DotProductItem(field));
}
-
@NonNull
private Item buildPredicate(String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
@@ -805,7 +754,6 @@ public class SelectParser implements Parser {
return leafStyleSettings(getAnnotations(value), item);
}
-
@NonNull
private CompositeItem buildRank(String key, Inspector value) {
RankItem rankItem = new RankItem();
@@ -813,7 +761,6 @@ public class SelectParser implements Parser {
return rankItem;
}
-
@NonNull
private Item buildTermSearch(String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
@@ -822,7 +769,6 @@ public class SelectParser implements Parser {
return instantiateLeafItem(field, key, value);
}
-
private String getInspectorKey(Inspector inspector){
String[] actualKey = {""};
if (inspector.type() == OBJECT){
@@ -834,7 +780,6 @@ public class SelectParser implements Parser {
return actualKey[0];
}
-
@NonNull
private Item instantiateLeafItem(String field, String key, Inspector value) {
List<Inspector> possibleLeafFunction = valueListFromInspector(value);
@@ -848,7 +793,6 @@ public class SelectParser implements Parser {
}
}
-
@NonNull
private Item instantiateCompositeLeaf(String field, String key, Inspector value) {
switch (key) {
@@ -869,14 +813,12 @@ public class SelectParser implements Parser {
}
}
-
@NonNull
private Item instantiateWordItem(String field, String key, Inspector value) {
String wordData = getChildrenMap(value).get(1).asString();
return instantiateWordItem(field, wordData, key, value, false, decideParsingLanguage(value, wordData));
}
-
@NonNull
private Item instantiateWordItem(String field, String rawWord, String key, Inspector value, boolean exactMatch, Language language) {
String wordData = rawWord;
@@ -919,7 +861,6 @@ public class SelectParser implements Parser {
return (Item) leafStyleSettings(getAnnotations(value), wordItem);
}
-
private Language decideParsingLanguage(Inspector value, String wordData) {
String languageTag = getAnnotation(USER_INPUT_LANGUAGE, getAnnotationMap(value), String.class, null);
@@ -932,13 +873,11 @@ public class SelectParser implements Parser {
return Language.ENGLISH;
}
-
private void prepareWord(String field, Inspector value, WordItem wordItem) {
wordItem.setIndexName(field);
wordStyleSettings(value, wordItem);
}
-
private void wordStyleSettings(Inspector value, WordItem out) {
HashMap<String, Inspector> annotations = getAnnotationMap(value);
@@ -947,7 +886,7 @@ public class SelectParser implements Parser {
out.setOrigin(origin);
}
if (annotations != null){
- Boolean usePositionData = Boolean.getBoolean(getAnnotation(USE_POSITION_DATA, annotations, String.class, null));
+ Boolean usePositionData = getBoolAnnotation(USE_POSITION_DATA, annotations, null);
if (usePositionData != null) {
out.setPositionData(usePositionData);
}
@@ -975,16 +914,15 @@ public class SelectParser implements Parser {
}
}
-
private Substring getOrigin(Inspector annotations) {
if (annotations != null) {
Inspector origin = getAnnotationAsInspectorOrNull(ORIGIN, getAnnotationMapFromAnnotationInspector(annotations));
if (origin == null) {
return null;
}
- final String[] original = {null};
- final Integer[] offset = {null};
- final Integer[] length = {null};
+ String[] original = {null};
+ Integer[] offset = {null};
+ Integer[] length = {null};
origin.traverse((ObjectTraverser) (key, value) -> {
switch (key) {
@@ -1020,25 +958,23 @@ public class SelectParser implements Parser {
return sameElement;
}
-
@NonNull
private Item instantiatePhraseItem(String field, String key, Inspector value) {
assertHasOperator(key, PHRASE);
- HashMap<String, Inspector> annotations = getAnnotationMap(value);
PhraseItem phrase = new PhraseItem();
phrase.setIndexName(field);
HashMap<Integer, Inspector> children = getChildrenMap(value);
- for (Inspector word : children.values())
- if (word.type() == STRING) phrase.addItem(new WordItem(word.asString()));
- else if (word.type() == OBJECT && word.field(PHRASE).valid()) {
- phrase.addItem(instantiatePhraseItem(field, key, getChildren(word)));
- }
+ for (Inspector word : children.values()) {
+ if (word.type() == STRING)
+ phrase.addItem(new WordItem(word.asString()));
+ else if (word.type() == OBJECT && word.field(PHRASE).valid())
+ phrase.addItem(instantiatePhraseItem(field, key, getChildren(word)));
+ }
return leafStyleSettings(getAnnotations(value), phrase);
}
-
@NonNull
private Item instantiateNearItem(String field, String key, Inspector value) {
assertHasOperator(key, NEAR);
@@ -1060,7 +996,6 @@ public class SelectParser implements Parser {
return near;
}
-
@NonNull
private Item instantiateONearItem(String field, String key, Inspector value) {
assertHasOperator(key, ONEAR);
@@ -1105,7 +1040,6 @@ public class SelectParser implements Parser {
return leafStyleSettings(getAnnotations(value), equiv);
}
-
private Item instantiateWordAlternativesItem(String field, String key, Inspector value) {
HashMap<Integer, Inspector> children = getChildrenMap(value);
Preconditions.checkArgument(children.size() >= 1, "Expected 1 or more arguments, got %s.", children.size());
@@ -1119,7 +1053,6 @@ public class SelectParser implements Parser {
return leafStyleSettings(getAnnotations(value), new WordAlternativesItem(field, Boolean.TRUE, null, terms));
}
-
// Not in use yet
@NonNull
private String getIndex(String field) {
@@ -1128,12 +1061,10 @@ public class SelectParser implements Parser {
return field;
}
-
private static void assertHasOperator(String key, String expectedKey) {
Preconditions.checkArgument(key.equals(expectedKey), "Expected operator %s, got %s.", expectedKey, key);
}
-
private static IllegalArgumentException newUnexpectedArgumentException(Object actual, Object... expected) {
StringBuilder out = new StringBuilder("Expected ");
for (int i = 0, len = expected.length; i < len; ++i) {
@@ -1148,26 +1079,22 @@ public class SelectParser implements Parser {
return new IllegalArgumentException(out.toString());
}
-
private List<Inspector> valueListFromInspector(Inspector inspector){
List<Inspector> inspectorList = new ArrayList<>();
inspector.traverse((ArrayTraverser) (key, value) -> inspectorList.add(value));
return inspectorList;
}
-
private void connectItems() {
for (ConnectedItem entry : connectedItems) {
TaggableItem to = identifiedItems.get(entry.toId);
Preconditions.checkNotNull(to,
"Item '%s' was specified to connect to item with ID %s, which does not "
- + "exist in the query.", entry.fromItem,
- entry.toId);
+ + "exist in the query.", entry.fromItem, entry.toId);
entry.fromItem.setConnectivity((Item) to, entry.weight);
}
}
-
private static final class ConnectedItem {
final double weight;
@@ -1181,5 +1108,4 @@ public class SelectParser implements Parser {
}
}
-
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java b/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
index 36b38ad8d03..3252f0f4662 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
@@ -27,23 +27,23 @@ public class SubstituteString {
* Returns a new SubstituteString if the given string contains substitutions, null otherwise.
*/
public static SubstituteString create(String value) {
- int lastEnd=0;
- int start=value.indexOf("%{");
- if (start<0) return null; // Shortcut
- List<Component> components=new ArrayList<>();
- while (start>=0) {
- int end=value.indexOf("}",start+2);
- if (end<0)
+ int lastEnd = 0;
+ int start = value.indexOf("%{");
+ if (start < 0) return null; // Shortcut
+ List<Component> components = new ArrayList<>();
+ while (start >= 0) {
+ int end = value.indexOf("}", start + 2);
+ if (end < 0)
throw new IllegalArgumentException("Unterminated value substitution '" + value.substring(start) + "'");
- String propertyName=value.substring(start+2,end);
- if (propertyName.indexOf("%{")>=0)
+ String propertyName = value.substring(start+2,end);
+ if (propertyName.indexOf("%{") >= 0)
throw new IllegalArgumentException("Unterminated value substitution '" + value.substring(start) + "'");
- components.add(new StringComponent(value.substring(lastEnd,start)));
+ components.add(new StringComponent(value.substring(lastEnd, start)));
components.add(new PropertyComponent(propertyName));
- lastEnd=end+1;
- start=value.indexOf("%{",lastEnd);
+ lastEnd = end+1;
+ start = value.indexOf("%{", lastEnd);
}
- components.add(new StringComponent(value.substring(lastEnd,value.length())));
+ components.add(new StringComponent(value.substring(lastEnd)));
return new SubstituteString(components, value);
}
@@ -83,7 +83,7 @@ public class SubstituteString {
private abstract static class Component {
- protected abstract String getValue(Map<String,String> context,Properties substitution);
+ protected abstract String getValue(Map<String, String> context, Properties substitution);
}
@@ -92,11 +92,11 @@ public class SubstituteString {
private final String value;
public StringComponent(String value) {
- this.value=value;
+ this.value = value;
}
@Override
- public String getValue(Map<String,String> context,Properties substitution) {
+ public String getValue(Map<String, String> context, Properties substitution) {
return value;
}
@@ -112,13 +112,13 @@ public class SubstituteString {
private final String propertyName;
public PropertyComponent(String propertyName) {
- this.propertyName=propertyName;
+ this.propertyName = propertyName;
}
@Override
- public String getValue(Map<String,String> context,Properties substitution) {
- Object value=substitution.get(propertyName,context,substitution);
- if (value==null) return "";
+ public String getValue(Map<String,String> context, Properties substitution) {
+ Object value = substitution.get(propertyName, context, substitution);
+ if (value == null) return "";
return String.valueOf(value);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
index 55855624691..60427aeb0af 100644
--- a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
@@ -3,9 +3,12 @@ package com.yahoo.search.query.properties;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
-import com.yahoo.search.grouping.GroupingRequest;
-import com.yahoo.search.grouping.vespa.GroupingExecutor;
-import com.yahoo.search.query.*;
+
+import com.yahoo.search.query.Model;
+import com.yahoo.search.query.Presentation;
+import com.yahoo.search.query.Properties;
+import com.yahoo.search.query.Ranking;
+import com.yahoo.search.query.Select;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
@@ -15,11 +18,8 @@ import com.yahoo.search.query.ranking.Matching;
import com.yahoo.search.query.ranking.SoftTimeout;
import com.yahoo.tensor.Tensor;
-import java.util.List;
import java.util.Map;
-
-
/**
* Maps between the query model and text properties.
* This can be done simpler by using reflection but the performance penalty was not worth it,
diff --git a/container-search/src/test/java/com/yahoo/prelude/semantics/test/PhraseMatchTestCase.java b/container-search/src/test/java/com/yahoo/prelude/semantics/test/PhraseMatchTestCase.java
index 41f67ed16fc..5cee88de849 100644
--- a/container-search/src/test/java/com/yahoo/prelude/semantics/test/PhraseMatchTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/semantics/test/PhraseMatchTestCase.java
@@ -22,4 +22,9 @@ public class PhraseMatchTestCase extends RuleBaseAbstractTestCase {
assertSemantics("AND retailer:digital retailer:camera","keyword:digital keyword:camera");
}
+ @Test
+ public void testMatchingPhrase() {
+ assertSemantics("OR (AND iphone 7) i7", "iphone 7");
+ }
+
}
diff --git a/container-search/src/test/java/com/yahoo/prelude/semantics/test/rulebases/phrasematch.sr b/container-search/src/test/java/com/yahoo/prelude/semantics/test/rulebases/phrasematch.sr
index f985c693284..70351ba8ba1 100644
--- a/container-search/src/test/java/com/yahoo/prelude/semantics/test/rulebases/phrasematch.sr
+++ b/container-search/src/test/java/com/yahoo/prelude/semantics/test/rulebases/phrasematch.sr
@@ -3,3 +3,5 @@
[ret] :- keyword:[B];
retailer:"[...]" -> retailer:[...];
+
+iphone 7 +> ?i7; \ No newline at end of file
diff --git a/container-search/src/test/java/com/yahoo/search/handler/test/JSONSearchHandlerTestCase.java b/container-search/src/test/java/com/yahoo/search/handler/test/JSONSearchHandlerTestCase.java
index e0fef7fc3b4..fa398efd293 100644
--- a/container-search/src/test/java/com/yahoo/search/handler/test/JSONSearchHandlerTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/handler/test/JSONSearchHandlerTestCase.java
@@ -13,6 +13,7 @@ import com.yahoo.search.handler.SearchHandler;
import com.yahoo.search.searchchain.config.test.SearchChainConfigurerTestCase;
import com.yahoo.slime.Inspector;
import com.yahoo.vespa.config.SlimeUtils;
+import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
@@ -30,7 +31,11 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
-
+/**
+ * Tests submitting the query as JSON.
+ *
+ * @author henrhoi
+ */
public class JSONSearchHandlerTestCase {
private static final String testDir = "src/test/java/com/yahoo/search/handler/test/config";
@@ -154,7 +159,7 @@ public class JSONSearchHandlerTestCase {
}
}
- private void testInvalidQueryParam(final RequestHandlerTestDriver testDriver) throws Exception{
+ private void testInvalidQueryParam(final RequestHandlerTestDriver testDriver) throws Exception {
JSONObject json = new JSONObject();
json.put("query", "status_code:0");
json.put("hits", 20);
@@ -167,9 +172,6 @@ public class JSONSearchHandlerTestCase {
assertThat(response, containsString("\"code\":" + com.yahoo.container.protect.Error.INVALID_QUERY_PARAMETER.code));
}
-
-
-
@Test
public void testNormalResultJsonAliasRendering() throws Exception {
JSONObject json = new JSONObject();
@@ -178,8 +180,6 @@ public class JSONSearchHandlerTestCase {
assertJsonResult(json, driver);
}
-
-
@Test
public void testNullQuery() throws Exception {
JSONObject json = new JSONObject();
@@ -194,8 +194,6 @@ public class JSONSearchHandlerTestCase {
"</result>\n", driver.sendRequest(uri, com.yahoo.jdisc.http.HttpRequest.Method.POST, json.toString(), JSON_CONTENT_TYPE).readAll());
}
-
-
@Test
public void testWebServiceStatus() throws Exception {
JSONObject json = new JSONObject();
@@ -230,7 +228,6 @@ public class JSONSearchHandlerTestCase {
assertXmlResult(json, driver);
}
-
@Test
public void testNormalResultExplicitDefaultRenderingFullRendererName1() throws Exception {
JSONObject json = new JSONObject();
@@ -263,7 +260,6 @@ public class JSONSearchHandlerTestCase {
assertPageResult(json, driver);
}
-
private static final String xmlResult =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
"<result total-hit-count=\"0\">\n" +
@@ -273,18 +269,17 @@ public class JSONSearchHandlerTestCase {
" </hit>\n" +
"</result>\n";
- private void assertXmlResult(JSONObject json, RequestHandlerTestDriver driver) throws Exception {
+ private void assertXmlResult(JSONObject json, RequestHandlerTestDriver driver) {
assertOkResult(driver.sendRequest(uri, com.yahoo.jdisc.http.HttpRequest.Method.POST, json.toString(), JSON_CONTENT_TYPE), xmlResult);
}
-
private static final String jsonResult = "{\"root\":{"
+ "\"id\":\"toplevel\",\"relevance\":1.0,\"fields\":{\"totalCount\":0},"
+ "\"children\":["
+ "{\"id\":\"testHit\",\"relevance\":1.0,\"fields\":{\"uri\":\"testHit\"}}"
+ "]}}";
- private void assertJsonResult(JSONObject json, RequestHandlerTestDriver driver) throws Exception {
+ private void assertJsonResult(JSONObject json, RequestHandlerTestDriver driver) {
assertOkResult(driver.sendRequest(uri, com.yahoo.jdisc.http.HttpRequest.Method.POST, json.toString(), JSON_CONTENT_TYPE), jsonResult);
}
@@ -300,7 +295,7 @@ public class JSONSearchHandlerTestCase {
"\n" +
"</result>\n";
- private void assertTiledResult(JSONObject json, RequestHandlerTestDriver driver) throws Exception {
+ private void assertTiledResult(JSONObject json, RequestHandlerTestDriver driver) {
assertOkResult(driver.sendRequest(uri, com.yahoo.jdisc.http.HttpRequest.Method.POST, json.toString(), JSON_CONTENT_TYPE), tiledResult);
}
@@ -317,7 +312,7 @@ public class JSONSearchHandlerTestCase {
"\n" +
"</page>\n";
- private void assertPageResult(JSONObject json, RequestHandlerTestDriver driver) throws Exception {
+ private void assertPageResult(JSONObject json, RequestHandlerTestDriver driver) {
assertOkResult(driver.sendRequest(uri, com.yahoo.jdisc.http.HttpRequest.Method.POST, json.toString(), JSON_CONTENT_TYPE), pageResult);
}
@@ -338,9 +333,8 @@ public class JSONSearchHandlerTestCase {
return new RequestHandlerTestDriver(newSearchHandler);
}
-
@Test
- public void testSelectParameter() throws Exception {
+ public void testSelectParameters() throws Exception {
JSONObject json = new JSONObject();
JSONObject select = new JSONObject();
@@ -356,8 +350,6 @@ public class JSONSearchHandlerTestCase {
json.put("select", select);
-
- // Create mapping
Inspector inspector = SlimeUtils.jsonToSlime(json.toString().getBytes("utf-8")).get();
Map<String, String> map = new HashMap<>();
searchHandler.createRequestMapping(inspector, map, "");
@@ -369,7 +361,34 @@ public class JSONSearchHandlerTestCase {
assertEquals(grouping.toString(), processedGrouping.toString());
}
+ @Test
+ public void testJsonQueryWithSelectWhere() throws Exception {
+ JSONObject root = new JSONObject();
+ JSONObject select = new JSONObject();
+ JSONObject where = new JSONObject();
+ JSONArray term = new JSONArray();
+ term.put("default");
+ term.put("bad");
+ where.put("contains", term);
+ select.put("where", where);
+ root.put("select", select);
+
+ // Run query
+ String result = driver.sendRequest(uri + "searchChain=echoingQuery", com.yahoo.jdisc.http.HttpRequest.Method.POST, root.toString(), JSON_CONTENT_TYPE).readAll();
+ assertEquals("{\"root\":{\"id\":\"toplevel\",\"relevance\":1.0,\"fields\":{\"totalCount\":0},\"children\":[{\"id\":\"Query\",\"relevance\":1.0,\"fields\":{\"query\":\"select * from sources * where default contains \\\"bad\\\";\"}}]}}",
+ result);
+ }
+ @Test
+ public void testJsonQueryWithYQL() throws Exception {
+ JSONObject root = new JSONObject();
+ root.put("yql", "select * from sources * where default contains 'bad';");
+
+ // Run query
+ String result = driver.sendRequest(uri + "searchChain=echoingQuery", com.yahoo.jdisc.http.HttpRequest.Method.POST, root.toString(), JSON_CONTENT_TYPE).readAll();
+ assertEquals("{\"root\":{\"id\":\"toplevel\",\"relevance\":1.0,\"fields\":{\"totalCount\":0},\"children\":[{\"id\":\"Query\",\"relevance\":1.0,\"fields\":{\"query\":\"select * from sources * where default contains \\\"bad\\\";\"}}]}}",
+ result);
+ }
@Test
public void testRequestMapping() throws Exception {
@@ -468,8 +487,6 @@ public class JSONSearchHandlerTestCase {
json.put("nocachewrite", false);
json.put("hitcountestimate", true);
-
-
// Create mapping
Inspector inspector = SlimeUtils.jsonToSlime(json.toString().getBytes("utf-8")).get();
Map<String, String> map = new HashMap<>();
@@ -484,9 +501,7 @@ public class JSONSearchHandlerTestCase {
"&queryProfile=foo&presentation.bolding=true&model.encoding=json&model.queryString=abc&streaming.selection=none&trace.timestamps=false&collapse.size=2&streaming.priority=10&ranking.matchPhase.diversity.attribute=title" +
"&ranking.matchPhase.attribute=title&hits=10&streaming.userid=123&pos.bb=1237123W%3B123218N&model.restrict=_doc%2Cjson%2Cxml&ranking.freshness=0.05&user=123";
-
-
- final HttpRequest request = HttpRequest.createTestRequest(url, GET);
+ HttpRequest request = HttpRequest.createTestRequest(url, GET);
// Get mapping
Map<String, String> propertyMap = request.propertyMap();
diff --git a/container-search/src/test/java/com/yahoo/search/handler/test/SearchHandlerTestCase.java b/container-search/src/test/java/com/yahoo/search/handler/test/SearchHandlerTestCase.java
index 6dcb34ec3e9..5ef13eba2ed 100644
--- a/container-search/src/test/java/com/yahoo/search/handler/test/SearchHandlerTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/handler/test/SearchHandlerTestCase.java
@@ -406,6 +406,19 @@ public class SearchHandlerTestCase {
}
/** Referenced from config */
+ public static class EchoingQuerySearcher extends Searcher {
+
+ @Override
+ public Result search(Query query, Execution execution) {
+ Result result = execution.search(query);
+ Hit hit = new Hit("Query");
+ hit.setField("query", query.yqlRepresentation());
+ result.hits().add(hit);
+ return result;
+ }
+ }
+
+ /** Referenced from config */
public static class ForwardingHandler extends ThreadedHttpRequestHandler {
private final SearchHandler searchHandler;
diff --git a/container-search/src/test/java/com/yahoo/search/handler/test/config/chains.cfg b/container-search/src/test/java/com/yahoo/search/handler/test/config/chains.cfg
index 0336e06f54b..9a16c6ed1e7 100644
--- a/container-search/src/test/java/com/yahoo/search/handler/test/config/chains.cfg
+++ b/container-search/src/test/java/com/yahoo/search/handler/test/config/chains.cfg
@@ -1,4 +1,4 @@
-chains[3]
+chains[4]
chains[0].id default
chains[0].components[1]
chains[0].components[0] com.yahoo.search.handler.test.SearchHandlerTestCase$TestSearcher
@@ -8,7 +8,13 @@ chains[1].components[0] com.yahoo.search.handler.test.SearchHandlerTestCase$Clas
chains[2].id exceptionInPlugin
chains[2].components[1]
chains[2].components[0] com.yahoo.search.handler.test.SearchHandlerTestCase$ExceptionInPluginSearcher
-components[3]
+chains[3].id echoingQuery
+chains[3].components[2]
+chains[3].components[0] com.yahoo.search.yql.MinimalQueryInserter
+chains[3].components[1] com.yahoo.search.handler.test.SearchHandlerTestCase$EchoingQuerySearcher
+components[5]
components[0].id com.yahoo.search.handler.test.SearchHandlerTestCase$TestSearcher
components[1].id com.yahoo.search.handler.test.SearchHandlerTestCase$ClassLoadingErrorSearcher
components[2].id com.yahoo.search.handler.test.SearchHandlerTestCase$ExceptionInPluginSearcher
+components[3].id com.yahoo.search.handler.test.SearchHandlerTestCase$EchoingQuerySearcher
+components[4].id com.yahoo.search.yql.MinimalQueryInserter
diff --git a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/QueryProfileIntegrationTestCase.java b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/QueryProfileIntegrationTestCase.java
index 67fb5da10a0..67d22fba4a3 100644
--- a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/QueryProfileIntegrationTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/QueryProfileIntegrationTestCase.java
@@ -80,7 +80,7 @@ public class QueryProfileIntegrationTestCase {
System.setProperty("config.id", configId);
Container container = new Container();
HandlersConfigurerTestWrapper configurer = new HandlersConfigurerTestWrapper(container, configId);
- SearchHandler searchHandler = (SearchHandler) configurer.getRequestHandlerRegistry().getComponent(SearchHandler.class.getName());
+ SearchHandler searchHandler = (SearchHandler)configurer.getRequestHandlerRegistry().getComponent(SearchHandler.class.getName());
// Should get "default" query profile containing the "test" search chain containing the "test" searcher
HttpRequest request = HttpRequest.createTestRequest("search", Method.GET);
diff --git a/container-search/src/test/java/com/yahoo/search/query/profile/test/QueryProfileSubstitutionTestCase.java b/container-search/src/test/java/com/yahoo/search/query/profile/test/QueryProfileSubstitutionTestCase.java
index 7ff120ddc70..9a0063e7f07 100644
--- a/container-search/src/test/java/com/yahoo/search/query/profile/test/QueryProfileSubstitutionTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/query/profile/test/QueryProfileSubstitutionTestCase.java
@@ -19,7 +19,7 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testSingleSubstitution() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message","Hello %{world}!", null);
p.set("world", "world", null);
assertEquals("Hello world!",p.compile(null).get("message"));
@@ -27,7 +27,7 @@ public class QueryProfileSubstitutionTestCase {
QueryProfile p2=new QueryProfile("test2");
p2.addInherited(p);
p2.set("world", "universe", null);
- assertEquals("Hello universe!",p2.compile(null).get("message"));
+ assertEquals("Hello universe!", p2.compile(null).get("message"));
}
@Test
@@ -39,16 +39,16 @@ public class QueryProfileSubstitutionTestCase {
p.set("exclamation","?", null);
assertEquals("Hola local group?",p.compile(null).get("message"));
- QueryProfile p2=new QueryProfile("test2");
+ QueryProfile p2 = new QueryProfile("test2");
p2.addInherited(p);
p2.set("entity","milky way", null);
- assertEquals("Hola milky way?",p2.compile(null).get("message"));
+ assertEquals("Hola milky way?", p2.compile(null).get("message"));
}
@Test
public void testUnclosedSubstitution1() {
try {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message1","%{greeting} %{entity}%{exclamation", null);
fail("Should have produced an exception");
}
@@ -61,7 +61,7 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testUnclosedSubstitution2() {
try {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message1","%{greeting} %{entity%{exclamation}", null);
fail("Should have produced an exception");
}
@@ -73,26 +73,26 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testNullSubstitution() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message","%{greeting} %{entity}%{exclamation}", null);
p.set("greeting","Hola", null);
assertEquals("Hola ", p.compile(null).get("message"));
- QueryProfile p2=new QueryProfile("test2");
+ QueryProfile p2 = new QueryProfile("test2");
p2.addInherited(p);
p2.set("greeting","Hola", null);
p2.set("exclamation", "?", null);
- assertEquals("Hola ?",p2.compile(null).get("message"));
+ assertEquals("Hola ?", p2.compile(null).get("message"));
}
@Test
public void testNoOverridingOfPropertiesSetAtRuntime() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message","Hello %{world}!", null);
p.set("world","world", null);
p.freeze();
- Properties runtime=new QueryProfileProperties(p.compile(null));
+ Properties runtime = new QueryProfileProperties(p.compile(null));
runtime.set("runtimeMessage","Hello %{world}!");
assertEquals("Hello world!", runtime.get("message"));
assertEquals("Hello %{world}!",runtime.get("runtimeMessage"));
@@ -100,18 +100,18 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testButPropertiesSetAtRuntimeAreUsedInSubstitutions() {
- QueryProfile p=new QueryProfile("test");
- p.set("message","Hello %{world}!", null);
- p.set("world","world", null);
+ QueryProfile p = new QueryProfile("test");
+ p.set("message", "Hello %{world}!", null);
+ p.set("world", "world", null);
- Properties runtime=new QueryProfileProperties(p.compile(null));
- runtime.set("world","Earth");
- assertEquals("Hello Earth!",runtime.get("message"));
+ Properties runtime = new QueryProfileProperties(p.compile(null));
+ runtime.set("world", "Earth");
+ assertEquals("Hello Earth!", runtime.get("message"));
}
@Test
public void testInspection() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message", "%{greeting} %{entity}%{exclamation}", null);
assertEquals("message","%{greeting} %{entity}%{exclamation}",
p.declaredContent().entrySet().iterator().next().getValue().toString());
@@ -119,7 +119,7 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testVariants() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message","Hello %{world}!", null);
p.set("world","world", null);
p.setDimensions(new String[] {"x"});
@@ -134,7 +134,7 @@ public class QueryProfileSubstitutionTestCase {
@Test
public void testRecursion() {
- QueryProfile p=new QueryProfile("test");
+ QueryProfile p = new QueryProfile("test");
p.set("message","Hello %{world}!", null);
p.set("world","sol planet number %{number}", null);
p.set("number",3, null);
diff --git a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
index e60d84db3d0..ed80c0bf256 100644
--- a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
@@ -300,6 +300,14 @@ public class QueryTestCase {
}
@Test
+ public void testQueryProfileSubstitution() {
+ QueryProfile profile = new QueryProfile("myProfile");
+ profile.set("myField", "Profile: %{queryProfile}", null);
+ Query q = new Query(QueryTestCase.httpEncode("/search?queryProfile=myProfile"), profile.compile(null));
+ assertEquals("Profile: myProfile", q.properties().get("myField"));
+ }
+
+ @Test
public void testTimeoutInRequestOverridesQueryProfile() {
QueryProfile profile = new QueryProfile("test");
profile.set("timeout", 318, (QueryProfileRegistry)null);
@@ -332,6 +340,20 @@ public class QueryTestCase {
}
@Test
+ public void testQueryProfileInSubstitution() {
+ QueryProfile testProfile = new QueryProfile("test");
+ testProfile.setOverridable("u", false, null);
+ testProfile.set("d","e", null);
+ testProfile.set("u","11", null);
+ testProfile.set("foo.bar", "wiz", null);
+ Query q = new Query(QueryTestCase.httpEncode("?query=a:>5&a=b&traceLevel=5&sources=a,b&u=12&foo.bar2=wiz2&c.d=foo&queryProfile=test"),testProfile.compile(null));
+ String trace = q.getContext(false).getTrace().toString();
+ String[] traceLines = trace.split("\n");
+ for (String line : traceLines)
+ System.out.println(line);
+ }
+
+ @Test
public void testDefaultIndex() {
Query q = new Query("?query=hi%20hello%20keyword:kanoo%20" +
"default:munkz%20%22phrases+too%22&default-index=def");
@@ -385,18 +407,15 @@ public class QueryTestCase {
}
public class TestClass {
+
private int testInt = 0;
- public int getTestInt() {
- return testInt;
- }
- public void setTestInt(int testInt) {
- this.testInt = testInt;
- }
+ public int getTestInt() { return testInt; }
+
+ public void setTestInt(int testInt) { this.testInt = testInt; }
+
+ public void setTestInt(String testInt) { this.testInt = Integer.parseInt(testInt); }
- public void setTestInt(String testInt) {
- this.testInt = Integer.parseInt(testInt);
- }
}
@Test
@@ -431,7 +450,6 @@ public class QueryTestCase {
Set<String> traces = new HashSet<>();
for (String trace : q.getContext(true).getTrace().traceNode().descendants(String.class))
traces.add(trace);
- // for (String s : traces) System.out.println(s);
assertTrue(traces.contains("trace1: [select * from sources * where default contains \"foo\";]"));
assertTrue(traces.contains("trace2"));
assertTrue(traces.contains("trace3-1, trace3-2: [select * from sources * where default contains \"foo\";]"));
@@ -444,9 +462,8 @@ public class QueryTestCase {
assertEquals(2, q.getTraceLevel());
q.trace(false,2, "trace2 ", null);
Set<String> traces = new HashSet<>();
- for (String trace : q.getContext(true).getTrace().traceNode().descendants(String.class)) {
+ for (String trace : q.getContext(true).getTrace().traceNode().descendants(String.class))
traces.add(trace);
- }
assertTrue(traces.contains("trace2 null"));
}
@@ -460,8 +477,6 @@ public class QueryTestCase {
Query q = new Query(QueryTestCase.httpEncode("?query=a:>5&a=b&traceLevel=5&sources=a,b&u=12&foo.bar2=wiz2&c.d=foo&queryProfile=test"),testProfile.compile(null));
String trace = q.getContext(false).getTrace().toString();
String[] traceLines = trace.split("\n");
- for (String line : traceLines)
- System.out.println(line);
assertTrue(contains("query=a:>5 (value from request)", traceLines));
assertTrue(contains("traceLevel=5 (value from request)", traceLines));
assertTrue(contains("a=b (value from request)", traceLines));
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
index 1c8b861ba51..b1dad7d814e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
@@ -1,6 +1,9 @@
// 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.deployment;
+import com.yahoo.component.Version;
+
+import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
@@ -17,7 +20,8 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
* Used in cases where application version cannot be determined, such as manual deployments (e.g. in dev
* environment)
*/
- public static final ApplicationVersion unknown = new ApplicationVersion(Optional.empty(), OptionalLong.empty(), Optional.empty());
+ public static final ApplicationVersion unknown = new ApplicationVersion(Optional.empty(), OptionalLong.empty(),
+ Optional.empty(), Optional.empty(), Optional.empty());
// This never changes and is only used to create a valid semantic version number, as required by application bundles
private static final String majorVersion = "1.0";
@@ -25,33 +29,53 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
private final Optional<SourceRevision> source;
private final Optional<String> authorEmail;
private final OptionalLong buildNumber;
+ private final Optional<Version> compileVersion;
+ private final Optional<Instant> buildTime;
- private ApplicationVersion(Optional<SourceRevision> source, OptionalLong buildNumber, Optional<String> authorEmail) {
+ private ApplicationVersion(Optional<SourceRevision> source, OptionalLong buildNumber, Optional<String> authorEmail,
+ Optional<Version> compileVersion, Optional<Instant> buildTime) {
Objects.requireNonNull(source, "source cannot be null");
Objects.requireNonNull(buildNumber, "buildNumber cannot be null");
Objects.requireNonNull(authorEmail, "author cannot be null");
if (source.isPresent() != buildNumber.isPresent()) {
throw new IllegalArgumentException("both buildNumber and source must be set together");
}
+ if (compileVersion.isPresent() != buildTime.isPresent()) {
+ throw new IllegalArgumentException("both compileVersion and buildTime must be set together");
+ }
if (buildNumber.isPresent() && buildNumber.getAsLong() <= 0) {
throw new IllegalArgumentException("buildNumber must be > 0");
}
if (authorEmail.isPresent() && ! authorEmail.get().matches("[^@]+@[^@]+")) {
throw new IllegalArgumentException("Invalid author email '" + authorEmail.get() + "'.");
}
+ if (compileVersion.isPresent() && compileVersion.get().equals(Version.emptyVersion)) {
+ throw new IllegalArgumentException("The empty version is not a legal compile version.");
+ }
this.source = source;
this.buildNumber = buildNumber;
this.authorEmail = authorEmail;
+ this.compileVersion = compileVersion;
+ this.buildTime = buildTime;
}
/** Create an application package version from a completed build, without an author email */
public static ApplicationVersion from(SourceRevision source, long buildNumber) {
- return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.empty());
+ return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.empty(),
+ Optional.empty(), Optional.empty());
}
- /** Create an application package version from a completed build, with an author email */
+ /** Creates an version from a completed build and an author email. */
public static ApplicationVersion from(SourceRevision source, long buildNumber, String authorEmail) {
- return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.of(authorEmail));
+ return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber),
+ Optional.of(authorEmail), Optional.empty(), Optional.empty());
+ }
+
+ /** Creates an version from a completed build, an author email, and build meta data. */
+ public static ApplicationVersion from(SourceRevision source, long buildNumber, String authorEmail,
+ Version compileVersion, Instant buildTime) {
+ return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.of(authorEmail),
+ Optional.of(compileVersion), Optional.of(buildTime));
}
/** Returns an unique identifier for this version or "unknown" if version is not known */
@@ -74,6 +98,12 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
/** Returns the email of the author of commit of this version, if known */
public Optional<String> authorEmail() { return authorEmail; }
+ /** Returns the Vespa version this package was compiled against, if known. */
+ public Optional<Version> compileVersion() { return compileVersion; }
+
+ /** Returns the time this package was built, if known. */
+ public Optional<Instant> buildTime() { return buildTime; }
+
/** Returns whether this is unknown */
public boolean isUnknown() {
return this.equals(unknown);
@@ -86,19 +116,23 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
ApplicationVersion that = (ApplicationVersion) o;
return Objects.equals(source, that.source) &&
Objects.equals(authorEmail, that.authorEmail) &&
- Objects.equals(buildNumber, that.buildNumber);
+ Objects.equals(buildNumber, that.buildNumber) &&
+ Objects.equals(compileVersion, that.compileVersion) &&
+ Objects.equals(buildTime, that.buildTime);
}
@Override
public int hashCode() {
- return Objects.hash(source, authorEmail, buildNumber);
+ return Objects.hash(source, authorEmail, buildNumber, compileVersion, buildTime);
}
@Override
public String toString() {
- return "Application package version: " + id()
+ return "Application package version: " + id()
+ source.map(s -> ", " + s.toString()).orElse("")
- + authorEmail.map(e -> ", " + e).orElse("");
+ + authorEmail.map(e -> ", by " + e).orElse("")
+ + compileVersion.map(v -> ", built against " + v).orElse("")
+ + buildTime.map(t -> " at " + t).orElse("") ;
}
/** Abbreviate given commit hash to 9 characters */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
index 40e2e4a92d1..a0b4a888727 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
@@ -1,14 +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.application;
+import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.SlimeUtils;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
@@ -27,10 +32,12 @@ public class ApplicationPackage {
private final byte[] zippedContent;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
+ private final Optional<Version> compileVersion;
+ private final Optional<Instant> buildTime;
/**
* Creates an application package from its zipped content.
- * This <b>assigns ownership</b> of the given byte array to this class:
+ * This <b>assigns ownership</b> of the given byte array to this class;
* it must not be further changed by the caller.
*/
public ApplicationPackage(byte[] zippedContent) {
@@ -38,6 +45,10 @@ public class ApplicationPackage {
this.contentHash = DigestUtils.shaHex(zippedContent);
this.deploymentSpec = extractFile("deployment.xml", zippedContent).map(DeploymentSpec::fromXml).orElse(DeploymentSpec.empty);
this.validationOverrides = extractFile("validation-overrides.xml", zippedContent).map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty);
+ Optional<Inspector> buildMetaObject = extractFileBytes("build-meta.json", zippedContent)
+ .map(SlimeUtils::jsonToSlime).map(Slime::get);
+ this.compileVersion = buildMetaObject.map(object -> Version.fromString(object.field("compileVersion").asString()));
+ this.buildTime = buildMetaObject.map(object -> Instant.ofEpochMilli(object.field("buildTime").asLong()));
}
/** Returns a hash of the content of this package */
@@ -58,12 +69,18 @@ public class ApplicationPackage {
*/
public ValidationOverrides validationOverrides() { return validationOverrides; }
- private static Optional<Reader> extractFile(String fileName, byte[] zippedContent) {
+ /** Returns the platform version which package was compiled against, if known. */
+ public Optional<Version> compileVersion() { return compileVersion; }
+
+ /** Returns the time this package was built, if known. */
+ public Optional<Instant> buildTime() { return buildTime; }
+
+ private static Optional<byte[]> extractFileBytes(String fileName, byte[] zippedContent) {
try (ByteArrayInputStream stream = new ByteArrayInputStream(zippedContent)) {
ZipStreamReader reader = new ZipStreamReader(stream);
for (ZipStreamReader.ZipEntryWithContent entry : reader.entries())
if (entry.zipEntry().getName().equals(fileName) || entry.zipEntry().getName().equals("application/" + fileName)) // TODO: Remove application/ directory support
- return Optional.of(new InputStreamReader(new ByteArrayInputStream(entry.content())));
+ return Optional.of(entry.content());
return Optional.empty();
}
catch (IOException e) {
@@ -71,4 +88,8 @@ public class ApplicationPackage {
}
}
+ private static Optional<Reader> extractFile(String fileName, byte[] zippedContent) {
+ return extractFileBytes(fileName, zippedContent).map(ByteArrayInputStream::new).map(InputStreamReader::new);
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 14222ca96d2..81a4e30feac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -504,7 +504,7 @@ public class InternalStepRunner implements StepRunner {
DeploymentSpec spec = controller.applications().require(id.application()).deploymentSpec();
ZoneId zone = id.type().zone(controller.system());
- byte[] deploymentXml = deploymentXml(spec.athenzDomain().get(), spec.athenzService(zone.environment(), zone.region()).get());
+ byte[] deploymentXml = deploymentXml(spec.athenzDomain(), spec.athenzService(zone.environment(), zone.region()));
try (ZipBuilder zipBuilder = new ZipBuilder(testPackage.length + servicesXml.length + 1000)) {
zipBuilder.add(testPackage);
@@ -591,11 +591,14 @@ public class InternalStepRunner implements StepRunner {
return servicesXml.getBytes();
}
- /** Returns a dummy deployment xml which sets up the service identity for the tester. */
- static byte[] deploymentXml(AthenzDomain domain, AthenzService service) {
+ /** Returns a dummy deployment xml which sets up the service identity for the tester, if present. */
+ private static byte[] deploymentXml(Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService) {
String deploymentSpec =
"<?xml version='1.0' encoding='UTF-8'?>\n" +
- "<deployment version=\"1.0\" athenz-domain=\"" + domain.value() + "\" athenz-service=\"" + service.value() + "\" />";
+ "<deployment version=\"1.0\" " +
+ athenzDomain.map(domain -> "athenz-domain=\"" + domain.value() + "\" ").orElse("") +
+ athenzService.map(service -> "athenz-service=\"" + service.value() + "\" ").orElse("")
+ + "/>";
return deploymentSpec.getBytes(StandardCharsets.UTF_8);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 5c56c7d2280..fcead86893a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -10,25 +10,23 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.RunDataStore;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NoInstanceException;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.persistence.BufferedLogStore;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.net.URI;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -41,7 +39,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.collect.ImmutableList.copyOf;
@@ -229,7 +226,7 @@ public class JobController {
* Accepts and stores a new application package and test jar pair under a generated application version key.
*/
public ApplicationVersion submit(ApplicationId id, SourceRevision revision, String authorEmail, long projectId,
- byte[] packageBytes, byte[] testPackageBytes) {
+ ApplicationPackage applicationPackage, byte[] testPackageBytes) {
AtomicReference<ApplicationVersion> version = new AtomicReference<>();
controller.applications().lockOrThrow(id, application -> {
if ( ! application.get().deploymentJobs().deployedInternally()) {
@@ -245,17 +242,22 @@ public class JobController {
}
long run = nextBuild(id);
- version.set(ApplicationVersion.from(revision, run, authorEmail));
+ if (applicationPackage.compileVersion().isPresent() && applicationPackage.buildTime().isPresent())
+ version.set(ApplicationVersion.from(revision, run, authorEmail,
+ applicationPackage.compileVersion().get(),
+ applicationPackage.buildTime().get()));
+ else
+ version.set(ApplicationVersion.from(revision, run, authorEmail));
controller.applications().applicationStore().put(id,
version.get(),
- packageBytes);
+ applicationPackage.zippedContent());
controller.applications().applicationStore().put(TesterId.of(id),
version.get(),
testPackageBytes);
prunePackages(id);
- controller.applications().storeWithUpdatedConfig(application.withBuiltInternally(true), new ApplicationPackage(packageBytes));
+ controller.applications().storeWithUpdatedConfig(application.withBuiltInternally(true), applicationPackage);
controller.applications().deploymentTrigger().notifyOfCompletion(DeploymentJobs.JobReport.ofSubmission(id, projectId, version.get()));
});
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 7924ea34475..08e5be2ea1f 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
@@ -72,7 +72,7 @@ public class ControllerMaintenance extends AbstractComponent {
readyJobsTrigger = new ReadyJobsTrigger(controller, Duration.ofMinutes(1), jobControl);
clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl, nodeRepositoryClient);
clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl);
- deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl);
+ deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(5), jobControl);
applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(12), jobControl, ownershipIssues);
dnsMaintainer = new DnsMaintainer(controller, Duration.ofHours(12), jobControl, nameService);
systemUpgrader = new SystemUpgrader(controller, Duration.ofMinutes(1), jobControl);
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 305241a7d0e..2ae38fabb94 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
@@ -11,6 +11,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.chef.AttributeMapping;
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.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
@@ -37,6 +38,7 @@ public class MetricsReporter extends Maintainer {
public static final String deploymentFailMetric = "deployment.failurePercentage";
public static final String deploymentAverageDuration = "deployment.averageDuration";
public static final String deploymentFailingUpgrades = "deployment.failingUpgrades";
+ public static final String deploymentBuildAgeSeconds = "deployment.buildAgeSeconds";
public static final String remainingRotations = "remaining_rotations";
private final Metric metric;
@@ -128,6 +130,14 @@ public class MetricsReporter extends Maintainer {
deploymentsFailingUpgrade(applications).forEach((application, failingJobs) -> {
metric.set(deploymentFailingUpgrades, failingJobs, metric.createContext(dimensions(application)));
});
+
+ for (Application application : applications.asList())
+ application.deploymentJobs().statusOf(JobType.component)
+ .flatMap(JobStatus::lastSuccess)
+ .flatMap(run -> run.application().buildTime())
+ .ifPresent(buildTime -> metric.set(deploymentBuildAgeSeconds,
+ controller().clock().instant().getEpochSecond() - buildTime.getEpochSecond(),
+ metric.createContext(dimensions(application.id()))));
}
private static double deploymentFailRatio(ApplicationList applicationList) {
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 69ea36c7e3a..147b2edee3e 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
@@ -81,6 +81,8 @@ public class ApplicationSerializer {
private final String branchField = "branchField";
private final String commitField = "commitField";
private final String authorEmailField = "authorEmailField";
+ private final String compileVersionField = "compileVersion";
+ private final String buildTimeField = "buildTime";
private final String lastQueriedField = "lastQueried";
private final String lastWrittenField = "lastWritten";
private final String lastQueriesPerSecondField = "lastQueriesPerSecond";
@@ -231,6 +233,8 @@ public class ApplicationSerializer {
object.setLong(applicationBuildNumberField, applicationVersion.buildNumber().getAsLong());
toSlime(applicationVersion.source().get(), object.setObject(sourceRevisionField));
applicationVersion.authorEmail().ifPresent(email -> object.setString(authorEmailField, email));
+ applicationVersion.compileVersion().ifPresent(version -> object.setString(compileVersionField, version.toString()));
+ applicationVersion.buildTime().ifPresent(time -> object.setLong(buildTimeField, time.toEpochMilli()));
}
}
@@ -406,12 +410,21 @@ public class ApplicationSerializer {
if ( ! object.valid()) return ApplicationVersion.unknown;
OptionalLong applicationBuildNumber = optionalLong(object.field(applicationBuildNumberField));
Optional<SourceRevision> sourceRevision = sourceRevisionFromSlime(object.field(sourceRevisionField));
- if (!sourceRevision.isPresent() || !applicationBuildNumber.isPresent()) {
+ if ( ! sourceRevision.isPresent() || ! applicationBuildNumber.isPresent()) {
return ApplicationVersion.unknown;
}
- return object.field(authorEmailField).valid()
- ? ApplicationVersion.from(sourceRevision.get(), applicationBuildNumber.getAsLong(), object.field(authorEmailField).asString())
- : ApplicationVersion.from(sourceRevision.get(), applicationBuildNumber.getAsLong());
+ Optional<String> authorEmail = optionalString(object.field(authorEmailField));
+ Optional<Version> compileVersion = optionalString(object.field(compileVersionField)).map(Version::fromString);
+ Optional<Instant> buildTime = optionalInstant(object.field(buildTimeField));
+
+ if ( ! authorEmail.isPresent())
+ return ApplicationVersion.from(sourceRevision.get(), applicationBuildNumber.getAsLong());
+
+ if ( ! compileVersion.isPresent() || ! buildTime.isPresent())
+ return ApplicationVersion.from(sourceRevision.get(), applicationBuildNumber.getAsLong(), authorEmail.get());
+
+ return ApplicationVersion.from(sourceRevision.get(), applicationBuildNumber.getAsLong(), authorEmail.get(),
+ compileVersion.get(), buildTime.get());
}
private Optional<SourceRevision> sourceRevisionFromSlime(Inspector object) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
index ef91a2e5a15..32c92e6f135 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
@@ -68,6 +68,8 @@ class RunSerializer {
private static final String branchField = "branch";
private static final String commitField = "commit";
private static final String authorEmailField = "authorEmail";
+ private static final String compileVersionField = "compileVersion";
+ private static final String buildTimeField = "buildTime";
private static final String buildField = "build";
private static final String sourceField = "source";
private static final String lastTestRecordField = "lastTestRecord";
@@ -123,9 +125,16 @@ class RunSerializer {
versionObject.field(branchField).asString(),
versionObject.field(commitField).asString());
long buildNumber = versionObject.field(buildField).asLong();
- return versionObject.field(authorEmailField).valid()
- ? ApplicationVersion.from(revision, buildNumber, versionObject.field(authorEmailField).asString())
- : ApplicationVersion.from(revision, buildNumber);
+
+ if ( ! versionObject.field(authorEmailField).valid())
+ return ApplicationVersion.from(revision, buildNumber);
+
+ if ( ! versionObject.field(compileVersionField).valid() || ! versionObject.field(buildTimeField).valid())
+ return ApplicationVersion.from(revision, buildNumber, versionObject.field(authorEmailField).asString());
+
+ return ApplicationVersion.from(revision, buildNumber, versionObject.field(authorEmailField).asString(),
+ Version.fromString(versionObject.field(compileVersionField).asString()),
+ Instant.ofEpochMilli(versionObject.field(buildTimeField).asLong()));
}
Slime toSlime(Iterable<Run> runs) {
@@ -173,6 +182,8 @@ class RunSerializer {
versionsObject.setLong(buildField, applicationVersion.buildNumber()
.orElseThrow(() -> new IllegalArgumentException("Build number must be present in application version.")));
applicationVersion.authorEmail().ifPresent(email -> versionsObject.setString(authorEmailField, email));
+ applicationVersion.compileVersion().ifPresent(version -> versionsObject.setString(compileVersionField, version.toString()));
+ applicationVersion.buildTime().ifPresent(time -> versionsObject.setLong(buildTimeField, time.toEpochMilli()));
}
static String valueOf(Step step) {
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 c8104c6e2b0..56e1e746d04 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
@@ -803,7 +803,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
StringBuilder response = new StringBuilder();
controller.applications().lockOrThrow(id, application -> {
Change change = application.get().change();
- if ( ! change.isPresent()) {
+ if ( ! change.isPresent() && ! change.isPinned()) {
response.append("No deployment in progress for " + application + " at this time");
return;
}
@@ -1322,9 +1322,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
long projectId = Math.max(1, submitOptions.field("projectId").asLong());
ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get(EnvironmentResource.APPLICATION_ZIP));
- if ( ! applicationPackage.deploymentSpec().athenzDomain().isPresent())
- throw new IllegalArgumentException("Application must define an Athenz service in deployment.xml!");
- controller.applications().verifyApplicationIdentityConfiguration(TenantName.from(tenant), applicationPackage, Optional.of(getUserPrincipal(request).getIdentity()));
+ controller.applications().verifyApplicationIdentityConfiguration(TenantName.from(tenant),
+ applicationPackage,
+ Optional.of(getUserPrincipal(request).getIdentity()));
return JobControllerApiHandlerHelper.submitResponse(controller.jobController(),
tenant,
@@ -1332,7 +1332,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
sourceRevision,
authorEmail,
projectId,
- applicationPackage.zippedContent(),
+ applicationPackage,
dataParts.get(EnvironmentResource.APPLICATION_TEST_ZIP));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index a3dc3f1e706..7d49ffe8139 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
@@ -27,6 +28,7 @@ import com.yahoo.vespa.hosted.controller.deployment.RunLog;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.Step;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
+import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
@@ -398,19 +400,16 @@ class JobControllerApiHandlerHelper {
* @return Response with the new application version
*/
static HttpResponse submitResponse(JobController jobController, String tenant, String application,
- SourceRevision sourceRevision, String authorEmail,
- long projectId, byte[] appPackage, byte[] testPackage) {
+ SourceRevision sourceRevision, String authorEmail, long projectId,
+ ApplicationPackage applicationPackage, byte[] testPackage) {
ApplicationVersion version = jobController.submit(ApplicationId.from(tenant, application, "default"),
sourceRevision,
authorEmail,
projectId,
- appPackage,
+ applicationPackage,
testPackage);
- Slime slime = new Slime();
- Cursor responseObject = slime.setObject();
- responseObject.setString("version", version.id());
- return new SlimeJsonResponse(slime);
+ return new MessageResponse(version.toString());
}
/** Aborts any job of the given type. */
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 b7a881c672e..1199f0229b6 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
@@ -155,7 +155,8 @@ public class ControllerTest {
.region("deep-space-9")
.build();
try {
- tester.controller().jobController().submit(app1.id(), BuildJob.defaultSourceRevision, "a@b", 2, applicationPackage.zippedContent(), new byte[0]);
+ tester.controller().jobController().submit(app1.id(), BuildJob.defaultSourceRevision, "a@b",
+ 2, applicationPackage, new byte[0]);
fail("Expected exception due to illegal deployment spec.");
}
catch (IllegalArgumentException e) {
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 0499a92d05f..44a797687d4 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
@@ -170,6 +170,10 @@ public class ApplicationPackageBuilder {
return searchDefinition.getBytes(StandardCharsets.UTF_8);
}
+ private byte[] buildMeta() {
+ return "{\"compileVersion\":\"6.1\",\"buildTime\":1000}".getBytes(StandardCharsets.UTF_8);
+ }
+
public ApplicationPackage build() {
ByteArrayOutputStream zip = new ByteArrayOutputStream();
try (ZipOutputStream out = new ZipOutputStream(zip)) {
@@ -182,6 +186,9 @@ public class ApplicationPackageBuilder {
out.putNextEntry(new ZipEntry("search-definitions/test.sd"));
out.write(searchDefinition());
out.closeEntry();
+ out.putNextEntry(new ZipEntry("build-meta.json"));
+ out.write(buildMeta());
+ out.closeEntry();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
index 80cbbfa2c30..35e7aea5efe 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
@@ -89,7 +89,7 @@ public class InternalDeploymentTester {
* Submits a new application, and returns the version of the new submission.
*/
public ApplicationVersion newSubmission() {
- ApplicationVersion version = jobs.submit(appId, BuildJob.defaultSourceRevision, "a@b", 2, applicationPackage.zippedContent(), new byte[0]);
+ ApplicationVersion version = jobs.submit(appId, BuildJob.defaultSourceRevision, "a@b", 2, applicationPackage, new byte[0]);
tester.applicationStore().put(appId, version, applicationPackage.zippedContent());
tester.applicationStore().put(testerId, version, new byte[0]);
return version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index 7e6e892891a..8ebb8c108c0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -7,6 +7,7 @@ import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
+import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.Run;
@@ -64,6 +65,7 @@ import static org.junit.Assert.fail;
*/
public class JobRunnerTest {
+ private static final ApplicationPackage applicationPackage = new ApplicationPackage(new byte[0]);
private static final Versions versions = new Versions(Version.fromString("1.2.3"),
ApplicationVersion.from(new SourceRevision("repo",
"branch",
@@ -82,7 +84,7 @@ public class JobRunnerTest {
phasedExecutor(phaser), stepRunner);
ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
- jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, new byte[0], new byte[0]);
+ jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
try {
@@ -113,7 +115,7 @@ public class JobRunnerTest {
inThreadExecutor(), mappedRunner(outcomes));
ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
- jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, new byte[0], new byte[0]);
+ jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
Supplier<Run> run = () -> jobs.last(id, systemTest).get();
jobs.start(id, systemTest, versions);
@@ -197,7 +199,7 @@ public class JobRunnerTest {
Executors.newFixedThreadPool(32), waitingRunner(barrier));
ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
- jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, new byte[0], new byte[0]);
+ jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
RunId runId = new RunId(id, systemTest, 1);
jobs.start(id, systemTest, versions);
@@ -233,7 +235,7 @@ public class JobRunnerTest {
inThreadExecutor(), (id, step) -> Optional.of(running));
ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
- jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, new byte[0], new byte[0]);
+ jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
for (int i = 0; i < jobs.historyLength(); i++) {
jobs.start(id, systemTest, versions);
@@ -261,7 +263,7 @@ public class JobRunnerTest {
inThreadExecutor(), mappedRunner(outcomes));
ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
- jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, new byte[0], new byte[0]);
+ jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
tester.clock().advance(JobRunner.jobTimeout.plus(Duration.ofSeconds(1)));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index 7ab858f3081..9c01e526a50 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -11,9 +11,11 @@ import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock;
import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
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.deployment.InternalDeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
import com.yahoo.vespa.hosted.controller.integration.MetricsMock.MapContext;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
@@ -204,6 +206,18 @@ public class MetricsReporterTest {
assertEquals(0, getDeploymentsFailingUpgrade(app));
}
+ @Test
+ public void testBuildTimeReporting() {
+ InternalDeploymentTester tester = new InternalDeploymentTester();
+ ApplicationVersion version = tester.deployNewSubmission();
+ assertEquals(1000, version.buildTime().get().toEpochMilli());
+
+ MetricsReporter reporter = createReporter(tester.tester().controller(), metrics, SystemName.main);
+ reporter.maintain();
+ assertEquals(tester.clock().instant().getEpochSecond() - 1,
+ getMetric(MetricsReporter.deploymentBuildAgeSeconds, tester.app()));
+ }
+
private Duration getAverageDeploymentDuration(Application application) {
return Duration.ofSeconds(getMetric(MetricsReporter.deploymentAverageDuration, application).longValue());
}
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 774caea97b0..0b337eb5380 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
@@ -10,11 +10,12 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
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.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -25,7 +26,6 @@ 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.RotationStatus;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import org.junit.Test;
@@ -70,7 +70,8 @@ public class ApplicationSerializerTest {
List<Deployment> deployments = new ArrayList<>();
ApplicationVersion applicationVersion1 = ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 31);
ApplicationVersion applicationVersion2 = ApplicationVersion
- .from(new SourceRevision("repo1", "branch1", "commit1"), 32, "a@b");
+ .from(new SourceRevision("repo1", "branch1", "commit1"), 32, "a@b",
+ Version.fromString("6.3.1"), Instant.ofEpochMilli(496));
Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, applicationVersion1, Version.fromString("1.2.3"), Instant.ofEpochMilli(3))); // One deployment without cluster info and utils
deployments.add(new Deployment(zone2, applicationVersion2, Version.fromString("1.2.3"), Instant.ofEpochMilli(5),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
index d6334a9ea86..03b432588bd 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
@@ -19,6 +19,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Optional;
@@ -78,7 +79,9 @@ public class RunSerializerTest {
"master",
"f00bad"),
123,
- "a@b"),
+ "a@b",
+ Version.fromString("6.3.1"),
+ Instant.ofEpochMilli(100)),
run.versions().targetApplication());
assertEquals(new Version(1, 2, 2), run.versions().sourcePlatform().get());
assertEquals(ApplicationVersion.from(new SourceRevision("git@github.com:user/repo.git",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json
index 75bbea6861d..cda3834d47d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json
@@ -26,6 +26,8 @@
"commit": "f00bad",
"build": 123,
"authorEmail": "a@b",
+ "compileVersion": "6.3.1",
+ "buildTime": 100,
"source": {
"platform": "1.2.2",
"repository": "git@github.com:user/repo.git",
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 d455218f4e9..30795008032 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
@@ -361,24 +361,40 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE (cancel) ongoing change
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE)
.userIdentity(HOSTED_VESPA_OPERATOR),
- new File("application-deployment-cancelled.json"));
+ "{\"message\":\"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1'\"}");
// DELETE (cancel) again is a no-op
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE)
.userIdentity(USER_ID)
.data("{\"cancel\":\"all\"}"),
- new File("application-deployment-cancelled-no-op.json"));
+ "{\"message\":\"No deployment in progress for application 'tenant1.application1' at this time\"}");
// POST pinning to a given version to an application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", POST)
.userIdentity(USER_ID)
.data("6.1.0"),
- new File("application-deployment.json"));
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
// DELETE only the pin to a given version
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", DELETE)
.userIdentity(USER_ID),
- new File("application-pin-cancelled.json"));
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1'\"}");
+
+ // POST pinning again
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", POST)
+ .userIdentity(USER_ID)
+ .data("6.1"),
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
+
+ // DELETE only the version, but leave the pin
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/platform", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for application 'tenant1.application1'\"}");
+
+ // DELETE also the pin to a given version
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'pin to current platform' to 'no change' for application 'tenant1.application1'\"}");
// POST a pause to a production job
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1/pause", POST)
@@ -435,13 +451,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
"Deactivated tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
// POST an application package and a test jar, submitting a new application for internal pipeline deployment.
- // First attempt does not have an Athenz service definition in deployment spec, and fails.
+ // First attempt does not have an Athenz service definition in deployment spec, and is accepted.
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(createApplicationSubmissionData(applicationPackage)),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Application must define an Athenz service in deployment.xml!\"}", 400);
+ "{\"message\":\"Application package version: 1.0.43-d00d, source revision of repository 'repo', branch 'master' with commit 'd00d', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- // Second attempt has a service under a different domain than the tenant of the application, and fails as well.
+ // Second attempt has a service under a different domain than the tenant of the application, and fails.
ApplicationPackage packageWithServiceForWrongDomain = new ApplicationPackageBuilder()
.environment(Environment.prod)
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN_2.getName()), AthenzService.from("service"))
@@ -461,7 +477,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(createApplicationSubmissionData(packageWithService)),
- "{\"version\":\"1.0.43-d00d\"}");
+ "{\"message\":\"Application package version: 1.0.44-d00d, source revision of repository 'repo', branch 'master' with commit 'd00d', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/badge", GET)
.userIdentity(USER_ID),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled-no-op.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled-no-op.json
deleted file mode 100644
index 91d3e64d6db..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled-no-op.json
+++ /dev/null
@@ -1 +0,0 @@
-{"message":"No deployment in progress for application 'tenant1.application1' at this time"}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled.json
deleted file mode 100644
index efca5831256..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment-cancelled.json
+++ /dev/null
@@ -1 +0,0 @@
-{"message":"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1'"}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment.json
deleted file mode 100644
index fe68f3d94a3..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-deployment.json
+++ /dev/null
@@ -1 +0,0 @@
-{"message":"Triggered pin to 6.1 for tenant1.application1"} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-pin-cancelled.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-pin-cancelled.json
deleted file mode 100644
index 62360458ce4..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-pin-cancelled.json
+++ /dev/null
@@ -1 +0,0 @@
-{"message":"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1'"}
diff --git a/document/abi-spec.json b/document/abi-spec.json
index 2b3d443d02e..79e0cdc34d0 100644
--- a/document/abi-spec.json
+++ b/document/abi-spec.json
@@ -2463,6 +2463,7 @@
"public void clear()",
"public void assign(java.lang.Object)",
"public boolean getBoolean()",
+ "public void setBoolean(boolean)",
"public java.lang.Object getWrappedValue()",
"public com.yahoo.document.DataType getDataType()",
"public void printXml(com.yahoo.document.serialization.XmlStream)",
@@ -4740,6 +4741,7 @@
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.Array)",
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.MapFieldValue)",
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)",
+ "public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)",
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.CollectionFieldValue)",
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.DoubleFieldValue)",
"public abstract void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.FloatFieldValue)",
@@ -4773,6 +4775,7 @@
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.Array)",
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.MapFieldValue)",
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)",
+ "public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)",
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.CollectionFieldValue)",
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.DoubleFieldValue)",
"public abstract void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.FloatFieldValue)",
@@ -4878,6 +4881,7 @@
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.Array)",
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.MapFieldValue)",
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.CollectionFieldValue)",
+ "public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)",
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)",
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.DoubleFieldValue)",
"public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.FloatFieldValue)",
@@ -4917,7 +4921,8 @@
],
"methods": [
"public void <init>(com.yahoo.document.DocumentTypeManager, com.yahoo.io.GrowableByteBuffer)",
- "public void read(com.yahoo.document.DocumentUpdate)"
+ "public void read(com.yahoo.document.DocumentUpdate)",
+ "public void read(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)"
],
"fields": []
},
@@ -4937,6 +4942,7 @@
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.Array)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.MapFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)",
+ "public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.CollectionFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.DoubleFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.FloatFieldValue)",
@@ -4982,7 +4988,8 @@
"public void write(com.yahoo.document.DocumentUpdate)",
"public void write(com.yahoo.document.fieldpathupdate.FieldPathUpdate)",
"public void write(com.yahoo.document.fieldpathupdate.AssignFieldPathUpdate)",
- "public void write(com.yahoo.document.fieldpathupdate.AddFieldPathUpdate)"
+ "public void write(com.yahoo.document.fieldpathupdate.AddFieldPathUpdate)",
+ "public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)"
],
"fields": []
},
@@ -5003,6 +5010,7 @@
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.Array)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.MapFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.ByteFieldValue)",
+ "public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.BoolFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.CollectionFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.DoubleFieldValue)",
"public void write(com.yahoo.vespa.objects.FieldBase, com.yahoo.document.datatypes.FloatFieldValue)",
diff --git a/document/src/main/java/com/yahoo/document/datatypes/BoolFieldValue.java b/document/src/main/java/com/yahoo/document/datatypes/BoolFieldValue.java
index 2a48b550658..189c275809a 100644
--- a/document/src/main/java/com/yahoo/document/datatypes/BoolFieldValue.java
+++ b/document/src/main/java/com/yahoo/document/datatypes/BoolFieldValue.java
@@ -60,6 +60,7 @@ public class BoolFieldValue extends FieldValue {
public boolean getBoolean() {
return value;
}
+ public void setBoolean(boolean value) { this.value = value; }
@Override
public Object getWrappedValue() {
diff --git a/document/src/main/java/com/yahoo/document/json/DocumentUpdateJsonSerializer.java b/document/src/main/java/com/yahoo/document/json/DocumentUpdateJsonSerializer.java
index 6adae27cadc..e4ca20a8e32 100644
--- a/document/src/main/java/com/yahoo/document/json/DocumentUpdateJsonSerializer.java
+++ b/document/src/main/java/com/yahoo/document/json/DocumentUpdateJsonSerializer.java
@@ -9,6 +9,7 @@ import com.yahoo.document.DocumentUpdate;
import com.yahoo.document.FieldPath;
import com.yahoo.document.annotation.AnnotationReference;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -285,6 +286,11 @@ public class DocumentUpdateJsonSerializer
}
@Override
+ public void write(FieldBase field, BoolFieldValue value) {
+ serializeBoolField(generator, field, value);
+ }
+
+ @Override
public <T extends FieldValue> void write(FieldBase field, CollectionFieldValue<T> value) {
serializeCollectionField(this, generator, field, value);
}
diff --git a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
index afe14cf1e6a..55e7dc3c1b3 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
@@ -8,6 +8,7 @@ import com.yahoo.document.Field;
import com.yahoo.document.PositionDataType;
import com.yahoo.document.PrimitiveDataType;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -234,6 +235,10 @@ public class JsonSerializationHelper {
serializeByte(generator, field, value.getByte());
}
+ public static void serializeBoolField(JsonGenerator generator, FieldBase field, BoolFieldValue value) {
+ serializeBool(generator, field, value.getBoolean());
+ }
+
public static void serializePredicateField(JsonGenerator generator, FieldBase field, PredicateFieldValue value){
serializeString(generator, field, value.toString());
}
@@ -252,6 +257,11 @@ public class JsonSerializationHelper {
wrapIOException(() -> generator.writeNumber(value));
}
+ public static void serializeBool(JsonGenerator generator, FieldBase field, boolean value) {
+ fieldNameIfNotNull(generator, field);
+ wrapIOException(() -> generator.writeBoolean(value));
+ }
+
public static void serializeShort(JsonGenerator generator, FieldBase field, short value) {
fieldNameIfNotNull(generator, field);
wrapIOException(() -> generator.writeNumber(value));
diff --git a/document/src/main/java/com/yahoo/document/json/JsonWriter.java b/document/src/main/java/com/yahoo/document/json/JsonWriter.java
index ecec34c5d3f..ab0884a54a3 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonWriter.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonWriter.java
@@ -9,6 +9,7 @@ import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
import com.yahoo.document.annotation.AnnotationReference;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -140,6 +141,11 @@ public class JsonWriter implements DocumentWriter {
}
@Override
+ public void write(FieldBase field, BoolFieldValue value) {
+ serializeBoolField(generator, field, value);
+ }
+
+ @Override
public <T extends FieldValue> void write(FieldBase field, CollectionFieldValue<T> value) {
serializeCollectionField(this, generator, field, value);
}
diff --git a/document/src/main/java/com/yahoo/document/serialization/FieldReader.java b/document/src/main/java/com/yahoo/document/serialization/FieldReader.java
index 11fc0c314af..0b1500ed6ba 100644
--- a/document/src/main/java/com/yahoo/document/serialization/FieldReader.java
+++ b/document/src/main/java/com/yahoo/document/serialization/FieldReader.java
@@ -6,7 +6,24 @@ package com.yahoo.document.serialization;
import com.yahoo.document.Document;
import com.yahoo.document.annotation.AnnotationReference;
-import com.yahoo.document.datatypes.*;
+import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
+import com.yahoo.document.datatypes.ByteFieldValue;
+import com.yahoo.document.datatypes.CollectionFieldValue;
+import com.yahoo.document.datatypes.DoubleFieldValue;
+import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.document.datatypes.FloatFieldValue;
+import com.yahoo.document.datatypes.IntegerFieldValue;
+import com.yahoo.document.datatypes.LongFieldValue;
+import com.yahoo.document.datatypes.MapFieldValue;
+import com.yahoo.document.datatypes.PredicateFieldValue;
+import com.yahoo.document.datatypes.Raw;
+import com.yahoo.document.datatypes.ReferenceFieldValue;
+import com.yahoo.document.datatypes.StringFieldValue;
+import com.yahoo.document.datatypes.Struct;
+import com.yahoo.document.datatypes.StructuredFieldValue;
+import com.yahoo.document.datatypes.TensorFieldValue;
+import com.yahoo.document.datatypes.WeightedSet;
import com.yahoo.vespa.objects.Deserializer;
import com.yahoo.vespa.objects.FieldBase;
@@ -54,6 +71,14 @@ public interface FieldReader extends Deserializer {
void read(FieldBase field, ByteFieldValue value);
/**
+ * Read in the value of byte field
+ *
+ * @param field - field description (name and data type)
+ * @param value - field value
+ */
+ void read(FieldBase field, BoolFieldValue value);
+
+ /**
* Read in the value of collection field
*
* @param field - field description (name and data type)
diff --git a/document/src/main/java/com/yahoo/document/serialization/FieldWriter.java b/document/src/main/java/com/yahoo/document/serialization/FieldWriter.java
index 243d25c3950..63a6d997b04 100644
--- a/document/src/main/java/com/yahoo/document/serialization/FieldWriter.java
+++ b/document/src/main/java/com/yahoo/document/serialization/FieldWriter.java
@@ -4,6 +4,7 @@ package com.yahoo.document.serialization;
import com.yahoo.document.Document;
import com.yahoo.document.annotation.AnnotationReference;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -78,6 +79,16 @@ public interface FieldWriter extends Serializer {
void write(FieldBase field, ByteFieldValue value);
/**
+ * Write out the value of byte field
+ *
+ * @param field
+ * field description (name and data type)
+ * @param value
+ * field value
+ */
+ void write(FieldBase field, BoolFieldValue value);
+
+ /**
* Write out the value of collection field
*
* @param field
diff --git a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializer42.java b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializer42.java
index 6ec7a1e2b21..7ff5729ca39 100644
--- a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializer42.java
+++ b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializer42.java
@@ -27,6 +27,7 @@ import com.yahoo.document.annotation.SpanNode;
import com.yahoo.document.annotation.SpanNodeParent;
import com.yahoo.document.annotation.SpanTree;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -111,6 +112,7 @@ public class VespaDocumentDeserializer42 extends VespaDocumentSerializer42 imple
public void read(Document document) {
read(null, document);
}
+
public void read(FieldBase field, Document doc) {
// Verify that we have correct version
@@ -219,6 +221,10 @@ public class VespaDocumentDeserializer42 extends VespaDocumentSerializer42 imple
public <T extends FieldValue> void read(FieldBase field, CollectionFieldValue<T> value) {
throw new IllegalArgumentException("read not implemented yet.");
}
+ @Override
+ public void read(FieldBase field, BoolFieldValue value) {
+ value.setBoolean((getByte(null) != 0));
+ }
public void read(FieldBase field, ByteFieldValue value) { value.assign(getByte(null)); }
public void read(FieldBase field, DoubleFieldValue value) { value.assign(getDouble(null)); }
public void read(FieldBase field, FloatFieldValue value) { value.assign(getFloat(null)); }
diff --git a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializerHead.java b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializerHead.java
index 44a1ca6e749..40aec94aec6 100644
--- a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializerHead.java
+++ b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentDeserializerHead.java
@@ -4,9 +4,11 @@ package com.yahoo.document.serialization;
import com.yahoo.document.DocumentId;
import com.yahoo.document.DocumentTypeManager;
import com.yahoo.document.DocumentUpdate;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
import com.yahoo.document.update.FieldUpdate;
import com.yahoo.io.GrowableByteBuffer;
+import com.yahoo.vespa.objects.FieldBase;
/**
* Class used for de-serializing documents on the current head document format.
@@ -42,4 +44,8 @@ public class VespaDocumentDeserializerHead extends VespaDocumentDeserializer42 {
}
}
+ @Override
+ public void read(FieldBase field, BoolFieldValue value) {
+ value.setBoolean((getByte(null) != 0));
+ }
}
diff --git a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializer42.java b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializer42.java
index 6a07e04a621..581c7df8aee 100644
--- a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializer42.java
+++ b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializer42.java
@@ -20,6 +20,7 @@ import com.yahoo.document.annotation.SpanList;
import com.yahoo.document.annotation.SpanNode;
import com.yahoo.document.annotation.SpanTree;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -57,7 +58,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.logging.Logger;
import static com.yahoo.text.Utf8.calculateBytePositions;
@@ -168,16 +168,17 @@ public class VespaDocumentSerializer42 extends BufferSerializer implements Docum
}
}
- /**
- * Write out the value of byte field
- *
- * @param field - field description (name and data type)
- * @param value - field value
- */
+ @Override
public void write(FieldBase field, ByteFieldValue value) {
buf.put(value.getByte());
}
+ @Override
+ public void write(FieldBase field, BoolFieldValue value) {
+ byte v = value.getBoolean() ? (byte)1 : (byte)0;
+ buf.put(v);
+ }
+
/**
* Write out the value of collection field
*
diff --git a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializerHead.java b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializerHead.java
index ae995371125..92bce41ba8c 100644
--- a/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializerHead.java
+++ b/document/src/main/java/com/yahoo/document/serialization/VespaDocumentSerializerHead.java
@@ -2,11 +2,13 @@
package com.yahoo.document.serialization;
import com.yahoo.document.DocumentUpdate;
+import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.fieldpathupdate.AddFieldPathUpdate;
import com.yahoo.document.fieldpathupdate.AssignFieldPathUpdate;
import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
import com.yahoo.document.update.FieldUpdate;
import com.yahoo.io.GrowableByteBuffer;
+import com.yahoo.vespa.objects.FieldBase;
/**
* Class used for serializing documents on the current head document format.
@@ -70,4 +72,9 @@ public class VespaDocumentSerializerHead extends VespaDocumentSerializer42 {
write((FieldPathUpdate)update);
update.getNewValues().serialize(this);
}
+
+ @Override
+ public void write(FieldBase field, ByteFieldValue value) {
+ buf.put(value.getByte());
+ }
}
diff --git a/document/src/main/java/com/yahoo/document/serialization/XmlDocumentWriter.java b/document/src/main/java/com/yahoo/document/serialization/XmlDocumentWriter.java
index 768ec879ce1..0d6b0cae926 100644
--- a/document/src/main/java/com/yahoo/document/serialization/XmlDocumentWriter.java
+++ b/document/src/main/java/com/yahoo/document/serialization/XmlDocumentWriter.java
@@ -7,6 +7,7 @@ import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
import com.yahoo.document.annotation.AnnotationReference;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
@@ -137,6 +138,11 @@ public final class XmlDocumentWriter implements DocumentWriter {
}
@Override
+ public void write(FieldBase field, BoolFieldValue value) {
+ singleValueTag(field, value);
+ }
+
+ @Override
public <T extends FieldValue> void write(FieldBase field,
CollectionFieldValue<T> value) {
buffer.beginTag(field.getName());
diff --git a/document/src/main/java/com/yahoo/vespaxmlparser/VespaXMLFieldReader.java b/document/src/main/java/com/yahoo/vespaxmlparser/VespaXMLFieldReader.java
index 8aa34ae9bba..737371f2375 100644
--- a/document/src/main/java/com/yahoo/vespaxmlparser/VespaXMLFieldReader.java
+++ b/document/src/main/java/com/yahoo/vespaxmlparser/VespaXMLFieldReader.java
@@ -298,6 +298,19 @@ public class VespaXMLFieldReader extends VespaXMLReader implements FieldReader {
}
}
+ public void read(FieldBase field, BoolFieldValue value) {
+ try {
+ String dataParsed = reader.getElementText();
+ try {
+ value.assign(dataParsed);
+ } catch (Exception e) {
+ throw newDeserializeException(field, e.getMessage());
+ }
+ } catch (XMLStreamException e) {
+ throw newException(field, e);
+ }
+ }
+
public void read(FieldBase field, DoubleFieldValue value) {
try {
String dataParsed = reader.getElementText();
diff --git a/document/src/test/java/com/yahoo/document/DocumentSerializationTestCase.java b/document/src/test/java/com/yahoo/document/DocumentSerializationTestCase.java
index e45da62353d..7d1992225e4 100644
--- a/document/src/test/java/com/yahoo/document/DocumentSerializationTestCase.java
+++ b/document/src/test/java/com/yahoo/document/DocumentSerializationTestCase.java
@@ -4,6 +4,7 @@ package com.yahoo.document;
import com.yahoo.compress.CompressionType;
import com.yahoo.document.annotation.AbstractTypesTest;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
import com.yahoo.document.datatypes.FloatFieldValue;
@@ -72,6 +73,7 @@ public class DocumentSerializationTestCase extends AbstractTypesTest {
docType.addField(new Field("rawfield", DataType.RAW, false));
docType.addField(new Field("doublefield", DataType.DOUBLE, false));
docType.addField(new Field("bytefield", DataType.BYTE, false));
+ docType.addField(new Field("boolfield", DataType.BOOL, false));
DataType arrayOfFloatDataType = new ArrayDataType(DataType.FLOAT);
docType.addField(new Field("arrayoffloatfield", arrayOfFloatDataType, false));
DataType arrayOfArrayOfFloatDataType = new ArrayDataType(arrayOfFloatDataType);
@@ -94,6 +96,7 @@ public class DocumentSerializationTestCase extends AbstractTypesTest {
doc.setFieldValue("longfield", new LongFieldValue(398420092938472983l));
doc.setFieldValue("doublefield", new DoubleFieldValue(98374532.398820));
doc.setFieldValue("bytefield", new ByteFieldValue(254));
+ doc.setFieldValue("boolfield", new BoolFieldValue(true));
byte[] rawData = "RAW DATA".getBytes();
assertEquals(8, rawData.length);
doc.setFieldValue(docType.getField("rawfield"),new Raw(ByteBuffer.wrap("RAW DATA".getBytes())));
@@ -176,12 +179,12 @@ public class DocumentSerializationTestCase extends AbstractTypesTest {
assertEquals(new StringFieldValue("This is a string."), doc.getFieldValue("stringfield"));
assertEquals(new LongFieldValue(398420092938472983l), doc.getFieldValue("longfield"));
assertEquals(98374532.398820, ((DoubleFieldValue)doc.getFieldValue("doublefield")).getDouble(), 1E-6);
- assertEquals(new ByteFieldValue((byte)254),
- doc.getFieldValue("bytefield"));
+ assertEquals(new ByteFieldValue((byte)254), doc.getFieldValue("bytefield"));
+ // Todo add cpp serialization
+ // assertEquals(new BoolFieldValue(true), doc.getFieldValue("boolfield"));
ByteBuffer bbuffer = ((Raw)doc.getFieldValue("rawfield")).getByteBuffer();
if (!Arrays.equals("RAW DATA".getBytes(), bbuffer.array())) {
- System.err.println("Expected 'RAW DATA' but got '"
- + new String(bbuffer.array()) + "'.");
+ System.err.println("Expected 'RAW DATA' but got '" + new String(bbuffer.array()) + "'.");
assertTrue(false);
}
if (test.version > 6) {
diff --git a/document/src/test/java/com/yahoo/document/DocumentTestCase.java b/document/src/test/java/com/yahoo/document/DocumentTestCase.java
index 6a2147d6f15..3eebc4396e8 100644
--- a/document/src/test/java/com/yahoo/document/DocumentTestCase.java
+++ b/document/src/test/java/com/yahoo/document/DocumentTestCase.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.compress.CompressionType;
import com.yahoo.document.datatypes.Array;
+import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
import com.yahoo.document.datatypes.FieldPathIteratorHandler;
@@ -89,6 +90,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
" </item>\n" +
" </mapfield>\n" +
SERTEST_DOC_AS_XML_SUNNYVALE +
+ " <myboolfield>true</myboolfield>\n" +
"</document>\n";
static DocumentTypeManager setUpCppDocType() {
@@ -124,6 +126,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
sertestDocType.addField(new Field("docindoc", 882, docInDocType, false));
sertestDocType.addField(new Field("mapfield", 883, new MapDataType(DataType.STRING, DataType.STRING), false));
sertestDocType.addField(new Field("myposfield", 884, PositionDataType.INSTANCE, false));
+ sertestDocType.addField(new Field("myboolfield", 885, DataType.BOOL, false));
docMan.registerDocumentType(sertestDocType);
}
@@ -165,6 +168,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
map.put(new StringFieldValue("foo1"), new StringFieldValue("bar1"));
map.put(new StringFieldValue("foo2"), new StringFieldValue("bar2"));
doc.setFieldValue("mapfield", map);
+ doc.setFieldValue("myboolfield", new BoolFieldValue(true));
return doc;
}
@@ -776,12 +780,12 @@ public class DocumentTestCase extends DocumentTestCaseBase {
doc.setFieldValue("wsfield", wset);
MapFieldValue<StringFieldValue, StringFieldValue> map =
- new MapFieldValue<>(
- (MapDataType)doc.getDataType().getField("mapfield").getDataType());
+ new MapFieldValue<>((MapDataType)doc.getDataType().getField("mapfield").getDataType());
map.put(new StringFieldValue("foo1"), new StringFieldValue("bar1"));
map.put(new StringFieldValue("foo2"), new StringFieldValue("bar2"));
doc.setFieldValue("mapfield", map);
+ doc.setFieldValue("boolfield", new BoolFieldValue(true));
doc.setFieldValue("bytefield", new ByteFieldValue((byte)254));
doc.setFieldValue("rawfield", new Raw(ByteBuffer.wrap("RAW DATA".getBytes())));
doc.setFieldValue("intfield", new IntegerFieldValue(5));
@@ -841,6 +845,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
Document doc2 = docMan.createDocument(data);
+ assertEquals(doc.getFieldValue("myboolfield"), doc2.getFieldValue("myboolfield"));
assertEquals(doc.getFieldValue("mailid"), doc2.getFieldValue("mailid"));
assertEquals(doc.getFieldValue("date"), doc2.getFieldValue("date"));
assertEquals(doc.getFieldValue("from"), doc2.getFieldValue("from"));
@@ -1229,7 +1234,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
assertEquals(fields.get("date"), -2013512400);
assertThat(fields.get("docindoc"), instanceOf(Map.class));
assertThat(fields.keySet(),
- containsInAnyOrder("mailid", "date", "attachmentcount", "rawfield", "weightedfield", "docindoc", "mapfield"));
+ containsInAnyOrder("mailid", "date", "attachmentcount", "rawfield", "weightedfield", "docindoc", "mapfield", "myboolfield"));
}
}
diff --git a/document/src/tests/data/crossplatform-java-cpp-document.cfg b/document/src/tests/data/crossplatform-java-cpp-document.cfg
index f12dae77fc0..134d31b1831 100644
--- a/document/src/tests/data/crossplatform-java-cpp-document.cfg
+++ b/document/src/tests/data/crossplatform-java-cpp-document.cfg
@@ -83,7 +83,7 @@ datatype[8].weightedsettype[0]
datatype[8].structtype[1]
datatype[8].structtype[0].name serializetest.body
datatype[8].structtype[0].version 0
-datatype[8].structtype[0].field[10]
+datatype[8].structtype[0].field[11]
datatype[8].structtype[0].field[0].name intfield
datatype[8].structtype[0].field[0].id[0]
datatype[8].structtype[0].field[0].datatype 0
@@ -114,6 +114,9 @@ datatype[8].structtype[0].field[8].datatype 437829
datatype[8].structtype[0].field[9].name mapfield
datatype[8].structtype[0].field[9].id[0]
datatype[8].structtype[0].field[9].datatype 9999
+datatype[8].structtype[0].field[10].name boolfield
+datatype[8].structtype[0].field[10].id[0]
+datatype[8].structtype[0].field[10].datatype 6
datatype[8].documenttype[0]
datatype[9].id 1306012852
datatype[9].arraytype[0]
diff --git a/document/src/tests/data/serializejava-compressed.dat b/document/src/tests/data/serializejava-compressed.dat
index 453abef81f1..0f6cb55ff85 100644
--- a/document/src/tests/data/serializejava-compressed.dat
+++ b/document/src/tests/data/serializejava-compressed.dat
Binary files differ
diff --git a/document/src/tests/data/serializejava.dat b/document/src/tests/data/serializejava.dat
index 3dd9f8fcd52..53ef6a8fbc2 100644
--- a/document/src/tests/data/serializejava.dat
+++ b/document/src/tests/data/serializejava.dat
Binary files differ
diff --git a/document/src/vespa/document/datatype/datatype.cpp b/document/src/vespa/document/datatype/datatype.cpp
index 8d2721a4d9b..8c17ca4e383 100644
--- a/document/src/vespa/document/datatype/datatype.cpp
+++ b/document/src/vespa/document/datatype/datatype.cpp
@@ -21,6 +21,7 @@ NumericDataType INT_OBJ(DataType::T_INT);
NumericDataType LONG_OBJ(DataType::T_LONG);
NumericDataType FLOAT_OBJ(DataType::T_FLOAT);
NumericDataType DOUBLE_OBJ(DataType::T_DOUBLE);
+NumericDataType BOOL_OBJ(DataType::T_BOOL);
PrimitiveDataType STRING_OBJ(DataType::T_STRING);
PrimitiveDataType RAW_OBJ(DataType::T_RAW);
DocumentType DOCUMENT_OBJ("document");
@@ -37,6 +38,7 @@ const DataType *const DataType::INT(&INT_OBJ);
const DataType *const DataType::LONG(&LONG_OBJ);
const DataType *const DataType::FLOAT(&FLOAT_OBJ);
const DataType *const DataType::DOUBLE(&DOUBLE_OBJ);
+const DataType *const DataType::BOOL(&BOOL_OBJ);
const DataType *const DataType::STRING(&STRING_OBJ);
const DataType *const DataType::RAW(&RAW_OBJ);
const DocumentType *const DataType::DOCUMENT(&DOCUMENT_OBJ);
@@ -71,6 +73,7 @@ DataType2FieldValueId::DataType2FieldValueId()
_type2FieldValueId[DataType::T_LONG] = LongFieldValue::classId;
_type2FieldValueId[DataType::T_FLOAT] = FloatFieldValue::classId;
_type2FieldValueId[DataType::T_DOUBLE] = DoubleFieldValue::classId;
+ _type2FieldValueId[DataType::T_BOOL] = BoolFieldValue::classId;
_type2FieldValueId[DataType::T_STRING] = StringFieldValue::classId;
_type2FieldValueId[DataType::T_RAW] = RawFieldValue::classId;
_type2FieldValueId[DataType::T_URI] = StringFieldValue::classId;
@@ -103,6 +106,7 @@ DataType::getDefaultDataTypes()
types.push_back(LONG);
types.push_back(FLOAT);
types.push_back(DOUBLE);
+ types.push_back(BOOL);
types.push_back(STRING);
types.push_back(RAW);
types.push_back(DOCUMENT);
diff --git a/document/src/vespa/document/datatype/datatype.h b/document/src/vespa/document/datatype/datatype.h
index 4dd5d6aae64..723e7c69ed6 100644
--- a/document/src/vespa/document/datatype/datatype.h
+++ b/document/src/vespa/document/datatype/datatype.h
@@ -66,6 +66,7 @@ public:
T_RAW = 3,
T_LONG = 4,
T_DOUBLE = 5,
+ T_BOOL = 6,
T_DOCUMENT = 8, // Type of super document type Document.0 that all documents inherit.
// T_TIMESTAMP = 9, // Not used anymore, Id should probably not be reused
T_URI = 10,
@@ -88,6 +89,7 @@ public:
static const DataType *const LONG;
static const DataType *const FLOAT;
static const DataType *const DOUBLE;
+ static const DataType *const BOOL;
static const DataType *const STRING;
static const DataType *const RAW;
static const DocumentType *const DOCUMENT;
@@ -108,7 +110,7 @@ public:
* Create a field value using this datatype.
*/
virtual std::unique_ptr<FieldValue> createFieldValue() const = 0;
- virtual DataType* clone() const override = 0;
+ DataType* clone() const override = 0;
/**
* Whether another datatype is a supertype of this one. Document types may
diff --git a/document/src/vespa/document/datatype/primitivedatatype.cpp b/document/src/vespa/document/datatype/primitivedatatype.cpp
index e48e4464acf..7ec47f52d9c 100644
--- a/document/src/vespa/document/datatype/primitivedatatype.cpp
+++ b/document/src/vespa/document/datatype/primitivedatatype.cpp
@@ -23,6 +23,7 @@ namespace {
const char *Double = "Double";
const char *Uri = "Uri";
const char *Byte = "Byte";
+ const char *Bool = "Bool";
const char *Predicate = "Predicate";
const char *Tensor = "Tensor";
@@ -37,6 +38,7 @@ namespace {
case DataType::T_DOUBLE: return Double;
case DataType::T_URI: return Uri;
case DataType::T_BYTE: return Byte;
+ case DataType::T_BOOL: return Bool;
case DataType::T_PREDICATE: return Predicate;
case DataType::T_TENSOR: return Tensor;
default:
@@ -56,16 +58,17 @@ FieldValue::UP
PrimitiveDataType::createFieldValue() const
{
switch (getId()) {
- case T_INT: return FieldValue::UP(new IntFieldValue);
- case T_SHORT: return FieldValue::UP(new ShortFieldValue);
- case T_FLOAT: return FieldValue::UP(new FloatFieldValue);
- case T_URI: return FieldValue::UP(new StringFieldValue);
- case T_STRING: return FieldValue::UP(new StringFieldValue);
- case T_RAW: return FieldValue::UP(new RawFieldValue);
- case T_LONG: return FieldValue::UP(new LongFieldValue);
- case T_DOUBLE: return FieldValue::UP(new DoubleFieldValue);
- case T_BYTE: return FieldValue::UP(new ByteFieldValue);
- case T_PREDICATE: return FieldValue::UP(new PredicateFieldValue);
+ case T_INT: return std::make_unique<IntFieldValue>();
+ case T_SHORT: return std::make_unique<ShortFieldValue>();
+ case T_FLOAT: return std::make_unique<FloatFieldValue>();
+ case T_URI: return std::make_unique<StringFieldValue>();
+ case T_STRING: return std::make_unique<StringFieldValue>();
+ case T_RAW: return std::make_unique<RawFieldValue>();
+ case T_LONG: return std::make_unique<LongFieldValue>();
+ case T_DOUBLE: return std::make_unique<DoubleFieldValue>();
+ case T_BOOL: return std::make_unique<BoolFieldValue>();
+ case T_BYTE: return std::make_unique<ByteFieldValue>();
+ case T_PREDICATE: return std::make_unique<PredicateFieldValue>();
case T_TENSOR: return std::make_unique<TensorFieldValue>();
}
LOG_ABORT("getId() returned value out of range");
diff --git a/document/src/vespa/document/fieldvalue/CMakeLists.txt b/document/src/vespa/document/fieldvalue/CMakeLists.txt
index 0b161cff08a..dcef1bc8305 100644
--- a/document/src/vespa/document/fieldvalue/CMakeLists.txt
+++ b/document/src/vespa/document/fieldvalue/CMakeLists.txt
@@ -3,6 +3,7 @@ vespa_add_library(document_fieldvalues OBJECT
SOURCES
annotationreferencefieldvalue.cpp
arrayfieldvalue.cpp
+ boolfieldvalue.cpp
bytefieldvalue.cpp
collectionfieldvalue.cpp
document.cpp
diff --git a/document/src/vespa/document/fieldvalue/boolfieldvalue.cpp b/document/src/vespa/document/fieldvalue/boolfieldvalue.cpp
new file mode 100644
index 00000000000..88ad1ddd11b
--- /dev/null
+++ b/document/src/vespa/document/fieldvalue/boolfieldvalue.cpp
@@ -0,0 +1,85 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "boolfieldvalue.h"
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/vespalib/util/xmlstream.h>
+#include <ostream>
+
+using namespace vespalib::xml;
+
+namespace document {
+
+IMPLEMENT_IDENTIFIABLE(BoolFieldValue, FieldValue);
+
+BoolFieldValue::BoolFieldValue(bool value)
+ : _value(value), _altered(false) {
+}
+
+BoolFieldValue::~BoolFieldValue() = default;
+
+FieldValue &BoolFieldValue::assign(const FieldValue &rhs) {
+ if (rhs.inherits(BoolFieldValue::classId)) {
+ operator=(static_cast<const BoolFieldValue &>(rhs));
+ return *this;
+ } else {
+ _altered = true;
+ return FieldValue::assign(rhs);
+ }
+}
+
+int BoolFieldValue::compare(const FieldValue&rhs) const {
+ int diff = FieldValue::compare(rhs);
+ if (diff != 0) return diff;
+ const BoolFieldValue &o = static_cast<const BoolFieldValue &>(rhs);
+ return (_value == o._value) ? 0 : _value ? 1 : -1;
+}
+
+void BoolFieldValue::printXml(XmlOutputStream& out) const {
+ out << XmlContent(getAsString());
+}
+
+void BoolFieldValue::print(std::ostream& out, bool, const std::string&) const {
+ out << (_value ? "true" : "false") << "\n";
+}
+
+const DataType *
+BoolFieldValue::getDataType() const {
+ return DataType::BOOL;
+}
+
+bool
+BoolFieldValue::hasChanged() const {
+ return _altered;
+}
+
+FieldValue *
+BoolFieldValue::clone() const {
+ return new BoolFieldValue(*this);
+}
+
+char
+BoolFieldValue::getAsByte() const {
+ return _value ? 1 : 0;
+}
+int32_t
+BoolFieldValue::getAsInt() const {
+ return _value ? 1 : 0;
+}
+int64_t
+BoolFieldValue::getAsLong() const {
+ return _value ? 1 : 0;
+}
+float
+BoolFieldValue::getAsFloat() const {
+ return _value ? 1 : 0;
+}
+double
+BoolFieldValue::getAsDouble() const {
+ return _value ? 1 : 0;
+}
+vespalib::string
+BoolFieldValue::getAsString() const {
+ return _value ? "true" : "false";
+}
+
+} // namespace document
diff --git a/document/src/vespa/document/fieldvalue/boolfieldvalue.h b/document/src/vespa/document/fieldvalue/boolfieldvalue.h
new file mode 100644
index 00000000000..689bd3f4d53
--- /dev/null
+++ b/document/src/vespa/document/fieldvalue/boolfieldvalue.h
@@ -0,0 +1,47 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "fieldvalue.h"
+
+namespace document {
+
+/**
+ * Represent the value in a filed of type 'bool' which can be either true or false.
+ **/
+class BoolFieldValue : public FieldValue {
+ bool _value;
+ bool _altered;
+
+public:
+ BoolFieldValue(bool value=false);
+ ~BoolFieldValue() override;
+
+ void accept(FieldValueVisitor &visitor) override { visitor.visit(*this); }
+ void accept(ConstFieldValueVisitor &visitor) const override { visitor.visit(*this); }
+
+ FieldValue *clone() const override;
+ int compare(const FieldValue &rhs) const override;
+
+ void printXml(XmlOutputStream &out) const override;
+ void print(std::ostream &out, bool verbose, const std::string &indent) const override;
+
+ const DataType *getDataType() const override;
+ bool hasChanged() const override;
+
+ bool getValue() const { return _value; }
+ void setValue(bool v) { _value = v; }
+
+ FieldValue &assign(const FieldValue &rhs) override;
+
+ char getAsByte() const override;
+ int32_t getAsInt() const override;
+ int64_t getAsLong() const override;
+ float getAsFloat() const override;
+ double getAsDouble() const override;
+ vespalib::string getAsString() const override;
+
+ DECLARE_IDENTIFIABLE(BoolFieldValue);
+};
+
+}
diff --git a/document/src/vespa/document/fieldvalue/document.cpp b/document/src/vespa/document/fieldvalue/document.cpp
index 7acc7e97be9..d915d9fd66d 100644
--- a/document/src/vespa/document/fieldvalue/document.cpp
+++ b/document/src/vespa/document/fieldvalue/document.cpp
@@ -10,6 +10,7 @@
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/document/util/serializableexceptions.h>
#include <vespa/document/base/exceptions.h>
+#include <vespa/document/fieldset/fieldsets.h>
#include <vespa/document/util/bytebuffer.h>
#include <vespa/vespalib/util/xmlstream.h>
#include <sstream>
diff --git a/document/src/vespa/document/fieldvalue/fieldvalues.h b/document/src/vespa/document/fieldvalue/fieldvalues.h
index 6d438bcd0d5..cc665cee95c 100644
--- a/document/src/vespa/document/fieldvalue/fieldvalues.h
+++ b/document/src/vespa/document/fieldvalue/fieldvalues.h
@@ -2,18 +2,19 @@
#pragma once
-#include <vespa/document/fieldvalue/arrayfieldvalue.h>
-#include <vespa/document/fieldvalue/bytefieldvalue.h>
-#include <vespa/document/fieldvalue/document.h>
-#include <vespa/document/fieldvalue/doublefieldvalue.h>
-#include <vespa/document/fieldvalue/floatfieldvalue.h>
-#include <vespa/document/fieldvalue/intfieldvalue.h>
-#include <vespa/document/fieldvalue/longfieldvalue.h>
-#include <vespa/document/fieldvalue/mapfieldvalue.h>
-#include <vespa/document/fieldvalue/predicatefieldvalue.h>
-#include <vespa/document/fieldvalue/rawfieldvalue.h>
-#include <vespa/document/fieldvalue/shortfieldvalue.h>
-#include <vespa/document/fieldvalue/stringfieldvalue.h>
-#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
-#include <vespa/document/fieldvalue/tensorfieldvalue.h>
+#include "arrayfieldvalue.h"
+#include "boolfieldvalue.h"
+#include "bytefieldvalue.h"
+#include "document.h"
+#include "doublefieldvalue.h"
+#include "floatfieldvalue.h"
+#include "intfieldvalue.h"
+#include "longfieldvalue.h"
+#include "mapfieldvalue.h"
+#include "predicatefieldvalue.h"
+#include "rawfieldvalue.h"
+#include "shortfieldvalue.h"
+#include "stringfieldvalue.h"
+#include "weightedsetfieldvalue.h"
+#include "tensorfieldvalue.h"
diff --git a/document/src/vespa/document/fieldvalue/fieldvaluevisitor.h b/document/src/vespa/document/fieldvalue/fieldvaluevisitor.h
index ef07dd25212..778b1c77023 100644
--- a/document/src/vespa/document/fieldvalue/fieldvaluevisitor.h
+++ b/document/src/vespa/document/fieldvalue/fieldvaluevisitor.h
@@ -5,6 +5,7 @@
namespace document {
class AnnotationReferenceFieldValue;
class ArrayFieldValue;
+class BoolFieldValue;
class ByteFieldValue;
class Document;
class DoubleFieldValue;
@@ -26,6 +27,7 @@ struct FieldValueVisitor {
virtual void visit(AnnotationReferenceFieldValue &value) = 0;
virtual void visit(ArrayFieldValue &value) = 0;
+ virtual void visit(BoolFieldValue &value) = 0;
virtual void visit(ByteFieldValue &value) = 0;
virtual void visit(Document &value) = 0;
virtual void visit(DoubleFieldValue &value) = 0;
@@ -48,6 +50,7 @@ struct ConstFieldValueVisitor {
virtual void visit(const AnnotationReferenceFieldValue &value) = 0;
virtual void visit(const ArrayFieldValue &value) = 0;
+ virtual void visit(const BoolFieldValue &value) = 0;
virtual void visit(const ByteFieldValue &value) = 0;
virtual void visit(const Document &value) = 0;
virtual void visit(const DoubleFieldValue &value) = 0;
diff --git a/document/src/vespa/document/fieldvalue/numericfieldvalue.hpp b/document/src/vespa/document/fieldvalue/numericfieldvalue.hpp
index 90c93e7a944..91873f021d1 100644
--- a/document/src/vespa/document/fieldvalue/numericfieldvalue.hpp
+++ b/document/src/vespa/document/fieldvalue/numericfieldvalue.hpp
@@ -27,8 +27,7 @@ NumericFieldValue<Number>::assign(const FieldValue& value)
_value = static_cast<Number>(value.getAsLong());
} else if (value.getClass().id() == IDENTIFIABLE_CLASSID(FloatFieldValue)) {
_value = static_cast<Number>(value.getAsFloat());
- } else if (value.getClass().id() == IDENTIFIABLE_CLASSID(DoubleFieldValue))
- {
+ } else if (value.getClass().id() == IDENTIFIABLE_CLASSID(DoubleFieldValue)) {
_value = static_cast<Number>(value.getAsDouble());
} else {
return FieldValue::assign(value);
diff --git a/document/src/vespa/document/fieldvalue/predicatefieldvalue.h b/document/src/vespa/document/fieldvalue/predicatefieldvalue.h
index d5c58e862f5..e0df3a38353 100644
--- a/document/src/vespa/document/fieldvalue/predicatefieldvalue.h
+++ b/document/src/vespa/document/fieldvalue/predicatefieldvalue.h
@@ -40,5 +40,4 @@ public:
DECLARE_IDENTIFIABLE(PredicateFieldValue);
};
-} // namespace document
-
+}
diff --git a/document/src/vespa/document/serialization/vespadocumentdeserializer.cpp b/document/src/vespa/document/serialization/vespadocumentdeserializer.cpp
index 4f30851ac4c..89172b0bc46 100644
--- a/document/src/vespa/document/serialization/vespadocumentdeserializer.cpp
+++ b/document/src/vespa/document/serialization/vespadocumentdeserializer.cpp
@@ -5,6 +5,7 @@
#include <vespa/document/annotation/spantree.h>
#include <vespa/document/fieldvalue/annotationreferencefieldvalue.h>
#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/boolfieldvalue.h>
#include <vespa/document/fieldvalue/bytefieldvalue.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/fieldvalue/doublefieldvalue.h>
@@ -190,12 +191,12 @@ void VespaDocumentDeserializer::read(MapFieldValue &value) {
}
namespace {
-template <typename T> struct ValueType { typedef typename T::Number Type; };
-template <> struct ValueType<ShortFieldValue> { typedef uint16_t Type; };
-template <> struct ValueType<IntFieldValue> { typedef uint32_t Type; };
-template <> struct ValueType<LongFieldValue> { typedef uint64_t Type; };
-template <>
-struct ValueType<RawFieldValue> { typedef vespalib::string Type; };
+template <typename T> struct ValueType { using Type = typename T::Number; };
+template <> struct ValueType<BoolFieldValue> { using Type = bool; };
+template <> struct ValueType<ShortFieldValue> { using Type = uint16_t; };
+template <> struct ValueType<IntFieldValue> { using Type = uint32_t; };
+template <> struct ValueType<LongFieldValue> { using Type = uint64_t; };
+template <> struct ValueType<RawFieldValue> { using Type = vespalib::string; };
template <typename T>
void readFieldValue(nbostream &input, T &value) {
@@ -214,6 +215,10 @@ stringref readAttributeString(Input &input) {
}
} // namespace
+void VespaDocumentDeserializer::read(BoolFieldValue &value) {
+ readFieldValue(_stream, value);
+}
+
void VespaDocumentDeserializer::read(ByteFieldValue &value) {
readFieldValue(_stream, value);
}
diff --git a/document/src/vespa/document/serialization/vespadocumentdeserializer.h b/document/src/vespa/document/serialization/vespadocumentdeserializer.h
index 64346428cdd..e6b490e1075 100644
--- a/document/src/vespa/document/serialization/vespadocumentdeserializer.h
+++ b/document/src/vespa/document/serialization/vespadocumentdeserializer.h
@@ -21,6 +21,7 @@ class VespaDocumentDeserializer : private FieldValueVisitor {
void visit(AnnotationReferenceFieldValue &value) override { read(value); }
void visit(ArrayFieldValue &value) override { read(value); }
+ void visit(BoolFieldValue &value) override { read(value); }
void visit(ByteFieldValue &value) override { read(value); }
void visit(Document &value) override { read(value); }
void visit(DoubleFieldValue &value) override { read(value); }
@@ -63,6 +64,7 @@ public:
void read(AnnotationReferenceFieldValue &value);
void read(ArrayFieldValue &value);
void read(MapFieldValue &value);
+ void read(BoolFieldValue &value);
void read(ByteFieldValue &value);
void read(DoubleFieldValue &value);
void read(FloatFieldValue &value);
diff --git a/document/src/vespa/document/serialization/vespadocumentserializer.cpp b/document/src/vespa/document/serialization/vespadocumentserializer.cpp
index ae03bcc0d3c..3519f38baab 100644
--- a/document/src/vespa/document/serialization/vespadocumentserializer.cpp
+++ b/document/src/vespa/document/serialization/vespadocumentserializer.cpp
@@ -6,6 +6,7 @@
#include "util.h"
#include <vespa/document/fieldvalue/annotationreferencefieldvalue.h>
#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/boolfieldvalue.h>
#include <vespa/document/fieldvalue/bytefieldvalue.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/fieldvalue/doublefieldvalue.h>
@@ -24,6 +25,7 @@
#include <vespa/document/update/updates.h>
#include <vespa/document/update/fieldpathupdates.h>
#include <vespa/document/util/bytebuffer.h>
+#include <vespa/document/fieldset/fieldsets.h>
#include <vespa/vespalib/data/slime/binary_format.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/vespalib/data/databuffer.h>
@@ -185,6 +187,10 @@ void VespaDocumentSerializer::write(const MapFieldValue &value) {
}
}
+void VespaDocumentSerializer::write(const BoolFieldValue &value) {
+ _stream << value.getValue();
+}
+
void VespaDocumentSerializer::write(const ByteFieldValue &value) {
_stream << value.getValue();
}
diff --git a/document/src/vespa/document/serialization/vespadocumentserializer.h b/document/src/vespa/document/serialization/vespadocumentserializer.h
index 818759d35b5..fde073c13b3 100644
--- a/document/src/vespa/document/serialization/vespadocumentserializer.h
+++ b/document/src/vespa/document/serialization/vespadocumentserializer.h
@@ -4,12 +4,10 @@
#include <vespa/document/fieldvalue/fieldvaluevisitor.h>
#include <vespa/document/fieldvalue/fieldvaluewriter.h>
-#include <vespa/document/fieldset/fieldsets.h>
+#include <vespa/document/fieldset/fieldset.h>
#include <vespa/document/update/updatevisitor.h>
-namespace vespalib {
- class nbostream;
-}
+namespace vespalib { class nbostream; }
namespace document {
@@ -40,6 +38,7 @@ public:
void write(const AnnotationReferenceFieldValue &value);
void write(const ArrayFieldValue &value);
void write(const MapFieldValue &map);
+ void write(const BoolFieldValue &value);
void write(const ByteFieldValue &value);
void write(const DoubleFieldValue &val);
void write(const FloatFieldValue &value);
@@ -90,6 +89,7 @@ private:
void visit(const AnnotationReferenceFieldValue &value) override { write(value); }
void visit(const ArrayFieldValue &value) override { write(value); }
+ void visit(const BoolFieldValue &value) override { write(value); }
void visit(const ByteFieldValue &value) override { write(value); }
void visit(const Document &value) override { write(value, COMPLETE); }
void visit(const DoubleFieldValue &value) override { write(value); }
diff --git a/document/src/vespa/document/util/identifiableid.h b/document/src/vespa/document/util/identifiableid.h
index c52888e8491..84cfc506bcc 100644
--- a/document/src/vespa/document/util/identifiableid.h
+++ b/document/src/vespa/document/util/identifiableid.h
@@ -19,7 +19,7 @@
#define CID_StringFieldValue DOCUMENT_CID(15)
#define CID_RawFieldValue DOCUMENT_CID(16)
//Gone with vespa 6 #define CID_ContentFieldValue DOCUMENT_CID(17)
-//Long gone #define CID_ContentMetaFieldValue DOCUMENT_CID(18)
+#define CID_BoolFieldValue DOCUMENT_CID(18)
#define CID_ArrayFieldValue DOCUMENT_CID(19)
#define CID_WeightedSetFieldValue DOCUMENT_CID(20)
#define CID_FieldMapValue DOCUMENT_CID(21)
diff --git a/flags/pom.xml b/flags/pom.xml
index 99fde3faebb..fc38676ff20 100644
--- a/flags/pom.xml
+++ b/flags/pom.xml
@@ -38,6 +38,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespalog</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
index 581ec599aab..7d84efa52b2 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -17,8 +17,7 @@ import java.util.function.Consumer;
* @author hakonhall
*/
@Immutable
-public
-class FetchVector {
+public class FetchVector {
public enum Dimension {
/** Value from ZoneId::value */
ZONE_ID,
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java
index f4e23144449..1a31ecd713e 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.flags.json.Rule;
@@ -21,8 +20,6 @@ import java.util.Optional;
* @author hakonhall
*/
public class FileFlagSource implements FlagSource {
- private static final ObjectMapper mapper = new ObjectMapper();
-
static final String FLAGS_DIRECTORY = "/etc/vespa/flags";
private final Path flagsDirectory;
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java
index da8e6b29cab..182ab85858c 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java
@@ -4,8 +4,11 @@ package com.yahoo.vespa.flags;
import java.util.Optional;
/**
+ * A source of raw flag values that can be converted to typed flag values elsewhere.
+ *
* @author hakonhall
*/
public interface FlagSource {
+ /** Get raw flag for the given vector (specifying hostname, application id, etc). */
Optional<RawFlag> fetch(FlagId id, FetchVector vector);
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index 6773c02e441..545f288af29 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -30,7 +30,7 @@ public class Flags {
HOSTNAME);
public static final UnboundBooleanFlag DUPERMODEL_USE_CONFIGSERVERCONFIG = defineFeatureFlag(
- "dupermodel-use-configserverconfig", true,
+ "dupermodel-use-configserverconfig", false,
"For historical reasons, the ApplicationInfo in the DuperModel for controllers and config servers " +
"is based on the ConfigserverConfig (this flag is true). We want to transition to use the " +
"infrastructure application activated by the InfrastructureProvisioner once that supports health.",
@@ -71,6 +71,12 @@ public class Flags {
"Whether to enable Nessus.", "Takes effect on next host admin tick",
HOSTNAME);
+ public static final UnboundBooleanFlag ENABLE_CPU_TEMPERATURE_TASK = defineFeatureFlag(
+ "enable-cputemptask", true,
+ "Whether to enable CPU temperature task", "Takes effect on next host admin tick",
+ HOSTNAME
+ );
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDbFile.java b/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDbFile.java
new file mode 100644
index 00000000000..abe8d407ab0
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDbFile.java
@@ -0,0 +1,111 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.file;
+
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.defaults.Defaults;
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * Java API for a flag database stored in a single file
+ *
+ * @author hakonhall
+ */
+public class FlagDbFile {
+ private static final Logger logger = Logger.getLogger(FlagDbFile.class.getName());
+
+ private final Path path;
+
+ public FlagDbFile() {
+ this(FileSystems.getDefault());
+ }
+
+ public FlagDbFile(FileSystem fileSystem) {
+ this(fileSystem.getPath(Defaults.getDefaults().vespaHome() + "/var/vespa/flag.db"));
+ }
+
+ public FlagDbFile(Path path) {
+ this.path = path;
+ }
+
+ public Path getPath() {
+ return path;
+ }
+
+ public Map<FlagId, FlagData> read() {
+ Optional<byte[]> bytes = readFile();
+ if (!bytes.isPresent()) return Collections.emptyMap();
+ return FlagData.deserializeList(bytes.get()).stream().collect(Collectors.toMap(FlagData::id, Function.identity()));
+ }
+
+ public boolean sync(Map<FlagId, FlagData> flagData) {
+ boolean modified = false;
+ Map<FlagId, FlagData> currentFlagData = read();
+ Set<FlagId> flagIdsToBeRemoved = new HashSet<>(currentFlagData.keySet());
+ List<FlagData> flagDataList = new ArrayList<>(flagData.values());
+
+ for (FlagData data : flagDataList) {
+ flagIdsToBeRemoved.remove(data.id());
+
+ FlagData existingFlagData = currentFlagData.get(data.id());
+ if (existingFlagData == null) {
+ logger.log(LogLevel.INFO, "New flag " + data.id() + ": " + data.serializeToJson());
+ modified = true;
+
+ // Could also consider testing with FlagData::equals, but that would be too fragile?
+ } else if (!Objects.equals(data.serializeToJson(), existingFlagData.serializeToJson())){
+ logger.log(LogLevel.INFO, "Updating flag " + data.id() + " from " +
+ existingFlagData.serializeToJson() + " to " + data.serializeToJson());
+ modified = true;
+ }
+ }
+
+ if (!flagIdsToBeRemoved.isEmpty()) {
+ String flagIdsString = flagIdsToBeRemoved.stream().map(FlagId::toString).collect(Collectors.joining(", "));
+ logger.log(LogLevel.INFO, "Removing flags " + flagIdsString);
+ modified = true;
+ }
+
+ if (!modified) return false;
+
+ writeFile(FlagData.serializeListToUtf8Json(flagDataList));
+
+ return modified;
+ }
+
+ private Optional<byte[]> readFile() {
+ try {
+ return Optional.of(Files.readAllBytes(path));
+ } catch (NoSuchFileException e) {
+ return Optional.empty();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void writeFile(byte[] bytes) {
+ uncheck(() -> Files.createDirectories(path.getParent()));
+ uncheck(() -> Files.write(path, bytes));
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java b/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java
new file mode 100644
index 00000000000..27ad44f938e
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.flags.file;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
index 572ec511607..64c4bbe7616 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
@@ -8,6 +8,7 @@ import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.RawFlag;
import com.yahoo.vespa.flags.json.wire.WireFlagData;
+import com.yahoo.vespa.flags.json.wire.WireFlagDataList;
import com.yahoo.vespa.flags.json.wire.WireRule;
import javax.annotation.concurrent.Immutable;
@@ -114,6 +115,24 @@ public class FlagData {
);
}
+ public static byte[] serializeListToUtf8Json(List<FlagData> list) {
+ return listToWire(list).serializeToBytes();
+ }
+
+ public static List<FlagData> deserializeList(byte[] bytes) {
+ return listFromWire(WireFlagDataList.deserializeFrom(bytes));
+ }
+
+ public static WireFlagDataList listToWire(List<FlagData> list) {
+ WireFlagDataList wireList = new WireFlagDataList();
+ wireList.flags = list.stream().map(FlagData::toWire).collect(Collectors.toList());
+ return wireList;
+ }
+
+ public static List<FlagData> listFromWire(WireFlagDataList wireList) {
+ return wireList.flags.stream().map(FlagData::fromWire).collect(Collectors.toList());
+ }
+
private static List<Rule> rulesFromWire(List<WireRule> wireRules) {
if (wireRules == null) return Collections.emptyList();
return wireRules.stream().map(Rule::fromWire).collect(Collectors.toList());
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java
new file mode 100644
index 00000000000..60b35d9b69e
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java
@@ -0,0 +1,37 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json.wire;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * @author hakonhall
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class WireFlagDataList {
+ @JsonProperty("flags")
+ public List<WireFlagData> flags = new ArrayList<>();
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ public void serializeToOutputStream(OutputStream outputStream) {
+ uncheck(() -> mapper.writeValue(outputStream, this));
+ }
+
+ public byte[] serializeToBytes() {
+ return uncheck(() -> mapper.writeValueAsBytes(this));
+ }
+
+ public static WireFlagDataList deserializeFrom(byte[] bytes) {
+ return uncheck(() -> mapper.readValue(bytes, WireFlagDataList.class));
+ }
+}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDbFileTest.java b/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDbFileTest.java
new file mode 100644
index 00000000000..fd1a71e4b4a
--- /dev/null
+++ b/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDbFileTest.java
@@ -0,0 +1,76 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.file;
+
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+import com.yahoo.vespa.test.file.TestFileSystem;
+import org.hamcrest.collection.IsMapContaining;
+import org.hamcrest.collection.IsMapWithSize;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * @author hakonhall
+ */
+public class FlagDbFileTest {
+ private final FileSystem fileSystem = TestFileSystem.create();
+ private final FlagDbFile flagDb = new FlagDbFile(fileSystem);
+
+ @Test
+ public void test() {
+ Map<FlagId, FlagData> dataMap = new HashMap<>();
+ FlagId id1 = new FlagId("id1");
+ FlagData data1 = new FlagData(id1, new FetchVector());
+ dataMap.put(id1, data1);
+ FlagId id2 = new FlagId("id2");
+ FlagData data2 = new FlagData(id2, new FetchVector());
+ dataMap.put(id2, data2);
+
+ // Non-existing directory => empty map
+ assertThat(flagDb.read(), IsMapWithSize.anEmptyMap());
+
+ // sync() will create directory with map content
+ assertThat(flagDb.sync(dataMap), equalTo(true));
+ Map<FlagId, FlagData> readDataMap = flagDb.read();
+ assertThat(readDataMap, IsMapWithSize.aMapWithSize(2));
+ assertThat(readDataMap, IsMapContaining.hasKey(id1));
+ assertThat(readDataMap, IsMapContaining.hasKey(id2));
+
+ assertThat(getDbContent(), equalTo("{\"flags\":[{\"id\":\"id1\"},{\"id\":\"id2\"}]}"));
+
+ // another sync with the same data is a no-op
+ assertThat(flagDb.sync(dataMap), equalTo(false));
+
+ // Changing value of id1, removing id2, adding id3
+ dataMap.remove(id2);
+ FlagData newData1 = new FlagData(id1, new FetchVector().with(FetchVector.Dimension.HOSTNAME, "h1"));
+ dataMap.put(id1, newData1);
+ FlagId id3 = new FlagId("id3");
+ FlagData data3 = new FlagData(id3, new FetchVector());
+ dataMap.put(id3, data3);
+ assertThat(flagDb.sync(dataMap), equalTo(true));
+ Map<FlagId, FlagData> anotherReadDataMap = flagDb.read();
+ assertThat(anotherReadDataMap, IsMapWithSize.aMapWithSize(2));
+ assertThat(anotherReadDataMap, IsMapContaining.hasKey(id1));
+ assertThat(anotherReadDataMap, IsMapContaining.hasKey(id3));
+ assertThat(anotherReadDataMap.get(id1).serializeToJson(), equalTo("{\"id\":\"id1\",\"attributes\":{\"hostname\":\"h1\"}}"));
+
+ assertThat(flagDb.sync(Collections.emptyMap()), equalTo(true));
+ assertThat(getDbContent(), equalTo("{\"flags\":[]}"));
+ }
+
+ public String getDbContent() {
+ return uncheck(() -> new String(Files.readAllBytes(flagDb.getPath()), StandardCharsets.UTF_8));
+ }
+} \ No newline at end of file
diff --git a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/RetryingJaxRsStrategy.java b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/RetryingJaxRsStrategy.java
index c964dfce2c7..f51026382fc 100644
--- a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/RetryingJaxRsStrategy.java
+++ b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/RetryingJaxRsStrategy.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.jaxrs.client;
import com.yahoo.vespa.applicationmodel.HostName;
import javax.ws.rs.ProcessingException;
+import javax.ws.rs.ServiceUnavailableException;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;
@@ -73,7 +74,7 @@ public class RetryingJaxRsStrategy<T> implements JaxRsStrategy<T> {
@Override
public <R> R apply(final Function<T, R> function, JaxRsTimeouts timeouts) throws IOException {
- ProcessingException sampleException = null;
+ RuntimeException sampleException = null;
for (int i = 0; i < maxIterations; ++i) {
for (final HostName hostName : hostNames) {
@@ -84,8 +85,9 @@ public class RetryingJaxRsStrategy<T> implements JaxRsStrategy<T> {
final T jaxRsClient = jaxRsClientFactory.createClient(params);
try {
return function.apply(jaxRsClient);
- } catch (ProcessingException e) {
- // E.g. java.net.SocketTimeoutException thrown on read timeout is wrapped as a ProcessingException
+ } catch (ProcessingException | ServiceUnavailableException e) {
+ // E.g. java.net.SocketTimeoutException thrown on read timeout is wrapped as a ProcessingException,
+ // while ServiceUnavailableException is a WebApplicationException
sampleException = e;
logger.log(Level.INFO, "Failed REST API call to "
+ hostName + ":" + port + pathPrefix + " (in retry loop): "
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java b/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
index 65aafee0060..2a1967abd13 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/Request.java
@@ -389,7 +389,7 @@ public class Request extends AbstractResource {
* @throws BindingNotFoundException If the corresponding call to {@link Container#resolveHandler(Request)} returns
* null.
*/
- public ContentChannel connect(final ResponseHandler responseHandler) {
+ public ContentChannel connect(ResponseHandler responseHandler) {
try {
Objects.requireNonNull(responseHandler, "responseHandler");
RequestHandler requestHandler = container().resolveHandler(this);
diff --git a/node-admin/pom.xml b/node-admin/pom.xml
index 476902e400c..e1231f2585d 100644
--- a/node-admin/pom.xml
+++ b/node-admin/pom.xml
@@ -49,6 +49,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>flags</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
<!-- Compile -->
<dependency>
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java
index 4a2496f4d3e..ab899f9f919 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java
@@ -1,6 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.configserver;
+import com.yahoo.vespa.hosted.node.admin.configserver.flags.FlagRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.configserver.state.State;
@@ -18,7 +19,10 @@ public interface ConfigServerClients {
Orchestrator orchestrator();
/** Get handle to the /state/v1 REST API */
- default State state() { throw new UnsupportedOperationException(); }
+ State state();
+
+ /** Get handle to the /flags/v1 REST API */
+ FlagRepository flagRepository();
void stop();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
index 6c982bfa71c..af11c300c2b 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
@@ -1,6 +1,8 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.configserver;
+import com.yahoo.vespa.hosted.node.admin.configserver.flags.FlagRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.flags.RealFlagRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.RealNodeRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
@@ -19,6 +21,7 @@ public class RealConfigServerClients implements ConfigServerClients {
private final NodeRepository nodeRepository;
private final Orchestrator orchestrator;
private final State state;
+ private final RealFlagRepository flagRepository;
/**
* @param configServerApi the backend API to use - will be closed at {@link #stop()}.
@@ -28,6 +31,7 @@ public class RealConfigServerClients implements ConfigServerClients {
nodeRepository = new RealNodeRepository(configServerApi);
orchestrator = new OrchestratorImpl(configServerApi);
state = new StateImpl(configServerApi);
+ flagRepository = new RealFlagRepository(configServerApi);
}
@Override
@@ -46,6 +50,11 @@ public class RealConfigServerClients implements ConfigServerClients {
}
@Override
+ public FlagRepository flagRepository() {
+ return flagRepository;
+ }
+
+ @Override
public void stop() {
configServerApi.close();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java
new file mode 100644
index 00000000000..8407d42131b
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java
@@ -0,0 +1,15 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.flags;
+
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hakonhall
+ */
+public interface FlagRepository {
+ Map<FlagId, FlagData> getAllFlagData();
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java
new file mode 100644
index 00000000000..a017569294e
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java
@@ -0,0 +1,28 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.flags;
+
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+import com.yahoo.vespa.flags.json.wire.WireFlagDataList;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author hakonhall
+ */
+public class RealFlagRepository implements FlagRepository {
+ private final ConfigServerApi configServerApi;
+
+ public RealFlagRepository(ConfigServerApi configServerApi) {
+ this.configServerApi = configServerApi;
+ }
+
+ @Override
+ public Map<FlagId, FlagData> getAllFlagData() {
+ WireFlagDataList list = configServerApi.get("/flags/v1/data?recursive=true", WireFlagDataList.class);
+ return FlagData.listFromWire(list).stream().collect(Collectors.toMap(FlagData::id, Function.identity()));
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java
new file mode 100644
index 00000000000..b991adfc639
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.node.admin.configserver.flags;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
index c65d59a79dc..af8dfb1fd27 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
@@ -7,7 +7,6 @@ import com.yahoo.vespa.hosted.dockerapi.ContainerStats;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.nodeagent.ContainerData;
import java.util.List;
@@ -15,7 +14,7 @@ import java.util.Optional;
public interface DockerOperations {
- void createContainer(NodeAgentContext context, NodeSpec node, ContainerData containerData);
+ void createContainer(NodeAgentContext context, ContainerData containerData);
void startContainer(NodeAgentContext context);
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 89ab2e60b63..e1b77b6a41b 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
@@ -13,7 +13,6 @@ import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.nodeagent.ContainerData;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPAddresses;
@@ -55,19 +54,19 @@ public class DockerOperationsImpl implements DockerOperations {
}
@Override
- public void createContainer(NodeAgentContext context, NodeSpec node, ContainerData containerData) {
+ public void createContainer(NodeAgentContext context, ContainerData containerData) {
context.log(logger, "Creating container");
// IPv6 - Assume always valid
- Inet6Address ipV6Address = ipAddresses.getIPv6Address(node.getHostname()).orElseThrow(
- () -> new RuntimeException("Unable to find a valid IPv6 address for " + node.getHostname() +
+ Inet6Address ipV6Address = ipAddresses.getIPv6Address(context.node().getHostname()).orElseThrow(
+ () -> new RuntimeException("Unable to find a valid IPv6 address for " + context.node().getHostname() +
". Missing an AAAA DNS entry?"));
Docker.CreateContainerCommand command = docker.createContainerCommand(
- node.getWantedDockerImage().get(),
- ContainerResources.from(node.getMinCpuCores(), node.getMinMainMemoryAvailableGb()),
+ context.node().getWantedDockerImage().get(),
+ ContainerResources.from(context.node().getMinCpuCores(), context.node().getMinMainMemoryAvailableGb()),
context.containerName(),
- node.getHostname())
+ context.node().getHostname())
.withManagedBy(MANAGER_NAME)
.withUlimit("nofile", 262_144, 262_144)
// The nproc aka RLIMIT_NPROC resource limit works as follows:
@@ -100,20 +99,20 @@ public class DockerOperationsImpl implements DockerOperations {
command.withIpAddress(ipV6Local);
// IPv4 - Only present for some containers
- Optional<InetAddress> ipV4Local = ipAddresses.getIPv4Address(node.getHostname())
+ Optional<InetAddress> ipV4Local = ipAddresses.getIPv4Address(context.node().getHostname())
.map(ipV4Address -> {
InetAddress ipV4Prefix = InetAddresses.forString(IPV4_NPT_PREFIX);
return IPAddresses.prefixTranslate(ipV4Address, ipV4Prefix, 2);
});
ipV4Local.ifPresent(command::withIpAddress);
- addEtcHosts(containerData, node.getHostname(), ipV4Local, ipV6Local);
+ addEtcHosts(containerData, context.node().getHostname(), ipV4Local, ipV6Local);
}
addMounts(context, command);
// TODO: Enforce disk constraints
- long minMainMemoryAvailableMb = (long) (node.getMinMainMemoryAvailableGb() * 1024);
+ long minMainMemoryAvailableMb = (long) (context.node().getMinMainMemoryAvailableGb() * 1024);
if (minMainMemoryAvailableMb > 0) {
// VESPA_TOTAL_MEMORY_MB is used to make any jdisc container think the machine
// only has this much physical memory (overrides total memory reported by `free -m`).
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 2fd40a1b486..47255c54455 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
@@ -7,7 +7,6 @@ import com.yahoo.config.provision.NodeType;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.dockerapi.Container;
import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
-import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoredumpHandler;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
@@ -65,9 +64,9 @@ public class StorageMaintainer {
this.archiveContainerStoragePath = archiveContainerStoragePath;
}
- public void writeMetricsConfig(NodeAgentContext context, NodeSpec node) {
+ public void writeMetricsConfig(NodeAgentContext context) {
List<SecretAgentCheckConfig> configs = new ArrayList<>();
- Map<String, Object> tags = generateTags(context, node);
+ Map<String, Object> tags = generateTags(context);
// host-life
Path hostLifeCheckPath = context.pathInNodeUnderVespaHome("libexec/yms/yms_check_host_life");
@@ -154,26 +153,26 @@ public class StorageMaintainer {
dockerOperations.executeCommandInContainerAsRoot(context, "service", "yamas-agent", "restart");
}
- private Map<String, Object> generateTags(NodeAgentContext context, NodeSpec node) {
+ private Map<String, Object> generateTags(NodeAgentContext context) {
Map<String, String> tags = new LinkedHashMap<>();
tags.put("namespace", "Vespa");
- tags.put("role", nodeTypeToRole(node.getNodeType()));
+ tags.put("role", nodeTypeToRole(context.node().getNodeType()));
tags.put("zone", String.format("%s.%s", context.zoneId().environment().value(), context.zoneId().regionName().value()));
- node.getVespaVersion().ifPresent(version -> tags.put("vespaVersion", version));
+ context.node().getVespaVersion().ifPresent(version -> tags.put("vespaVersion", version));
if (! isConfigserverLike(context.nodeType())) {
- tags.put("flavor", node.getFlavor());
- tags.put("canonicalFlavor", node.getCanonicalFlavor());
- tags.put("state", node.getState().toString());
- node.getParentHostname().ifPresent(parent -> tags.put("parentHostname", parent));
- node.getOwner().ifPresent(owner -> {
+ tags.put("flavor", context.node().getFlavor());
+ tags.put("canonicalFlavor", context.node().getCanonicalFlavor());
+ tags.put("state", context.node().getState().toString());
+ context.node().getParentHostname().ifPresent(parent -> tags.put("parentHostname", parent));
+ context.node().getOwner().ifPresent(owner -> {
tags.put("tenantName", owner.getTenant());
tags.put("app", owner.getApplication() + "." + owner.getInstance());
tags.put("applicationName", owner.getApplication());
tags.put("instanceName", owner.getInstance());
tags.put("applicationId", owner.getTenant() + "." + owner.getApplication() + "." + owner.getInstance());
});
- node.getMembership().ifPresent(membership -> {
+ context.node().getMembership().ifPresent(membership -> {
tags.put("clustertype", membership.getClusterType());
tags.put("clusterid", membership.getClusterId());
});
@@ -250,26 +249,30 @@ public class StorageMaintainer {
FileFinder.directories(context.pathOnHostFromPathInNode(context.pathInNodeUnderVespaHome("var/db/vespa/filedistribution")))
.match(olderThan(Duration.ofDays(31)))
.deleteRecursively();
+
+ FileFinder.directories(context.pathOnHostFromPathInNode(context.pathInNodeUnderVespaHome("var/db/vespa/download")))
+ .match(olderThan(Duration.ofDays(31)))
+ .deleteRecursively();
}
/** Checks if container has any new coredumps, reports and archives them if so */
- public void handleCoreDumpsForContainer(NodeAgentContext context, NodeSpec node, Optional<Container> container) {
- final Map<String, Object> nodeAttributes = getCoredumpNodeAttributes(context, node, container);
+ public void handleCoreDumpsForContainer(NodeAgentContext context, Optional<Container> container) {
+ final Map<String, Object> nodeAttributes = getCoredumpNodeAttributes(context, container);
coredumpHandler.converge(context, nodeAttributes);
}
- private Map<String, Object> getCoredumpNodeAttributes(NodeAgentContext context, NodeSpec node, Optional<Container> container) {
+ private Map<String, Object> getCoredumpNodeAttributes(NodeAgentContext context, Optional<Container> container) {
Map<String, String> attributes = new HashMap<>();
- attributes.put("hostname", node.getHostname());
+ attributes.put("hostname", context.node().getHostname());
attributes.put("region", context.zoneId().regionName().value());
attributes.put("environment", context.zoneId().environment().value());
- attributes.put("flavor", node.getFlavor());
+ attributes.put("flavor", context.node().getFlavor());
attributes.put("kernel_version", System.getProperty("os.version"));
container.map(c -> c.image).ifPresent(image -> attributes.put("docker_image", image.asString()));
- node.getParentHostname().ifPresent(parent -> attributes.put("parent_hostname", parent));
- node.getVespaVersion().ifPresent(version -> attributes.put("vespa_version", version));
- node.getOwner().ifPresent(owner -> {
+ context.node().getParentHostname().ifPresent(parent -> attributes.put("parent_hostname", parent));
+ context.node().getVespaVersion().ifPresent(version -> attributes.put("vespa_version", version));
+ context.node().getOwner().ifPresent(owner -> {
attributes.put("tenant", owner.getTenant());
attributes.put("application", owner.getApplication());
attributes.put("instance", owner.getInstance());
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
index b191401b8e0..2303f78217c 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
@@ -9,6 +9,11 @@ import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextFactory;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextManager;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentFactory;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentScheduler;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import java.time.Clock;
@@ -23,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -33,12 +37,15 @@ import java.util.stream.Collectors;
*/
public class NodeAdminImpl implements NodeAdmin {
private static final PrefixLogger logger = PrefixLogger.getNodeAdminLogger(NodeAdmin.class);
+ private static final Duration NODE_AGENT_FREEZE_TIMEOUT = Duration.ofSeconds(5);
+
private final ScheduledExecutorService aclScheduler =
Executors.newScheduledThreadPool(1, ThreadFactoryFactory.getDaemonThreadFactory("aclscheduler"));
private final ScheduledExecutorService metricsScheduler =
Executors.newScheduledThreadPool(1, ThreadFactoryFactory.getDaemonThreadFactory("metricsscheduler"));
- private final Function<String, NodeAgent> nodeAgentFactory;
+ private final NodeAgentWithSchedulerFactory nodeAgentWithSchedulerFactory;
+ private final NodeAgentContextFactory nodeAgentContextFactory;
private final Optional<AclMaintainer> aclMaintainer;
private final Clock clock;
@@ -46,16 +53,27 @@ public class NodeAdminImpl implements NodeAdmin {
private boolean isFrozen;
private Instant startOfFreezeConvergence;
- private final Map<String, NodeAgent> nodeAgentsByHostname = new ConcurrentHashMap<>();
+ private final Map<String, NodeAgentWithScheduler> nodeAgentWithSchedulerByHostname = new ConcurrentHashMap<>();
private final GaugeWrapper numberOfContainersInLoadImageState;
private final CounterWrapper numberOfUnhandledExceptionsInNodeAgent;
- public NodeAdminImpl(Function<String, NodeAgent> nodeAgentFactory,
+ public NodeAdminImpl(NodeAgentFactory nodeAgentFactory,
+ NodeAgentContextFactory nodeAgentContextFactory,
Optional<AclMaintainer> aclMaintainer,
MetricReceiverWrapper metricReceiver,
Clock clock) {
- this.nodeAgentFactory = nodeAgentFactory;
+ this((NodeAgentWithSchedulerFactory) nodeAgentContext -> create(clock, nodeAgentFactory, nodeAgentContext),
+ nodeAgentContextFactory, aclMaintainer, metricReceiver, clock);
+ }
+
+ NodeAdminImpl(NodeAgentWithSchedulerFactory nodeAgentWithSchedulerFactory,
+ NodeAgentContextFactory nodeAgentContextFactory,
+ Optional<AclMaintainer> aclMaintainer,
+ MetricReceiverWrapper metricReceiver,
+ Clock clock) {
+ this.nodeAgentWithSchedulerFactory = nodeAgentWithSchedulerFactory;
+ this.nodeAgentContextFactory = nodeAgentContextFactory;
this.aclMaintainer = aclMaintainer;
this.clock = clock;
@@ -70,22 +88,33 @@ public class NodeAdminImpl implements NodeAdmin {
@Override
public void refreshContainersToRun(List<NodeSpec> containersToRun) {
- final Set<String> hostnamesOfContainersToRun = containersToRun.stream()
- .map(NodeSpec::getHostname)
- .collect(Collectors.toSet());
+ final Map<String, NodeAgentContext> nodeAgentContextsByHostname = containersToRun.stream()
+ .collect(Collectors.toMap(NodeSpec::getHostname, nodeAgentContextFactory::create));
- synchronizeNodesToNodeAgents(hostnamesOfContainersToRun);
+ // Stop and remove NodeAgents that should no longer be running
+ diff(nodeAgentWithSchedulerByHostname.keySet(), nodeAgentContextsByHostname.keySet())
+ .forEach(hostname -> nodeAgentWithSchedulerByHostname.remove(hostname).stop());
+
+ // Start NodeAgent for hostnames that should be running, but aren't yet
+ diff(nodeAgentContextsByHostname.keySet(), nodeAgentWithSchedulerByHostname.keySet()).forEach(hostname -> {
+ NodeAgentWithScheduler naws = nodeAgentWithSchedulerFactory.create(nodeAgentContextsByHostname.get(hostname));
+ naws.start();
+ nodeAgentWithSchedulerByHostname.put(hostname, naws);
+ });
- updateNodeAgentMetrics();
+ // At this point, nodeAgentContextsByHostname and nodeAgentWithSchedulerByHostname should have the same keys
+ nodeAgentContextsByHostname.forEach((hostname, context) ->
+ nodeAgentWithSchedulerByHostname.get(hostname).scheduleTickWith(context)
+ );
}
private void updateNodeAgentMetrics() {
int numberContainersWaitingImage = 0;
int numberOfNewUnhandledExceptions = 0;
- for (NodeAgent nodeAgent : nodeAgentsByHostname.values()) {
- if (nodeAgent.isDownloadingImage()) numberContainersWaitingImage++;
- numberOfNewUnhandledExceptions += nodeAgent.getAndResetNumberOfUnhandledExceptions();
+ for (NodeAgentWithScheduler nodeAgentWithScheduler : nodeAgentWithSchedulerByHostname.values()) {
+ if (nodeAgentWithScheduler.isDownloadingImage()) numberContainersWaitingImage++;
+ numberOfNewUnhandledExceptions += nodeAgentWithScheduler.getAndResetNumberOfUnhandledExceptions();
}
numberOfContainersInLoadImageState.sample(numberContainersWaitingImage);
@@ -105,8 +134,8 @@ public class NodeAdminImpl implements NodeAdmin {
}
// Use filter with count instead of allMatch() because allMatch() will short circuit on first non-match
- boolean allNodeAgentsConverged = nodeAgentsByHostname.values().stream()
- .filter(nodeAgent -> !nodeAgent.setFrozen(wantFrozen))
+ boolean allNodeAgentsConverged = nodeAgentWithSchedulerByHostname.values().parallelStream()
+ .filter(nodeAgentScheduler -> !nodeAgentScheduler.setFrozen(wantFrozen, NODE_AGENT_FREEZE_TIMEOUT))
.count() == 0;
if (wantFrozen) {
@@ -134,8 +163,8 @@ public class NodeAdminImpl implements NodeAdmin {
public void stopNodeAgentServices(List<String> hostnames) {
// Each container may spend 1-1:30 minutes stopping
hostnames.parallelStream()
- .filter(nodeAgentsByHostname::containsKey)
- .map(nodeAgentsByHostname::get)
+ .filter(nodeAgentWithSchedulerByHostname::containsKey)
+ .map(nodeAgentWithSchedulerByHostname::get)
.forEach(nodeAgent -> {
nodeAgent.suspend();
nodeAgent.stopServices();
@@ -146,7 +175,8 @@ public class NodeAdminImpl implements NodeAdmin {
public void start() {
metricsScheduler.scheduleAtFixedRate(() -> {
try {
- nodeAgentsByHostname.values().forEach(NodeAgent::updateContainerNodeMetrics);
+ updateNodeAgentMetrics();
+ nodeAgentWithSchedulerByHostname.values().forEach(NodeAgent::updateContainerNodeMetrics);
} catch (Throwable e) {
logger.warning("Metric fetcher scheduler failed", e);
}
@@ -166,7 +196,7 @@ public class NodeAdminImpl implements NodeAdmin {
aclScheduler.shutdown();
// Stop all node-agents in parallel, will block until the last NodeAgent is stopped
- nodeAgentsByHostname.values().parallelStream().forEach(NodeAgent::stop);
+ nodeAgentWithSchedulerByHostname.values().parallelStream().forEach(NodeAgent::stop);
do {
try {
@@ -185,23 +215,35 @@ public class NodeAdminImpl implements NodeAdmin {
return result;
}
- void synchronizeNodesToNodeAgents(Set<String> hostnamesToRun) {
- // Stop and remove NodeAgents that should no longer be running
- diff(nodeAgentsByHostname.keySet(), hostnamesToRun)
- .forEach(hostname -> nodeAgentsByHostname.remove(hostname).stop());
+ static class NodeAgentWithScheduler implements NodeAgent, NodeAgentScheduler {
+ private final NodeAgent nodeAgent;
+ private final NodeAgentScheduler nodeAgentScheduler;
- // Start NodeAgent for hostnames that should be running, but aren't yet
- diff(hostnamesToRun, nodeAgentsByHostname.keySet())
- .forEach(this::startNodeAgent);
+ private NodeAgentWithScheduler(NodeAgent nodeAgent, NodeAgentScheduler nodeAgentScheduler) {
+ this.nodeAgent = nodeAgent;
+ this.nodeAgentScheduler = nodeAgentScheduler;
+ }
+
+ @Override public void stopServices() { nodeAgent.stopServices(); }
+ @Override public void suspend() { nodeAgent.suspend(); }
+ @Override public void start() { nodeAgent.start(); }
+ @Override public void stop() { nodeAgent.stop(); }
+ @Override public void updateContainerNodeMetrics() { nodeAgent.updateContainerNodeMetrics(); }
+ @Override public boolean isDownloadingImage() { return nodeAgent.isDownloadingImage(); }
+ @Override public int getAndResetNumberOfUnhandledExceptions() { return nodeAgent.getAndResetNumberOfUnhandledExceptions(); }
+
+ @Override public void scheduleTickWith(NodeAgentContext context) { nodeAgentScheduler.scheduleTickWith(context); }
+ @Override public boolean setFrozen(boolean frozen, Duration timeout) { return nodeAgentScheduler.setFrozen(frozen, timeout); }
}
- private void startNodeAgent(String hostname) {
- if (nodeAgentsByHostname.containsKey(hostname))
- throw new IllegalArgumentException("Attempted to start NodeAgent for hostname " + hostname +
- ", but one is already running!");
+ @FunctionalInterface
+ interface NodeAgentWithSchedulerFactory {
+ NodeAgentWithScheduler create(NodeAgentContext context);
+ }
- NodeAgent agent = nodeAgentFactory.apply(hostname);
- agent.start();
- nodeAgentsByHostname.put(hostname, agent);
+ private static NodeAgentWithScheduler create(Clock clock, NodeAgentFactory nodeAgentFactory, NodeAgentContext context) {
+ NodeAgentContextManager contextManager = new NodeAgentContextManager(clock, context);
+ NodeAgent nodeAgent = nodeAgentFactory.create(contextManager);
+ return new NodeAgentWithScheduler(nodeAgent, contextManager);
}
}
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 a12104c6e98..13d3f3307d2 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
@@ -51,30 +51,21 @@ public class NodeAdminStateUpdater {
nodeAdmin.start();
}
- public void converge(State wantedState) {
- try {
- convergeState(wantedState);
- } finally {
- if (wantedState != 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.
- // To avoid node agents stalling for too long, we'll force unfrozen ticks now.
- log.info("Timed out trying to freeze, will force unfreezed ticks");
- fetchContainersToRunFromNodeRepository();
- nodeAdmin.setFrozen(false);
- }
- } else if (currentState == RESUMED) {
- fetchContainersToRunFromNodeRepository();
- }
- }
- }
-
/**
* This method attempts to converge node-admin w/agents to a {@link State}
* with respect to: freeze, Orchestrator, and services running.
*/
- private void convergeState(State wantedState) {
+ public void converge(State wantedState) {
+ if (wantedState == RESUMED) {
+ adjustNodeAgentsToRunFromNodeRepository();
+ } else if (currentState == TRANSITIONING && nodeAdmin.subsystemFreezeDuration().compareTo(FREEZE_CONVERGENCE_TIMEOUT) > 0) {
+ // We have spent too much time trying to freeze and node admin is still not frozen.
+ // To avoid node agents stalling for too long, we'll force unfrozen ticks now.
+ adjustNodeAgentsToRunFromNodeRepository();
+ nodeAdmin.setFrozen(false);
+ throw new ConvergenceException("Timed out trying to freeze all nodes: will force an unfrozen tick");
+ }
+
if (currentState == wantedState) return;
currentState = TRANSITIONING;
@@ -119,7 +110,7 @@ public class NodeAdminStateUpdater {
currentState = wantedState;
}
- private void fetchContainersToRunFromNodeRepository() {
+ private void adjustNodeAgentsToRunFromNodeRepository() {
try {
final List<NodeSpec> containersToRun = nodeRepository.getNodes(hostHostname);
nodeAdmin.refreshContainersToRun(containersToRun);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgent.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgent.java
index 947e7c85d66..10076c4f48a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgent.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgent.java
@@ -9,12 +9,6 @@ package com.yahoo.vespa.hosted.node.admin.nodeagent;
* @author bakksjo
*/
public interface NodeAgent {
- /**
- * Will eventually freeze/unfreeze the node agent
- * @param frozen whether node agent should be frozen
- * @return True if node agent has converged to the desired state
- */
- boolean setFrozen(boolean frozen);
/**
* Stop services running on node. Depending on the state of the node, {@link #suspend()} might need to be
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
index 4874eccb913..2e9f58a2c31 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java
@@ -6,6 +6,7 @@ import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
import com.yahoo.vespa.hosted.node.admin.component.ZoneId;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerNetworking;
import java.nio.file.Path;
@@ -13,11 +14,17 @@ import java.nio.file.Paths;
public interface NodeAgentContext extends TaskContext {
+ NodeSpec node();
+
ContainerName containerName();
- HostName hostname();
+ default HostName hostname() {
+ return HostName.from(node().getHostname());
+ }
- NodeType nodeType();
+ default NodeType nodeType() {
+ return node().getNodeType();
+ }
AthenzService identity();
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextFactory.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextFactory.java
new file mode 100644
index 00000000000..0cfafe34717
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextFactory.java
@@ -0,0 +1,12 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
+
+/**
+ * @author freva
+ */
+@FunctionalInterface
+public interface NodeAgentContextFactory {
+ NodeAgentContext create(NodeSpec nodeSpec);
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
index 3c34e35ab46..58414ab55f4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java
@@ -1,14 +1,15 @@
package com.yahoo.vespa.hosted.node.admin.nodeagent;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.component.ZoneId;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerNetworking;
+import com.yahoo.vespa.hosted.provision.Node;
import java.nio.file.FileSystem;
import java.nio.file.Path;
@@ -25,9 +26,8 @@ public class NodeAgentContextImpl implements NodeAgentContext {
private static final Path ROOT = Paths.get("/");
private final String logPrefix;
+ private final NodeSpec node;
private final ContainerName containerName;
- private final HostName hostName;
- private final NodeType nodeType;
private final AthenzService identity;
private final DockerNetworking dockerNetworking;
private final ZoneId zoneId;
@@ -36,13 +36,12 @@ public class NodeAgentContextImpl implements NodeAgentContext {
private final String vespaUser;
private final String vespaUserOnHost;
- public NodeAgentContextImpl(String hostname, NodeType nodeType, AthenzService identity,
+ public NodeAgentContextImpl(NodeSpec node, AthenzService identity,
DockerNetworking dockerNetworking, ZoneId zoneId,
Path pathToContainerStorage, Path pathToVespaHome,
String vespaUser, String vespaUserOnHost) {
- this.hostName = HostName.from(Objects.requireNonNull(hostname));
- this.containerName = ContainerName.fromHostname(hostname);
- this.nodeType = Objects.requireNonNull(nodeType);
+ this.node = Objects.requireNonNull(node);
+ this.containerName = ContainerName.fromHostname(node.getHostname());
this.identity = Objects.requireNonNull(identity);
this.dockerNetworking = Objects.requireNonNull(dockerNetworking);
this.zoneId = Objects.requireNonNull(zoneId);
@@ -54,18 +53,13 @@ public class NodeAgentContextImpl implements NodeAgentContext {
}
@Override
- public ContainerName containerName() {
- return containerName;
- }
-
- @Override
- public HostName hostname() {
- return hostName;
+ public NodeSpec node() {
+ return node;
}
@Override
- public NodeType nodeType() {
- return nodeType;
+ public ContainerName containerName() {
+ return containerName;
}
@Override
@@ -134,12 +128,25 @@ public class NodeAgentContextImpl implements NodeAgentContext {
public void log(Logger logger, Level level, String message, Throwable throwable) {
logger.log(level, logPrefix + message, throwable);
}
-
+
+ @Override
+ public String toString() {
+ return "NodeAgentContextImpl{" +
+ "node=" + node +
+ ", containerName=" + containerName +
+ ", identity=" + identity +
+ ", dockerNetworking=" + dockerNetworking +
+ ", zoneId=" + zoneId +
+ ", pathToNodeRootOnHost=" + pathToNodeRootOnHost +
+ ", pathToVespaHome=" + pathToVespaHome +
+ ", vespaUser='" + vespaUser + '\'' +
+ ", vespaUserOnHost='" + vespaUserOnHost + '\'' +
+ '}';
+ }
/** For testing only! */
public static class Builder {
- private final String hostname;
- private NodeType nodeType;
+ private NodeSpec.Builder nodeSpecBuilder = new NodeSpec.Builder();
private AthenzService identity;
private DockerNetworking dockerNetworking;
private ZoneId zoneId;
@@ -148,12 +155,25 @@ public class NodeAgentContextImpl implements NodeAgentContext {
private String vespaUser;
private String vespaUserOnHost;
+ public Builder(NodeSpec node) {
+ this.nodeSpecBuilder = new NodeSpec.Builder(node);
+ }
+
+ /**
+ * Creates a NodeAgentContext.Builder with a NodeSpec that has the given hostname and some
+ * reasonable values for the remaining required NodeSpec fields. Use {@link #Builder(NodeSpec)}
+ * if you want to control the entire NodeSpec.
+ */
public Builder(String hostname) {
- this.hostname = hostname;
+ this.nodeSpecBuilder
+ .hostname(hostname)
+ .state(Node.State.active)
+ .nodeType(NodeType.tenant)
+ .flavor("d-2-8-50");
}
public Builder nodeType(NodeType nodeType) {
- this.nodeType = nodeType;
+ this.nodeSpecBuilder.nodeType(nodeType);
return this;
}
@@ -198,8 +218,7 @@ public class NodeAgentContextImpl implements NodeAgentContext {
public NodeAgentContextImpl build() {
return new NodeAgentContextImpl(
- hostname,
- Optional.ofNullable(nodeType).orElse(NodeType.tenant),
+ nodeSpecBuilder.build(),
Optional.ofNullable(identity).orElseGet(() -> new AthenzService("domain", "service")),
Optional.ofNullable(dockerNetworking).orElse(DockerNetworking.HOST_NETWORK),
Optional.ofNullable(zoneId).orElseGet(() -> new ZoneId(SystemName.dev, Environment.dev, RegionName.defaultName())),
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManager.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManager.java
new file mode 100644
index 00000000000..54f357d5f29
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManager.java
@@ -0,0 +1,102 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.Objects;
+
+/**
+ * This class should be used by exactly 2 thread, 1 for each interface it implements.
+ *
+ * @author freva
+ */
+public class NodeAgentContextManager implements NodeAgentContextSupplier, NodeAgentScheduler {
+
+ private final Object monitor = new Object();
+ private final Clock clock;
+
+ private NodeAgentContext currentContext;
+ private NodeAgentContext nextContext;
+ private boolean wantFrozen = false;
+ private boolean isFrozen = true;
+ private boolean pendingInterrupt = false;
+
+ public NodeAgentContextManager(Clock clock, NodeAgentContext context) {
+ this.clock = clock;
+ this.currentContext = context;
+ }
+
+ @Override
+ public void scheduleTickWith(NodeAgentContext context) {
+ synchronized (monitor) {
+ nextContext = Objects.requireNonNull(context);
+ monitor.notifyAll(); // Notify of new context
+ }
+ }
+
+ @Override
+ public boolean setFrozen(boolean frozen, Duration timeout) {
+ synchronized (monitor) {
+ if (wantFrozen != frozen) {
+ wantFrozen = frozen;
+ monitor.notifyAll(); // Notify the supplier of the wantFrozen change
+ }
+
+ boolean successful;
+ long remainder;
+ long end = clock.instant().plus(timeout).toEpochMilli();
+ while (!(successful = isFrozen == frozen) && (remainder = end - clock.millis()) > 0) {
+ try {
+ monitor.wait(remainder); // Wait with timeout until the supplier is has reached wanted frozen state
+ } catch (InterruptedException ignored) { }
+ }
+
+ return successful;
+ }
+ }
+
+ @Override
+ public NodeAgentContext nextContext() throws InterruptedException {
+ synchronized (monitor) {
+ while (setAndGetIsFrozen(wantFrozen) || nextContext == null) {
+ if (pendingInterrupt) {
+ pendingInterrupt = false;
+ throw new InterruptedException("interrupt() was called before next context was scheduled");
+ }
+
+ try {
+ monitor.wait(); // Wait until scheduler provides a new context
+ } catch (InterruptedException ignored) { }
+ }
+
+ currentContext = nextContext;
+ nextContext = null;
+ return currentContext;
+ }
+ }
+
+ @Override
+ public NodeAgentContext currentContext() {
+ synchronized (monitor) {
+ return currentContext;
+ }
+ }
+
+ @Override
+ public void interrupt() {
+ synchronized (monitor) {
+ pendingInterrupt = true;
+ monitor.notifyAll();
+ }
+ }
+
+ private boolean setAndGetIsFrozen(boolean isFrozen) {
+ synchronized (monitor) {
+ if (this.isFrozen != isFrozen) {
+ this.isFrozen = isFrozen;
+ monitor.notifyAll(); // Notify the scheduler of the isFrozen change
+ }
+ return this.isFrozen;
+ }
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextSupplier.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextSupplier.java
new file mode 100644
index 00000000000..1fc730a3cb0
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextSupplier.java
@@ -0,0 +1,21 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+/**
+ * @author freva
+ */
+public interface NodeAgentContextSupplier {
+
+ /**
+ * Blocks until the next context is ready
+ * @return context
+ * @throws InterruptedException if {@link #interrupt()} was called before this method returned
+ */
+ NodeAgentContext nextContext() throws InterruptedException;
+
+ /** @return the last context returned by {@link #nextContext()} or a default value */
+ NodeAgentContext currentContext();
+
+ /** Interrupts the thread(s) currently waiting in {@link #nextContext()} */
+ void interrupt();
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentFactory.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentFactory.java
new file mode 100644
index 00000000000..bd13b7eb094
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentFactory.java
@@ -0,0 +1,10 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+/**
+ * @author freva
+ */
+@FunctionalInterface
+public interface NodeAgentFactory {
+ NodeAgent create(NodeAgentContextSupplier contextSupplier);
+}
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 98975dddb56..0bfff82a055 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
@@ -17,20 +17,17 @@ import com.yahoo.vespa.hosted.dockerapi.metrics.Dimensions;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeAttributes;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.identity.AthenzCredentialsMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
import com.yahoo.vespa.hosted.node.admin.util.SecretAgentCheckConfig;
import com.yahoo.vespa.hosted.provision.Node;
-import java.time.Clock;
-import java.time.Duration;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -59,30 +56,21 @@ public class NodeAgentImpl implements NodeAgent {
private static final Logger logger = Logger.getLogger(NodeAgentImpl.class.getName());
- private final Object monitor = new Object();
private final AtomicBoolean terminated = new AtomicBoolean(false);
-
- private boolean isFrozen = true;
- private boolean wantFrozen = false;
- private boolean workToDoNow = true;
- private boolean expectNodeNotInNodeRepo = false;
private boolean hasResumedNode = false;
private boolean hasStartedServices = true;
- private final NodeAgentContext context;
+ private final NodeAgentContextSupplier contextSupplier;
private final NodeRepository nodeRepository;
private final Orchestrator orchestrator;
private final DockerOperations dockerOperations;
private final StorageMaintainer storageMaintainer;
- private final Clock clock;
- private final Duration timeBetweenEachConverge;
private final Optional<AthenzCredentialsMaintainer> athenzCredentialsMaintainer;
private final Optional<AclMaintainer> aclMaintainer;
private final Optional<HealthChecker> healthChecker;
private int numberOfUnhandledException = 0;
private DockerImage imageBeingDownloaded = null;
- private Instant lastConverge;
private long currentRebootGeneration = 0;
private Optional<Long> currentRestartGeneration = Optional.empty();
@@ -115,24 +103,19 @@ public class NodeAgentImpl implements NodeAgent {
// Created in NodeAdminImpl
public NodeAgentImpl(
- final NodeAgentContext context,
+ final NodeAgentContextSupplier contextSupplier,
final NodeRepository nodeRepository,
final Orchestrator orchestrator,
final DockerOperations dockerOperations,
final StorageMaintainer storageMaintainer,
- final Clock clock,
- final Duration timeBetweenEachConverge,
final Optional<AthenzCredentialsMaintainer> athenzCredentialsMaintainer,
final Optional<AclMaintainer> aclMaintainer,
final Optional<HealthChecker> healthChecker) {
- this.context = context;
+ this.contextSupplier = contextSupplier;
this.nodeRepository = nodeRepository;
this.orchestrator = orchestrator;
this.dockerOperations = dockerOperations;
this.storageMaintainer = storageMaintainer;
- this.clock = clock;
- this.timeBetweenEachConverge = timeBetweenEachConverge;
- this.lastConverge = clock.instant();
this.athenzCredentialsMaintainer = athenzCredentialsMaintainer;
this.aclMaintainer = aclMaintainer;
this.healthChecker = healthChecker;
@@ -140,16 +123,15 @@ public class NodeAgentImpl implements NodeAgent {
this.loopThread = new Thread(() -> {
while (!terminated.get()) {
try {
- tick();
- } catch (Throwable t) {
- numberOfUnhandledException++;
- context.log(logger, LogLevel.ERROR, "Unhandled throwable, ignoring", t);
- }
+ NodeAgentContext context = contextSupplier.nextContext();
+ converge(context);
+ } catch (InterruptedException ignored) { }
}
});
- this.loopThread.setName("tick-" + context.hostname());
+ this.loopThread.setName("tick-" + contextSupplier.currentContext().hostname());
this.serviceRestarter = service -> {
+ NodeAgentContext context = contextSupplier.currentContext();
try {
ProcessResult processResult = dockerOperations.executeCommandInContainerAsRoot(
context, "service", service, "restart");
@@ -164,46 +146,29 @@ public class NodeAgentImpl implements NodeAgent {
}
@Override
- public boolean setFrozen(boolean frozen) {
- synchronized (monitor) {
- if (wantFrozen != frozen) {
- wantFrozen = frozen;
- context.log(logger, LogLevel.DEBUG, wantFrozen ? "Freezing" : "Unfreezing");
- signalWorkToBeDone();
- }
-
- return isFrozen == frozen;
- }
- }
-
- @Override
public void start() {
- context.log(logger, "Starting with interval " + timeBetweenEachConverge.toMillis() + " ms");
loopThread.start();
}
@Override
public void stop() {
- filebeatRestarter.shutdown();
if (!terminated.compareAndSet(false, true)) {
throw new RuntimeException("Can not re-stop a node agent.");
}
- signalWorkToBeDone();
+ filebeatRestarter.shutdown();
+ contextSupplier.interrupt();
do {
try {
loopThread.join();
filebeatRestarter.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (InterruptedException e) {
- context.log(logger, LogLevel.ERROR,
- "Interrupted while waiting for converge thread and filebeatRestarter scheduler to shutdown");
- }
+ } catch (InterruptedException ignored) { }
} while (loopThread.isAlive() || !filebeatRestarter.isTerminated());
- context.log(logger, "Stopped");
+ contextSupplier.currentContext().log(logger, "Stopped");
}
- void startServicesIfNeeded() {
+ void startServicesIfNeeded(NodeAgentContext context) {
if (!hasStartedServices) {
context.log(logger, "Starting services");
dockerOperations.startServices(context);
@@ -211,10 +176,10 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- void resumeNodeIfNeeded(NodeSpec node) {
+ void resumeNodeIfNeeded(NodeAgentContext context) {
if (!hasResumedNode) {
if (!currentFilebeatRestarter.isPresent()) {
- storageMaintainer.writeMetricsConfig(context, node);
+ storageMaintainer.writeMetricsConfig(context);
currentFilebeatRestarter = Optional.of(filebeatRestarter.scheduleWithFixedDelay(
() -> serviceRestarter.accept("filebeat"), 1, 1, TimeUnit.DAYS));
}
@@ -225,31 +190,31 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- private void updateNodeRepoWithCurrentAttributes(final NodeSpec node) {
+ private void updateNodeRepoWithCurrentAttributes(NodeAgentContext context) {
final NodeAttributes currentNodeAttributes = new NodeAttributes();
final NodeAttributes newNodeAttributes = new NodeAttributes();
- if (node.getWantedRestartGeneration().isPresent() &&
- !Objects.equals(node.getCurrentRestartGeneration(), currentRestartGeneration)) {
- currentNodeAttributes.withRestartGeneration(node.getCurrentRestartGeneration());
+ if (context.node().getWantedRestartGeneration().isPresent() &&
+ !Objects.equals(context.node().getCurrentRestartGeneration(), currentRestartGeneration)) {
+ currentNodeAttributes.withRestartGeneration(context.node().getCurrentRestartGeneration());
newNodeAttributes.withRestartGeneration(currentRestartGeneration);
}
- if (!Objects.equals(node.getCurrentRebootGeneration(), currentRebootGeneration)) {
- currentNodeAttributes.withRebootGeneration(node.getCurrentRebootGeneration());
+ if (!Objects.equals(context.node().getCurrentRebootGeneration(), currentRebootGeneration)) {
+ currentNodeAttributes.withRebootGeneration(context.node().getCurrentRebootGeneration());
newNodeAttributes.withRebootGeneration(currentRebootGeneration);
}
- Optional<DockerImage> actualDockerImage = node.getWantedDockerImage().filter(n -> containerState == UNKNOWN);
- if (!Objects.equals(node.getCurrentDockerImage(), actualDockerImage)) {
- currentNodeAttributes.withDockerImage(node.getCurrentDockerImage().orElse(new DockerImage("")));
+ Optional<DockerImage> actualDockerImage = context.node().getWantedDockerImage().filter(n -> containerState == UNKNOWN);
+ if (!Objects.equals(context.node().getCurrentDockerImage(), actualDockerImage)) {
+ currentNodeAttributes.withDockerImage(context.node().getCurrentDockerImage().orElse(new DockerImage("")));
newNodeAttributes.withDockerImage(actualDockerImage.orElse(new DockerImage("")));
}
- publishStateToNodeRepoIfChanged(currentNodeAttributes, newNodeAttributes);
+ publishStateToNodeRepoIfChanged(context, currentNodeAttributes, newNodeAttributes);
}
- private void publishStateToNodeRepoIfChanged(NodeAttributes currentAttributes, NodeAttributes newAttributes) {
+ private void publishStateToNodeRepoIfChanged(NodeAgentContext context, NodeAttributes currentAttributes, NodeAttributes newAttributes) {
if (!currentAttributes.equals(newAttributes)) {
context.log(logger, "Publishing new set of attributes to node repo: %s -> %s",
currentAttributes, newAttributes);
@@ -257,9 +222,9 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- private void startContainer(NodeSpec node) {
- ContainerData containerData = createContainerData(context, node);
- dockerOperations.createContainer(context, node, containerData);
+ private void startContainer(NodeAgentContext context) {
+ ContainerData containerData = createContainerData(context);
+ dockerOperations.createContainer(context, containerData);
dockerOperations.startContainer(context);
lastCpuMetric = new CpuUsageReporter();
@@ -268,14 +233,15 @@ public class NodeAgentImpl implements NodeAgent {
context.log(logger, "Container successfully started, new containerState is " + containerState);
}
- private Optional<Container> removeContainerIfNeededUpdateContainerState(NodeSpec node, Optional<Container> existingContainer) {
+ private Optional<Container> removeContainerIfNeededUpdateContainerState(
+ NodeAgentContext context, Optional<Container> existingContainer) {
return existingContainer
- .flatMap(container -> removeContainerIfNeeded(node, container))
+ .flatMap(container -> removeContainerIfNeeded(context, container))
.map(container -> {
- shouldRestartServices(node).ifPresent(restartReason -> {
+ shouldRestartServices(context.node()).ifPresent(restartReason -> {
context.log(logger, "Will restart services: " + restartReason);
- restartServices(node, container);
- currentRestartGeneration = node.getWantedRestartGeneration();
+ restartServices(context, container);
+ currentRestartGeneration = context.node().getWantedRestartGeneration();
});
return container;
});
@@ -292,17 +258,18 @@ public class NodeAgentImpl implements NodeAgent {
return Optional.empty();
}
- private void restartServices(NodeSpec node, Container existingContainer) {
- if (existingContainer.state.isRunning() && node.getState() == Node.State.active) {
+ private void restartServices(NodeAgentContext context, Container existingContainer) {
+ if (existingContainer.state.isRunning() && context.node().getState() == Node.State.active) {
context.log(logger, "Restarting services");
// Since we are restarting the services we need to suspend the node.
- orchestratorSuspendNode();
+ orchestratorSuspendNode(context);
dockerOperations.restartVespa(context);
}
}
@Override
public void stopServices() {
+ NodeAgentContext context = contextSupplier.currentContext();
context.log(logger, "Stopping services");
if (containerState == ABSENT) return;
try {
@@ -315,6 +282,7 @@ public class NodeAgentImpl implements NodeAgent {
@Override
public void suspend() {
+ NodeAgentContext context = contextSupplier.currentContext();
context.log(logger, "Suspending services on node");
if (containerState == ABSENT) return;
try {
@@ -358,18 +326,18 @@ public class NodeAgentImpl implements NodeAgent {
return Optional.empty();
}
- private Optional<Container> removeContainerIfNeeded(NodeSpec node, Container existingContainer) {
- Optional<String> removeReason = shouldRemoveContainer(node, existingContainer);
+ private Optional<Container> removeContainerIfNeeded(NodeAgentContext context, Container existingContainer) {
+ Optional<String> removeReason = shouldRemoveContainer(context.node(), existingContainer);
if (removeReason.isPresent()) {
context.log(logger, "Will remove container: " + removeReason.get());
if (existingContainer.state.isRunning()) {
- if (node.getState() == Node.State.active) {
- orchestratorSuspendNode();
+ if (context.node().getState() == Node.State.active) {
+ orchestratorSuspendNode(context);
}
try {
- if (node.getState() != Node.State.dirty) {
+ if (context.node().getState() != Node.State.dirty) {
suspend();
}
stopServices();
@@ -378,9 +346,9 @@ public class NodeAgentImpl implements NodeAgent {
}
}
stopFilebeatSchedulerIfNeeded();
- storageMaintainer.handleCoreDumpsForContainer(context, node, Optional.of(existingContainer));
+ storageMaintainer.handleCoreDumpsForContainer(context, Optional.of(existingContainer));
dockerOperations.removeContainer(context, existingContainer);
- currentRebootGeneration = node.getWantedRebootGeneration();
+ currentRebootGeneration = context.node().getWantedRebootGeneration();
containerState = ABSENT;
context.log(logger, "Container successfully removed, new containerState is " + containerState);
return Optional.empty();
@@ -399,78 +367,29 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- private void signalWorkToBeDone() {
- synchronized (monitor) {
- if (!workToDoNow) {
- workToDoNow = true;
- context.log(logger, LogLevel.DEBUG, "Signaling work to be done");
- monitor.notifyAll();
- }
- }
- }
-
- void tick() {
- boolean isFrozenCopy;
- synchronized (monitor) {
- while (!workToDoNow) {
- long remainder = timeBetweenEachConverge
- .minus(Duration.between(lastConverge, clock.instant()))
- .toMillis();
- if (remainder > 0) {
- try {
- monitor.wait(remainder);
- } catch (InterruptedException e) {
- context.log(logger, LogLevel.ERROR, "Interrupted while sleeping before tick, ignoring");
- }
- } else break;
- }
- lastConverge = clock.instant();
- workToDoNow = false;
-
- if (isFrozen != wantFrozen) {
- isFrozen = wantFrozen;
- context.log(logger, "Updated NodeAgent's frozen state, new value: isFrozen: " + isFrozen);
- }
- isFrozenCopy = isFrozen;
- }
-
- if (isFrozenCopy) {
- context.log(logger, LogLevel.DEBUG, "tick: isFrozen");
- } else {
- try {
- converge();
- } catch (OrchestratorException | ConvergenceException e) {
- context.log(logger, e.getMessage());
- } catch (ContainerNotFoundException e) {
- containerState = ABSENT;
- context.log(logger, LogLevel.WARNING, "Container unexpectedly gone, resetting containerState to " + containerState);
- } catch (DockerException e) {
- numberOfUnhandledException++;
- context.log(logger, LogLevel.ERROR, "Caught a DockerException", e);
- } catch (Exception e) {
- numberOfUnhandledException++;
- context.log(logger, LogLevel.ERROR, "Unhandled exception, ignoring.", e);
- }
+ public void converge(NodeAgentContext context) {
+ try {
+ doConverge(context);
+ } catch (OrchestratorException | ConvergenceException e) {
+ context.log(logger, e.getMessage());
+ } catch (ContainerNotFoundException e) {
+ containerState = ABSENT;
+ context.log(logger, LogLevel.WARNING, "Container unexpectedly gone, resetting containerState to " + containerState);
+ } catch (DockerException e) {
+ numberOfUnhandledException++;
+ context.log(logger, LogLevel.ERROR, "Caught a DockerException", e);
+ } catch (Throwable e) {
+ numberOfUnhandledException++;
+ context.log(logger, LogLevel.ERROR, "Unhandled exception, ignoring", e);
}
}
// Public for testing
- void converge() {
- final Optional<NodeSpec> optionalNode = nodeRepository.getOptionalNode(context.hostname().value());
-
- // We just removed the node from node repo, so this is expected until NodeAdmin stop this NodeAgent
- if (!optionalNode.isPresent() && expectNodeNotInNodeRepo) {
- context.log(logger, LogLevel.INFO, "Node removed from node repo (as expected)");
- return;
- }
-
- final NodeSpec node = optionalNode.orElseThrow(() ->
- new IllegalStateException(String.format("Node '%s' missing from node repository", context.hostname())));
- expectNodeNotInNodeRepo = false;
-
- Optional<Container> container = getContainer();
+ void doConverge(NodeAgentContext context) {
+ NodeSpec node = context.node();
+ Optional<Container> container = getContainer(context);
if (!node.equals(lastNode)) {
- logChangesToNodeSpec(lastNode, node);
+ logChangesToNodeSpec(context, lastNode, node);
// Current reboot generation uninitialized or incremented from outside to cancel reboot
if (currentRebootGeneration < node.getCurrentRebootGeneration())
@@ -485,7 +404,7 @@ public class NodeAgentImpl implements NodeAgent {
// Every time the node spec changes, we should clear the metrics for this container as the dimensions
// will change and we will be reporting duplicate metrics.
if (container.map(c -> c.state.isRunning()).orElse(false)) {
- storageMaintainer.writeMetricsConfig(context, node);
+ storageMaintainer.writeMetricsConfig(context);
}
lastNode = node;
@@ -496,11 +415,11 @@ public class NodeAgentImpl implements NodeAgent {
case reserved:
case parked:
case failed:
- removeContainerIfNeededUpdateContainerState(node, container);
- updateNodeRepoWithCurrentAttributes(node);
+ removeContainerIfNeededUpdateContainerState(context, container);
+ updateNodeRepoWithCurrentAttributes(context);
break;
case active:
- storageMaintainer.handleCoreDumpsForContainer(context, node, container);
+ storageMaintainer.handleCoreDumpsForContainer(context, container);
storageMaintainer.getDiskUsageFor(context)
.map(diskUsage -> (double) diskUsage / BYTES_IN_GB / node.getMinDiskAvailableGb())
@@ -512,17 +431,17 @@ public class NodeAgentImpl implements NodeAgent {
context.log(logger, LogLevel.DEBUG, "Waiting for image to download " + imageBeingDownloaded.asString());
return;
}
- container = removeContainerIfNeededUpdateContainerState(node, container);
+ container = removeContainerIfNeededUpdateContainerState(context, container);
athenzCredentialsMaintainer.ifPresent(maintainer -> maintainer.converge(context));
if (! container.isPresent()) {
containerState = STARTING;
- startContainer(node);
+ startContainer(context);
containerState = UNKNOWN;
aclMaintainer.ifPresent(AclMaintainer::converge);
}
- startServicesIfNeeded();
- resumeNodeIfNeeded(node);
+ startServicesIfNeeded(context);
+ resumeNodeIfNeeded(context);
healthChecker.ifPresent(checker -> checker.verifyHealth(context));
// Because it's more important to stop a bad release from rolling out in prod,
@@ -535,32 +454,31 @@ public class NodeAgentImpl implements NodeAgent {
// has been successfully rolled out.
// - Slobrok and internal orchestrator state is used to determine whether
// to allow upgrade (suspend).
- updateNodeRepoWithCurrentAttributes(node);
+ updateNodeRepoWithCurrentAttributes(context);
context.log(logger, "Call resume against Orchestrator");
orchestrator.resume(context.hostname().value());
break;
case inactive:
- removeContainerIfNeededUpdateContainerState(node, container);
- updateNodeRepoWithCurrentAttributes(node);
+ removeContainerIfNeededUpdateContainerState(context, container);
+ updateNodeRepoWithCurrentAttributes(context);
break;
case provisioned:
nodeRepository.setNodeState(context.hostname().value(), Node.State.dirty);
break;
case dirty:
- removeContainerIfNeededUpdateContainerState(node, container);
+ removeContainerIfNeededUpdateContainerState(context, container);
context.log(logger, "State is " + node.getState() + ", will delete application storage and mark node as ready");
athenzCredentialsMaintainer.ifPresent(maintainer -> maintainer.clearCredentials(context));
storageMaintainer.archiveNodeStorage(context);
- updateNodeRepoWithCurrentAttributes(node);
+ updateNodeRepoWithCurrentAttributes(context);
nodeRepository.setNodeState(context.hostname().value(), Node.State.ready);
- expectNodeNotInNodeRepo = true;
break;
default:
throw new RuntimeException("UNKNOWN STATE " + node.getState().name());
}
}
- private void logChangesToNodeSpec(NodeSpec lastNode, NodeSpec node) {
+ private static void logChangesToNodeSpec(NodeAgentContext context, NodeSpec lastNode, NodeSpec node) {
StringBuilder builder = new StringBuilder();
appendIfDifferent(builder, "state", lastNode, node, NodeSpec::getState);
if (builder.length() > 0) {
@@ -572,7 +490,7 @@ public class NodeAgentImpl implements NodeAgent {
return value == null ? "[absent]" : value.toString();
}
- private <T> void appendIfDifferent(StringBuilder builder, String name, NodeSpec oldNode, NodeSpec newNode, Function<NodeSpec, T> getter) {
+ private static <T> void appendIfDifferent(StringBuilder builder, String name, NodeSpec oldNode, NodeSpec newNode, Function<NodeSpec, T> getter) {
T oldValue = oldNode == null ? null : getter.apply(oldNode);
T newValue = getter.apply(newNode);
if (!Objects.equals(oldValue, newValue)) {
@@ -592,8 +510,9 @@ public class NodeAgentImpl implements NodeAgent {
@SuppressWarnings("unchecked")
public void updateContainerNodeMetrics() {
- final NodeSpec node = lastNode;
- if (node == null || containerState != UNKNOWN) return;
+ if (containerState != UNKNOWN) return;
+ final NodeAgentContext context = contextSupplier.currentContext();
+ final NodeSpec node = context.node();
Optional<ContainerStats> containerStats = dockerOperations.getContainerStats(context);
if (!containerStats.isPresent()) return;
@@ -660,10 +579,10 @@ public class NodeAgentImpl implements NodeAgent {
metrics.add(networkMetrics);
});
- pushMetricsToContainer(metrics);
+ pushMetricsToContainer(context, metrics);
}
- private void pushMetricsToContainer(List<DimensionMetrics> metrics) {
+ private void pushMetricsToContainer(NodeAgentContext context, List<DimensionMetrics> metrics) {
StringBuilder params = new StringBuilder();
try {
for (DimensionMetrics dimensionMetrics : metrics) {
@@ -679,7 +598,7 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- private Optional<Container> getContainer() {
+ private Optional<Container> getContainer(NodeAgentContext context) {
if (containerState == ABSENT) return Optional.empty();
Optional<Container> container = dockerOperations.getContainer(context);
if (! container.isPresent()) containerState = ABSENT;
@@ -743,12 +662,12 @@ public class NodeAgentImpl implements NodeAgent {
// More generally, the node repo response should contain sufficient info on what the docker image is,
// to allow the node admin to make decisions that depend on the docker image. Or, each docker image
// needs to contain routines for drain and suspend. For many images, these can just be dummy routines.
- private void orchestratorSuspendNode() {
+ private void orchestratorSuspendNode(NodeAgentContext context) {
context.log(logger, "Ask Orchestrator for permission to suspend node");
orchestrator.suspend(context.hostname().value());
}
- protected ContainerData createContainerData(NodeAgentContext context, NodeSpec node) {
+ protected ContainerData createContainerData(NodeAgentContext context) {
return (pathInContainer, data) -> {
throw new UnsupportedOperationException("addFile not implemented");
};
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentScheduler.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentScheduler.java
new file mode 100644
index 00000000000..540601ffa4f
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentScheduler.java
@@ -0,0 +1,21 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+import java.time.Duration;
+
+/**
+ * @author freva
+ */
+public interface NodeAgentScheduler {
+
+ /** Schedule a tick for NodeAgent to run with the given NodeAgentContext */
+ void scheduleTickWith(NodeAgentContext context);
+
+ /**
+ * Will eventually freeze/unfreeze the node agent
+ * @param frozen whether node agent should be frozen
+ * @param timeout maximum duration this method should block while waiting for NodeAgent to reach target state
+ * @return True if node agent has converged to the desired state
+ */
+ boolean setFrozen(boolean frozen, Duration timeout);
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java
new file mode 100644
index 00000000000..c9e4e33f8bb
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java
@@ -0,0 +1,41 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.flags;
+
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+import com.yahoo.vespa.flags.json.wire.WireFlagData;
+import com.yahoo.vespa.flags.json.wire.WireFlagDataList;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
+import org.hamcrest.collection.IsMapContaining;
+import org.hamcrest.collection.IsMapWithSize;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author hakonhall
+ */
+public class RealFlagRepositoryTest {
+ private final ConfigServerApi configServerApi = mock(ConfigServerApi.class);
+ private final RealFlagRepository repository = new RealFlagRepository(configServerApi);
+
+ @Test
+ public void test() {
+ WireFlagDataList list = new WireFlagDataList();
+ list.flags = new ArrayList<>();
+ list.flags.add(new WireFlagData());
+ list.flags.get(0).id = "id1";
+
+ when(configServerApi.get(any(), eq(WireFlagDataList.class))).thenReturn(list);
+ Map<FlagId, FlagData> allFlagData = repository.getAllFlagData();
+ assertThat(allFlagData, IsMapWithSize.aMapWithSize(1));
+ assertThat(allFlagData, IsMapContaining.hasKey(new FlagId("id1")));
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
index cb3a1fb5e2c..109bce4c13f 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
@@ -15,8 +15,9 @@ import com.yahoo.vespa.hosted.node.admin.docker.DockerOperationsImpl;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextFactory;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentFactory;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPAddressesMock;
import com.yahoo.vespa.hosted.provision.Node;
@@ -30,7 +31,6 @@ import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
import java.util.Optional;
-import java.util.function.Function;
import java.util.logging.Logger;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -87,19 +87,20 @@ public class DockerTester implements AutoCloseable {
.build();
nodeRepository.updateNodeRepositoryNode(hostSpec);
- Clock clock = Clock.systemUTC();
FileSystem fileSystem = TestFileSystem.create();
DockerOperations dockerOperations = new DockerOperationsImpl(docker, processExecuter, ipAddresses);
MetricReceiverWrapper mr = new MetricReceiverWrapper(MetricReceiver.nullImplementation);
- Function<String, NodeAgent> nodeAgentFactory = (hostName) -> new NodeAgentImpl(
- new NodeAgentContextImpl.Builder(hostName).fileSystem(fileSystem).build(), nodeRepository,
- orchestrator, dockerOperations, storageMaintainer, clock, INTERVAL, Optional.empty(), Optional.empty(), Optional.empty());
- nodeAdmin = new NodeAdminImpl(nodeAgentFactory, Optional.empty(), mr, Clock.systemUTC());
+ NodeAgentFactory nodeAgentFactory = contextSupplier -> new NodeAgentImpl(
+ contextSupplier, nodeRepository,
+ orchestrator, dockerOperations, storageMaintainer, Optional.empty(), Optional.empty(), Optional.empty());
+ NodeAgentContextFactory nodeAgentContextFactory = nodeSpec ->
+ new NodeAgentContextImpl.Builder(nodeSpec).fileSystem(fileSystem).build();
+ nodeAdmin = new NodeAdminImpl(nodeAgentFactory, nodeAgentContextFactory, Optional.empty(), mr, Clock.systemUTC());
nodeAdminStateUpdater = new NodeAdminStateUpdater(nodeRepository, orchestrator,
nodeAdmin, HOST_HOSTNAME);
- this.loopThread = new Thread(() -> {
+ loopThread = new Thread(() -> {
nodeAdminStateUpdater.start();
while (! terminated) {
@@ -135,8 +136,10 @@ public class DockerTester implements AutoCloseable {
@Override
public void close() {
- terminated = true;
+ // First, stop NodeAdmin and all the NodeAgents
+ nodeAdmin.stop();
+ terminated = true;
do {
try {
loopThread.join();
@@ -144,8 +147,5 @@ public class DockerTester implements AutoCloseable {
e.printStackTrace();
}
} while (loopThread.isAlive());
-
- // Finally, stop NodeAdmin and all the NodeAgents
- nodeAdmin.stop();
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
index 9ea5c87511b..05b9c413594 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
@@ -152,12 +152,8 @@ public class StorageMaintainerTest {
}
private Path executeAs(NodeType nodeType) {
- NodeAgentContext context = new NodeAgentContextImpl.Builder("host123-5.test.domain.tld")
- .nodeType(nodeType)
- .fileSystem(TestFileSystem.create())
- .zoneId(new ZoneId(SystemName.dev, Environment.prod, RegionName.from("us-north-1"))).build();
NodeSpec nodeSpec = new NodeSpec.Builder()
- .hostname(context.hostname().value())
+ .hostname("host123-5.test.domain.tld")
.nodeType(nodeType)
.state(Node.State.active)
.parentHostname("host123.test.domain.tld")
@@ -167,9 +163,12 @@ public class StorageMaintainerTest {
.flavor("d-2-8-50")
.canonicalFlavor("d-2-8-50")
.build();
+ NodeAgentContext context = new NodeAgentContextImpl.Builder(nodeSpec)
+ .fileSystem(TestFileSystem.create())
+ .zoneId(new ZoneId(SystemName.dev, Environment.prod, RegionName.from("us-north-1"))).build();
Path path = context.pathOnHostFromPathInNode("/etc/yamas-agent");
uncheck(() -> Files.createDirectories(path));
- storageMaintainer.writeMetricsConfig(context, nodeSpec);
+ storageMaintainer.writeMetricsConfig(context);
return path;
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java
index 3860e2e9780..47e220a968b 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java
@@ -1,22 +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.vespa.hosted.node.admin.nodeadmin;
+import com.yahoo.config.provision.NodeType;
import com.yahoo.metrics.simple.MetricReceiver;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextFactory;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
+import com.yahoo.vespa.hosted.provision.Node;
import org.junit.Test;
import org.mockito.InOrder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -31,75 +32,72 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl.NodeAgentWithScheduler;
+import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl.NodeAgentWithSchedulerFactory;
+
/**
* @author bakksjo
*/
public class NodeAdminImplTest {
- // Trick to allow mocking of typed interface without casts/warnings.
- private interface NodeAgentFactory extends Function<String, NodeAgent> {}
- private final Function<String, NodeAgent> nodeAgentFactory = mock(NodeAgentFactory.class);
+
+ private final NodeAgentWithSchedulerFactory nodeAgentWithSchedulerFactory = mock(NodeAgentWithSchedulerFactory.class);
+ private final NodeAgentContextFactory nodeAgentContextFactory = mock(NodeAgentContextFactory.class);
private final ManualClock clock = new ManualClock();
- private final NodeAdminImpl nodeAdmin = new NodeAdminImpl(nodeAgentFactory, Optional.empty(),
- new MetricReceiverWrapper(MetricReceiver.nullImplementation), clock);
+ private final NodeAdminImpl nodeAdmin = new NodeAdminImpl(nodeAgentWithSchedulerFactory, nodeAgentContextFactory,
+ Optional.empty(), new MetricReceiverWrapper(MetricReceiver.nullImplementation), clock);
@Test
public void nodeAgentsAreProperlyLifeCycleManaged() {
- final String hostName1 = "host1.test.yahoo.com";
- final String hostName2 = "host2.test.yahoo.com";
- final NodeAgent nodeAgent1 = mock(NodeAgentImpl.class);
- final NodeAgent nodeAgent2 = mock(NodeAgentImpl.class);
- when(nodeAgentFactory.apply(eq(hostName1))).thenReturn(nodeAgent1);
- when(nodeAgentFactory.apply(eq(hostName2))).thenReturn(nodeAgent2);
+ final NodeSpec nodeSpec1 = createNodeSpec("host1.test.yahoo.com");
+ final NodeSpec nodeSpec2 = createNodeSpec("host2.test.yahoo.com");
+ final NodeAgentWithScheduler nodeAgent1 = mockNodeAgentWithSchedulerFactory(nodeSpec1);
+ final NodeAgentWithScheduler nodeAgent2 = mockNodeAgentWithSchedulerFactory(nodeSpec2);
+ final InOrder inOrder = inOrder(nodeAgentWithSchedulerFactory, nodeAgent1, nodeAgent2);
+ nodeAdmin.refreshContainersToRun(Collections.emptyList());
+ verifyNoMoreInteractions(nodeAgentWithSchedulerFactory);
- final InOrder inOrder = inOrder(nodeAgentFactory, nodeAgent1, nodeAgent2);
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.emptySet());
- verifyNoMoreInteractions(nodeAgentFactory);
-
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.singleton(hostName1));
- inOrder.verify(nodeAgentFactory).apply(hostName1);
+ nodeAdmin.refreshContainersToRun(Collections.singletonList(nodeSpec1));
inOrder.verify(nodeAgent1).start();
+ inOrder.verify(nodeAgent2, never()).start();
inOrder.verify(nodeAgent1, never()).stop();
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.singleton(hostName1));
- inOrder.verify(nodeAgentFactory, never()).apply(any(String.class));
+ nodeAdmin.refreshContainersToRun(Collections.singletonList(nodeSpec1));
+ inOrder.verify(nodeAgentWithSchedulerFactory, never()).create(any());
inOrder.verify(nodeAgent1, never()).start();
inOrder.verify(nodeAgent1, never()).stop();
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.emptySet());
- inOrder.verify(nodeAgentFactory, never()).apply(any(String.class));
+ nodeAdmin.refreshContainersToRun(Collections.emptyList());
+ inOrder.verify(nodeAgentWithSchedulerFactory, never()).create(any());
verify(nodeAgent1).stop();
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.singleton(hostName2));
- inOrder.verify(nodeAgentFactory).apply(hostName2);
+ nodeAdmin.refreshContainersToRun(Collections.singletonList(nodeSpec2));
inOrder.verify(nodeAgent2).start();
inOrder.verify(nodeAgent2, never()).stop();
- verify(nodeAgent1).stop();
+ inOrder.verify(nodeAgent1, never()).stop();
- nodeAdmin.synchronizeNodesToNodeAgents(Collections.emptySet());
- inOrder.verify(nodeAgentFactory, never()).apply(any(String.class));
+ nodeAdmin.refreshContainersToRun(Collections.emptyList());
+ inOrder.verify(nodeAgentWithSchedulerFactory, never()).create(any());
inOrder.verify(nodeAgent2, never()).start();
inOrder.verify(nodeAgent2).stop();
-
- verifyNoMoreInteractions(nodeAgent1);
- verifyNoMoreInteractions(nodeAgent2);
+ inOrder.verify(nodeAgent1, never()).start();
+ inOrder.verify(nodeAgent1, never()).stop();
}
@Test
public void testSetFrozen() {
- List<NodeAgent> nodeAgents = new ArrayList<>();
- Set<String> existingContainerHostnames = new HashSet<>();
+ List<NodeSpec> nodeSpecs = new ArrayList<>();
+ List<NodeAgentWithScheduler> nodeAgents = new ArrayList<>();
for (int i = 0; i < 3; i++) {
- final String hostName = "host" + i + ".test.yahoo.com";
- NodeAgent nodeAgent = mock(NodeAgent.class);
- nodeAgents.add(nodeAgent);
- when(nodeAgentFactory.apply(eq(hostName))).thenReturn(nodeAgent);
+ NodeSpec nodeSpec = createNodeSpec("host" + i + ".test.yahoo.com");
+ NodeAgentWithScheduler nodeAgent = mockNodeAgentWithSchedulerFactory(nodeSpec);
- existingContainerHostnames.add(hostName);
+ nodeSpecs.add(nodeSpec);
+ nodeAgents.add(nodeAgent);
}
- nodeAdmin.synchronizeNodesToNodeAgents(existingContainerHostnames);
+ nodeAdmin.refreshContainersToRun(nodeSpecs);
assertTrue(nodeAdmin.isFrozen()); // Initially everything is frozen to force convergence
mockNodeAgentSetFrozenResponse(nodeAgents, true, true, true);
@@ -155,10 +153,28 @@ public class NodeAdminImplTest {
assertEquals(Duration.ofSeconds(1), nodeAdmin.subsystemFreezeDuration());
}
- private void mockNodeAgentSetFrozenResponse(List<NodeAgent> nodeAgents, boolean... responses) {
+ private void mockNodeAgentSetFrozenResponse(List<NodeAgentWithScheduler> nodeAgents, boolean... responses) {
for (int i = 0; i < nodeAgents.size(); i++) {
- NodeAgent nodeAgent = nodeAgents.get(i);
- when(nodeAgent.setFrozen(anyBoolean())).thenReturn(responses[i]);
+ NodeAgentWithScheduler nodeAgent = nodeAgents.get(i);
+ when(nodeAgent.setFrozen(anyBoolean(), any())).thenReturn(responses[i]);
}
}
+
+ private NodeSpec createNodeSpec(String hostname) {
+ return new NodeSpec.Builder()
+ .hostname(hostname)
+ .state(Node.State.active)
+ .nodeType(NodeType.tenant)
+ .flavor("default")
+ .build();
+ }
+
+ private NodeAgentWithScheduler mockNodeAgentWithSchedulerFactory(NodeSpec nodeSpec) {
+ NodeAgentContext context = new NodeAgentContextImpl.Builder(nodeSpec).build();
+ when(nodeAgentContextFactory.create(eq(nodeSpec))).thenReturn(context);
+
+ NodeAgentWithScheduler nodeAgentWithScheduler = mock(NodeAgentWithScheduler.class);
+ when(nodeAgentWithSchedulerFactory.create(eq(context))).thenReturn(nodeAgentWithScheduler);
+ return nodeAgentWithScheduler;
+ }
}
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 437195ca6d5..74ba5561c8e 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
@@ -42,7 +42,7 @@ public class NodeAdminStateUpdaterTest {
private final NodeAdmin nodeAdmin = mock(NodeAdmin.class);
private final HostName hostHostname = HostName.from("basehost1.test.yahoo.com");
- private final NodeAdminStateUpdater refresher = spy(new NodeAdminStateUpdater(
+ private final NodeAdminStateUpdater updater = spy(new NodeAdminStateUpdater(
nodeRepository, orchestrator, nodeAdmin, hostHostname));
@@ -58,19 +58,19 @@ public class NodeAdminStateUpdaterTest {
{
// Initially everything is frozen to force convergence
- assertResumeStateError(RESUMED, "NodeAdmin is not yet unfrozen");
+ assertConvergeError(RESUMED, "NodeAdmin is not yet unfrozen");
when(nodeAdmin.setFrozen(eq(false))).thenReturn(true);
- refresher.converge(RESUMED);
+ updater.converge(RESUMED);
verify(orchestrator, times(1)).resume(hostHostname.value());
// We are already resumed, so this should return without resuming again
- refresher.converge(RESUMED);
+ updater.converge(RESUMED);
verify(orchestrator, times(1)).resume(hostHostname.value());
verify(nodeAdmin, times(2)).setFrozen(eq(false));
// Lets try to suspend node admin only
when(nodeAdmin.setFrozen(eq(true))).thenReturn(false);
- assertResumeStateError(SUSPENDED_NODE_ADMIN, "NodeAdmin is not yet frozen");
+ assertConvergeError(SUSPENDED_NODE_ADMIN, "NodeAdmin is not yet frozen");
verify(nodeAdmin, times(2)).setFrozen(eq(false));
}
@@ -81,10 +81,10 @@ public class NodeAdminStateUpdaterTest {
when(nodeAdmin.setFrozen(eq(true))).thenReturn(true);
doThrow(new RuntimeException(exceptionMessage)).doNothing()
.when(orchestrator).suspend(eq(hostHostname.value()));
- assertResumeStateError(SUSPENDED_NODE_ADMIN, exceptionMessage);
+ assertConvergeError(SUSPENDED_NODE_ADMIN, exceptionMessage);
verify(nodeAdmin, times(2)).setFrozen(eq(false));
- refresher.converge(SUSPENDED_NODE_ADMIN);
+ updater.converge(SUSPENDED_NODE_ADMIN);
verify(nodeAdmin, times(2)).setFrozen(eq(false));
}
@@ -93,13 +93,13 @@ public class NodeAdminStateUpdaterTest {
final String exceptionMessage = "Failed to stop services";
verify(orchestrator, times(0)).suspend(eq(hostHostname.value()), eq(suspendHostnames));
doThrow(new RuntimeException(exceptionMessage)).doNothing().when(nodeAdmin).stopNodeAgentServices(eq(activeHostnames));
- assertResumeStateError(SUSPENDED, exceptionMessage);
+ assertConvergeError(SUSPENDED, exceptionMessage);
verify(orchestrator, times(1)).suspend(eq(hostHostname.value()), eq(suspendHostnames));
// Make sure we dont roll back if we fail to stop services - we will try to stop again next tick
verify(nodeAdmin, times(2)).setFrozen(eq(false));
// Finally we are successful in transitioning to frozen
- refresher.converge(SUSPENDED);
+ updater.converge(SUSPENDED);
}
}
@@ -110,31 +110,38 @@ public class NodeAdminStateUpdaterTest {
// Initially everything is frozen to force convergence
when(nodeAdmin.setFrozen(eq(false))).thenReturn(true);
- refresher.converge(RESUMED);
+ updater.converge(RESUMED);
verify(nodeAdmin, times(1)).setFrozen(eq(false));
+ verify(nodeAdmin, times(1)).refreshContainersToRun(any());
// 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(exceptionMsg)).when(orchestrator).suspend(eq(hostHostname.value()));
- assertResumeStateError(SUSPENDED_NODE_ADMIN, exceptionMsg);
+ assertConvergeError(SUSPENDED_NODE_ADMIN, exceptionMsg);
verify(nodeAdmin, times(1)).setFrozen(eq(true));
- assertResumeStateError(SUSPENDED_NODE_ADMIN, exceptionMsg);
+ verify(orchestrator, times(1)).suspend(eq(hostHostname.value()));
+ assertConvergeError(SUSPENDED_NODE_ADMIN, exceptionMsg);
verify(nodeAdmin, times(2)).setFrozen(eq(true));
- assertResumeStateError(SUSPENDED_NODE_ADMIN, exceptionMsg);
+ verify(orchestrator, times(2)).suspend(eq(hostHostname.value()));
+ assertConvergeError(SUSPENDED_NODE_ADMIN, exceptionMsg);
verify(nodeAdmin, times(3)).setFrozen(eq(true));
- verify(nodeAdmin, times(1)).setFrozen(eq(false)); // No new unfreezes during last 2 ticks
+ verify(orchestrator, times(3)).suspend(eq(hostHostname.value()));
+
+ // No new unfreezes nor refresh while trying to freeze
+ verify(nodeAdmin, times(1)).setFrozen(eq(false));
verify(nodeAdmin, times(1)).refreshContainersToRun(any());
// Only resume and fetch containers when subsystem freeze duration expires
when(nodeAdmin.subsystemFreezeDuration()).thenReturn(Duration.ofHours(1));
- assertResumeStateError(SUSPENDED_NODE_ADMIN, exceptionMsg);
+ assertConvergeError(SUSPENDED_NODE_ADMIN, "Timed out trying to freeze all nodes: will force an unfrozen tick");
verify(nodeAdmin, times(2)).setFrozen(eq(false));
+ verify(orchestrator, times(3)).suspend(eq(hostHostname.value())); // no new suspend calls
verify(nodeAdmin, times(2)).refreshContainersToRun(any());
// We change our mind, want to remain resumed
- refresher.converge(RESUMED);
+ updater.converge(RESUMED);
verify(nodeAdmin, times(3)).setFrozen(eq(false)); // Make sure that we unfreeze!
}
@@ -146,24 +153,24 @@ public class NodeAdminStateUpdaterTest {
// Resume and suspend only require that node-agents are frozen and permission from
// orchestrator to resume/suspend host. Therefore, if host is not active, we only need to freeze.
- refresher.converge(RESUMED);
+ updater.converge(RESUMED);
verify(orchestrator, never()).resume(eq(hostHostname.value()));
- refresher.converge(SUSPENDED_NODE_ADMIN);
+ updater.converge(SUSPENDED_NODE_ADMIN);
verify(orchestrator, never()).suspend(eq(hostHostname.value()));
// When doing batch suspend, only suspend the containers if the host is not active
List<String> activeHostnames = nodeRepository.getNodes(hostHostname.value()).stream()
.map(NodeSpec::getHostname)
.collect(Collectors.toList());
- refresher.converge(SUSPENDED);
+ updater.converge(SUSPENDED);
verify(orchestrator, times(1)).suspend(eq(hostHostname.value()), eq(activeHostnames));
}
- private void assertResumeStateError(NodeAdminStateUpdater.State targetState, String reason) {
+ private void assertConvergeError(NodeAdminStateUpdater.State targetState, String reason) {
try {
- refresher.converge(targetState);
- fail("Expected set resume state to fail with \"" + reason + "\", but it succeeded without error");
+ updater.converge(targetState);
+ fail("Expected converging to " + targetState + " to fail with \"" + reason + "\", but it succeeded without error");
} catch (RuntimeException e) {
assertEquals(reason, e.getMessage());
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManagerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManagerTest.java
new file mode 100644
index 00000000000..f32e3d91e34
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManagerTest.java
@@ -0,0 +1,142 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.nodeagent;
+
+import org.junit.Test;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author freva
+ */
+public class NodeAgentContextManagerTest {
+
+ private static final int TIMEOUT = 10_000;
+
+ private final Clock clock = Clock.systemUTC();
+ private final NodeAgentContext initialContext = generateContext();
+ private final NodeAgentContextManager manager = new NodeAgentContextManager(clock, initialContext);
+
+ @Test(timeout = TIMEOUT)
+ public void returns_immediately_if_next_context_is_ready() throws InterruptedException {
+ NodeAgentContext context1 = generateContext();
+ manager.scheduleTickWith(context1);
+
+ assertSame(initialContext, manager.currentContext());
+ assertSame(context1, manager.nextContext());
+ assertSame(context1, manager.currentContext());
+ }
+
+ @Test(timeout = TIMEOUT)
+ public void blocks_in_nextContext_until_one_is_scheduled() throws InterruptedException {
+ AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
+ assertFalse(async.response.isPresent());
+ Thread.sleep(10);
+ assertFalse(async.response.isPresent());
+
+ NodeAgentContext context1 = generateContext();
+ manager.scheduleTickWith(context1);
+
+ async.awaitResult();
+ assertEquals(Optional.of(context1), async.response);
+ assertFalse(async.exception.isPresent());
+ }
+
+ @Test(timeout = TIMEOUT)
+ public void blocks_in_nextContext_until_interrupt() throws InterruptedException {
+ AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
+ assertFalse(async.response.isPresent());
+ Thread.sleep(10);
+ assertFalse(async.response.isPresent());
+
+ manager.interrupt();
+
+ async.awaitResult();
+ assertEquals(Optional.of(InterruptedException.class), async.exception.map(Exception::getClass));
+ assertFalse(async.response.isPresent());
+ }
+
+ @Test(timeout = TIMEOUT)
+ public void setFrozen_does_not_block_with_no_timeout() throws InterruptedException {
+ assertFalse(manager.setFrozen(false, Duration.ZERO));
+
+ // Generate new context and get it from the supplier, this completes the unfreeze
+ NodeAgentContext context1 = generateContext();
+ manager.scheduleTickWith(context1);
+ assertSame(context1, manager.nextContext());
+
+ assertTrue(manager.setFrozen(false, Duration.ZERO));
+ }
+
+ @Test(timeout = TIMEOUT)
+ public void setFrozen_blocks_at_least_for_duration_of_timeout() {
+ long wantedDurationMillis = 100;
+ long start = clock.millis();
+ assertFalse(manager.setFrozen(false, Duration.ofMillis(wantedDurationMillis)));
+ long actualDurationMillis = clock.millis() - start;
+
+ assertTrue(actualDurationMillis >= wantedDurationMillis);
+ }
+
+ @Test(timeout = TIMEOUT)
+ public void setFrozen_is_successful_if_converged_in_time() throws InterruptedException {
+ AsyncExecutor<Boolean> async = new AsyncExecutor<>(() -> manager.setFrozen(false, Duration.ofMillis(500)));
+
+ assertFalse(async.response.isPresent());
+
+ NodeAgentContext context1 = generateContext();
+ manager.scheduleTickWith(context1);
+ assertSame(context1, manager.nextContext());
+
+ async.awaitResult();
+ assertEquals(Optional.of(true), async.response);
+ assertFalse(async.exception.isPresent());
+ }
+
+ private static NodeAgentContext generateContext() {
+ return new NodeAgentContextImpl.Builder("container-123.domain.tld").build();
+ }
+
+ private class AsyncExecutor<T> {
+ private final Object monitor = new Object();
+ private final Thread thread;
+ private volatile Optional<T> response = Optional.empty();
+ private volatile Optional<Exception> exception = Optional.empty();
+ private boolean completed = false;
+
+ private AsyncExecutor(ThrowingSupplier<T> supplier) {
+ this.thread = new Thread(() -> {
+ try {
+ response = Optional.of(supplier.get());
+ } catch (Exception e) {
+ exception = Optional.of(e);
+ }
+ synchronized (monitor) {
+ completed = true;
+ monitor.notifyAll();
+ }
+ });
+ this.thread.start();
+ }
+
+ private void awaitResult() {
+ synchronized (monitor) {
+ while (!completed) {
+ try {
+ monitor.wait();
+ } catch (InterruptedException ignored) { }
+ }
+ }
+ }
+ }
+
+ private interface ThrowingSupplier<T> {
+ T get() throws Exception;
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
index b6128fc8693..e392ac34414 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
@@ -4,8 +4,8 @@ package com.yahoo.vespa.hosted.node.admin.nodeagent;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.config.provision.NodeType;
import com.yahoo.metrics.simple.MetricReceiver;
-import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.dockerapi.Container;
+import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.dockerapi.ContainerResources;
import com.yahoo.vespa.hosted.dockerapi.ContainerStats;
import com.yahoo.vespa.hosted.dockerapi.exception.DockerException;
@@ -13,6 +13,7 @@ import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeAttributes;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
@@ -28,7 +29,6 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@@ -36,8 +36,6 @@ import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -56,14 +54,21 @@ import static org.mockito.Mockito.when;
* @author Øyvind Bakksjø
*/
public class NodeAgentImplTest {
- private static final Duration NODE_AGENT_SCAN_INTERVAL = Duration.ofSeconds(30);
private static final double MIN_CPU_CORES = 2;
private static final double MIN_MAIN_MEMORY_AVAILABLE_GB = 16;
private static final double MIN_DISK_AVAILABLE_GB = 250;
private static final String vespaVersion = "1.2.3";
private final String hostName = "host1.test.yahoo.com";
- private final NodeAgentContext context = new NodeAgentContextImpl.Builder(hostName).build();
+ private final NodeSpec.Builder nodeBuilder = new NodeSpec.Builder()
+ .hostname(hostName)
+ .nodeType(NodeType.tenant)
+ .flavor("docker")
+ .minCpuCores(MIN_CPU_CORES)
+ .minMainMemoryAvailableGb(MIN_MAIN_MEMORY_AVAILABLE_GB)
+ .minDiskAvailableGb(MIN_DISK_AVAILABLE_GB);
+
+ private final NodeAgentContextSupplier contextSupplier = mock(NodeAgentContextSupplier.class);
private final DockerImage dockerImage = new DockerImage("dockerImage");
private final DockerOperations dockerOperations = mock(DockerOperations.class);
private final NodeRepository nodeRepository = mock(NodeRepository.class);
@@ -76,16 +81,6 @@ public class NodeAgentImplTest {
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
private final AthenzCredentialsMaintainer athenzCredentialsMaintainer = mock(AthenzCredentialsMaintainer.class);
- private final ManualClock clock = new ManualClock();
-
- private final NodeSpec.Builder nodeBuilder = new NodeSpec.Builder()
- .hostname(context.hostname().value())
- .nodeType(NodeType.tenant)
- .flavor("docker")
- .minCpuCores(MIN_CPU_CORES)
- .minMainMemoryAvailableGb(MIN_MAIN_MEMORY_AVAILABLE_GB)
- .minDiskAvailableGb(MIN_DISK_AVAILABLE_GB);
-
@Test
public void upToDateContainerIsUntouched() {
@@ -97,11 +92,12 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(187500000000L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(dockerOperations, never()).removeContainer(eq(context), any());
verify(orchestrator, never()).suspend(any(String.class));
@@ -125,11 +121,12 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(217432719360L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(storageMaintainer, times(1)).removeOldFilesFromNode(eq(context));
}
@@ -145,27 +142,28 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(187500000000L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
inOrder.verify(dockerOperations, never()).startServices(eq(context));
inOrder.verify(dockerOperations, times(1)).resumeNode(eq(context));
nodeAgent.suspend();
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
inOrder.verify(dockerOperations, never()).startServices(eq(context));
inOrder.verify(dockerOperations, times(1)).resumeNode(eq(context)); // Expect a resume, but no start services
// No new suspends/stops, so no need to resume/start
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
inOrder.verify(dockerOperations, never()).startServices(eq(context));
inOrder.verify(dockerOperations, never()).resumeNode(eq(context));
nodeAgent.suspend();
nodeAgent.stopServices();
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
inOrder.verify(dockerOperations, times(1)).startServices(eq(context));
inOrder.verify(dockerOperations, times(1)).resumeNode(eq(context));
}
@@ -181,13 +179,14 @@ public class NodeAgentImplTest {
.currentRestartGeneration(restartGeneration.get())
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null, false);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(dockerOperations.pullImageAsyncIfNeeded(eq(dockerImage))).thenReturn(false);
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(dockerOperations, never()).removeContainer(eq(context), any());
verify(dockerOperations, never()).startServices(any());
@@ -195,7 +194,7 @@ public class NodeAgentImplTest {
final InOrder inOrder = inOrder(dockerOperations, orchestrator, nodeRepository, aclMaintainer, healthChecker);
inOrder.verify(dockerOperations, times(1)).pullImageAsyncIfNeeded(eq(dockerImage));
- inOrder.verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ inOrder.verify(dockerOperations, times(1)).createContainer(eq(context), any());
inOrder.verify(dockerOperations, times(1)).startContainer(eq(context));
inOrder.verify(aclMaintainer, times(1)).converge();
inOrder.verify(dockerOperations, times(1)).resumeNode(eq(context));
@@ -216,13 +215,14 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(dockerOperations.pullImageAsyncIfNeeded(any())).thenReturn(true);
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(orchestrator, never()).suspend(any(String.class));
verify(orchestrator, never()).resume(any(String.class));
@@ -241,29 +241,25 @@ public class NodeAgentImplTest {
.wantedVespaVersion(vespaVersion)
.vespaVersion(vespaVersion);
+ NodeAgentContext firstContext = createContext(specBuilder.build());
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
- NodeSpec firstSpec = specBuilder.build();
- NodeSpec secondSpec = specBuilder.minDiskAvailableGb(200).build();
- NodeSpec thirdSpec = specBuilder.minCpuCores(4).build();
-
- when(nodeRepository.getOptionalNode(hostName))
- .thenReturn(Optional.of(firstSpec))
- .thenReturn(Optional.of(secondSpec))
- .thenReturn(Optional.of(thirdSpec));
+
when(dockerOperations.pullImageAsyncIfNeeded(any())).thenReturn(true);
- when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
+ when(storageMaintainer.getDiskUsageFor(any())).thenReturn(Optional.of(201326592000L));
- nodeAgent.converge();
- nodeAgent.converge();
- nodeAgent.converge();
+ nodeAgent.doConverge(firstContext);
+ NodeAgentContext secondContext = createContext(specBuilder.minDiskAvailableGb(200).build());
+ nodeAgent.doConverge(secondContext);
+ NodeAgentContext thirdContext = createContext(specBuilder.minCpuCores(4).build());
+ nodeAgent.doConverge(thirdContext);
InOrder inOrder = inOrder(orchestrator, dockerOperations);
inOrder.verify(orchestrator).resume(any(String.class));
inOrder.verify(orchestrator).resume(any(String.class));
inOrder.verify(orchestrator).suspend(any(String.class));
- inOrder.verify(dockerOperations).removeContainer(eq(context), any());
- inOrder.verify(dockerOperations, times(1)).createContainer(eq(context), eq(thirdSpec), any());
- inOrder.verify(dockerOperations).startContainer(eq(context));
+ inOrder.verify(dockerOperations).removeContainer(eq(thirdContext), any());
+ inOrder.verify(dockerOperations, times(1)).createContainer(eq(thirdContext), any());
+ inOrder.verify(dockerOperations).startContainer(eq(thirdContext));
inOrder.verify(orchestrator).resume(any(String.class));
}
@@ -281,14 +277,16 @@ public class NodeAgentImplTest {
.currentRestartGeneration(currentRestartGeneration)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
+ doThrow(new OrchestratorException("Denied")).when(orchestrator).suspend(eq(hostName));
try {
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
fail("Expected to throw an exception");
- } catch (Exception ignored) { }
+ } catch (OrchestratorException ignored) { }
- verify(dockerOperations, never()).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, never()).createContainer(eq(context), any());
verify(dockerOperations, never()).startContainer(eq(context));
verify(orchestrator, never()).resume(any(String.class));
verify(nodeRepository, never()).updateNodeAttributes(any(String.class), any(NodeAttributes.class));
@@ -308,6 +306,7 @@ public class NodeAgentImplTest {
.currentRebootGeneration(currentRebootGeneration)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
@@ -317,22 +316,22 @@ public class NodeAgentImplTest {
.when(healthChecker).verifyHealth(eq(context));
try {
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
} catch (ConvergenceException ignored) {}
// First time we fail to resume because health verification fails
verify(orchestrator, times(1)).suspend(eq(hostName));
verify(dockerOperations, times(1)).removeContainer(eq(context), any());
- verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, times(1)).createContainer(eq(context), any());
verify(dockerOperations, times(1)).startContainer(eq(context));
verify(orchestrator, never()).resume(eq(hostName));
verify(nodeRepository, never()).updateNodeAttributes(any(), any());
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
// Do not reboot the container again
verify(dockerOperations, times(1)).removeContainer(eq(context), any());
- verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, times(1)).createContainer(eq(context), any());
verify(orchestrator, times(1)).resume(eq(hostName));
verify(nodeRepository, times(1)).updateNodeAttributes(eq(hostName), eq(new NodeAttributes()
.withRebootGeneration(wantedRebootGeneration)));
@@ -348,11 +347,12 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(dockerOperations, never()).removeContainer(eq(context), any());
verify(orchestrator, never()).resume(any(String.class));
@@ -365,18 +365,19 @@ public class NodeAgentImplTest {
.state(Node.State.ready)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null,false);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
- nodeAgent.converge();
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
+ nodeAgent.doConverge(context);
+ nodeAgent.doConverge(context);
// Should only be called once, when we initialize
verify(dockerOperations, times(1)).getContainer(eq(context));
verify(dockerOperations, never()).removeContainer(eq(context), any());
- verify(dockerOperations, never()).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, never()).createContainer(eq(context), any());
verify(dockerOperations, never()).startContainer(eq(context));
verify(orchestrator, never()).resume(any(String.class));
verify(nodeRepository, never()).updateNodeAttributes(eq(hostName), any());
@@ -392,11 +393,12 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
final InOrder inOrder = inOrder(storageMaintainer, dockerOperations);
inOrder.verify(dockerOperations, never()).removeContainer(eq(context), any());
@@ -413,11 +415,12 @@ public class NodeAgentImplTest {
.wantedVespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null, false);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(nodeRepository, never()).updateNodeAttributes(eq(hostName), any());
}
@@ -433,20 +436,21 @@ public class NodeAgentImplTest {
.state(nodeState)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
final InOrder inOrder = inOrder(storageMaintainer, dockerOperations, nodeRepository);
inOrder.verify(dockerOperations, times(1)).stopServices(eq(context));
- inOrder.verify(storageMaintainer, times(1)).handleCoreDumpsForContainer(eq(context), eq(node), any());
+ inOrder.verify(storageMaintainer, times(1)).handleCoreDumpsForContainer(eq(context), any());
inOrder.verify(dockerOperations, times(1)).removeContainer(eq(context), any());
inOrder.verify(storageMaintainer, times(1)).archiveNodeStorage(eq(context));
inOrder.verify(nodeRepository, times(1)).setNodeState(eq(hostName), eq(Node.State.ready));
- verify(dockerOperations, never()).createContainer(eq(context), any(), any());
+ verify(dockerOperations, never()).createContainer(eq(context), any());
verify(dockerOperations, never()).startContainer(eq(context));
verify(dockerOperations, never()).suspendNode(eq(context));
verify(dockerOperations, times(1)).stopServices(eq(context));
@@ -474,10 +478,11 @@ public class NodeAgentImplTest {
.state(Node.State.provisioned)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null, false);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(nodeRepository, times(1)).setNodeState(eq(hostName), eq(Node.State.dirty));
}
@@ -490,15 +495,16 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, false);
when(nodeRepository.getOptionalNode(eq(hostName))).thenReturn(Optional.of(node));
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
- nodeAgent.tick();
+ nodeAgent.doConverge(context);
verify(dockerOperations, times(1)).removeContainer(eq(context), any());
- verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, times(1)).createContainer(eq(context), any());
verify(dockerOperations, times(1)).startContainer(eq(context));
}
@@ -511,6 +517,7 @@ public class NodeAgentImplTest {
.vespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(eq(hostName))).thenReturn(Optional.of(node));
@@ -523,7 +530,7 @@ public class NodeAgentImplTest {
// 1st try
try {
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
fail("Expected to throw an exception");
} catch (RuntimeException ignored) { }
@@ -531,7 +538,7 @@ public class NodeAgentImplTest {
inOrder.verifyNoMoreInteractions();
// 2nd try
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
inOrder.verify(dockerOperations).resumeNode(any());
inOrder.verify(orchestrator).resume(hostName);
@@ -539,33 +546,6 @@ public class NodeAgentImplTest {
}
@Test
- public void testSetFrozen() {
- NodeAgentImpl nodeAgent = spy(makeNodeAgent(dockerImage, true));
- doNothing().when(nodeAgent).converge();
-
- nodeAgent.tick();
- verify(nodeAgent, times(1)).converge();
-
- assertFalse(nodeAgent.setFrozen(true)); // Returns true because we are not frozen until tick is called
- nodeAgent.tick();
- verify(nodeAgent, times(1)).converge(); // Frozen should be set, therefore converge is never called
-
- assertTrue(nodeAgent.setFrozen(true)); // Attempt to set frozen again, but it's already set
- clock.advance(Duration.ofSeconds(35)); // workToDoNow is no longer set, so we need to wait the regular time
- nodeAgent.tick();
- verify(nodeAgent, times(1)).converge();
-
- assertFalse(nodeAgent.setFrozen(false)); // Unfreeze, but still need to call tick for it to take effect
- nodeAgent.tick();
- verify(nodeAgent, times(2)).converge();
-
- assertTrue(nodeAgent.setFrozen(false));
- clock.advance(Duration.ofSeconds(35)); // workToDoNow is no longer set, so we need to wait the regular time
- nodeAgent.tick();
- verify(nodeAgent, times(3)).converge();
- }
-
- @Test
public void start_container_subtask_failure_leads_to_container_restart() {
final NodeSpec node = nodeBuilder
.wantedDockerImage(dockerImage)
@@ -573,30 +553,30 @@ public class NodeAgentImplTest {
.wantedVespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = spy(makeNodeAgent(null, false));
- when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(dockerOperations.pullImageAsyncIfNeeded(eq(dockerImage))).thenReturn(false);
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
doThrow(new DockerException("Failed to set up network")).doNothing().when(dockerOperations).startContainer(eq(context));
try {
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
fail("Expected to get DockerException");
} catch (DockerException ignored) { }
verify(dockerOperations, never()).removeContainer(eq(context), any());
- verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, times(1)).createContainer(eq(context), any());
verify(dockerOperations, times(1)).startContainer(eq(context));
verify(nodeAgent, never()).resumeNodeIfNeeded(any());
// The docker container was actually started and is running, but subsequent exec calls to set up
// networking failed
mockGetContainer(dockerImage, true);
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(dockerOperations, times(1)).removeContainer(eq(context), any());
- verify(dockerOperations, times(2)).createContainer(eq(context), eq(node), any());
+ verify(dockerOperations, times(2)).createContainer(eq(context), any());
verify(dockerOperations, times(2)).startContainer(eq(context));
verify(nodeAgent, times(1)).resumeNodeIfNeeded(any());
}
@@ -631,6 +611,7 @@ public class NodeAgentImplTest {
.parentHostname("parent.host.name.yahoo.com")
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true);
when(nodeRepository.getOptionalNode(eq(hostName))).thenReturn(Optional.of(node));
@@ -638,12 +619,9 @@ public class NodeAgentImplTest {
when(dockerOperations.getContainerStats(eq(context)))
.thenReturn(Optional.of(stats1))
.thenReturn(Optional.of(stats2));
-
- nodeAgent.converge(); // Run the converge loop once to initialize lastNode
+
nodeAgent.updateContainerNodeMetrics(); // Update metrics once to init and lastCpuMetric
- clock.advance(Duration.ofSeconds(1234));
-
Path pathToExpectedMetrics = Paths.get(classLoader.getResource("expected.container.system.metrics.txt").getPath());
String expectedMetrics = new String(Files.readAllBytes(pathToExpectedMetrics))
.replaceAll("\\s", "")
@@ -674,13 +652,11 @@ public class NodeAgentImplTest {
.state(Node.State.ready)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null, false);
- when(nodeRepository.getOptionalNode(eq(hostName))).thenReturn(Optional.of(node));
when(dockerOperations.getContainerStats(eq(context))).thenReturn(Optional.empty());
- nodeAgent.converge(); // Run the converge loop once to initialize lastNode
-
nodeAgent.updateContainerNodeMetrics();
Set<Map<String, Object>> actualMetrics = metricReceiver.getAllMetricsRaw();
@@ -696,20 +672,21 @@ public class NodeAgentImplTest {
.wantedVespaVersion(vespaVersion)
.build();
+ NodeAgentContext context = createContext(node);
NodeAgentImpl nodeAgent = makeNodeAgent(null, false);
when(nodeRepository.getOptionalNode(hostName)).thenReturn(Optional.of(node));
when(dockerOperations.pullImageAsyncIfNeeded(eq(dockerImage))).thenReturn(false);
when(storageMaintainer.getDiskUsageFor(eq(context))).thenReturn(Optional.of(201326592000L));
- nodeAgent.converge();
+ nodeAgent.doConverge(context);
verify(dockerOperations, never()).removeContainer(eq(context), any());
verify(orchestrator, never()).suspend(any(String.class));
final InOrder inOrder = inOrder(dockerOperations, orchestrator, nodeRepository, aclMaintainer);
inOrder.verify(dockerOperations, times(1)).pullImageAsyncIfNeeded(eq(dockerImage));
- inOrder.verify(dockerOperations, times(1)).createContainer(eq(context), eq(node), any());
+ inOrder.verify(dockerOperations, times(1)).createContainer(eq(context), any());
inOrder.verify(dockerOperations, times(1)).startContainer(eq(context));
inOrder.verify(aclMaintainer, times(1)).converge();
inOrder.verify(dockerOperations, times(1)).resumeNode(eq(context));
@@ -722,24 +699,33 @@ public class NodeAgentImplTest {
mockGetContainer(dockerImage, isRunning);
when(dockerOperations.getContainerStats(any())).thenReturn(Optional.of(emptyContainerStats));
- doNothing().when(storageMaintainer).writeMetricsConfig(any(), any());
+ doNothing().when(storageMaintainer).writeMetricsConfig(any());
- return new NodeAgentImpl(context, nodeRepository, orchestrator, dockerOperations,
- storageMaintainer, clock, NODE_AGENT_SCAN_INTERVAL, Optional.of(athenzCredentialsMaintainer), Optional.of(aclMaintainer),
+ return new NodeAgentImpl(contextSupplier, nodeRepository, orchestrator, dockerOperations,
+ storageMaintainer, Optional.of(athenzCredentialsMaintainer), Optional.of(aclMaintainer),
Optional.of(healthChecker));
}
private void mockGetContainer(DockerImage dockerImage, boolean isRunning) {
- Optional<Container> container = dockerImage != null ?
- Optional.of(new Container(
- hostName,
- dockerImage,
- ContainerResources.from(MIN_CPU_CORES, MIN_MAIN_MEMORY_AVAILABLE_GB),
- context.containerName(),
- isRunning ? Container.State.RUNNING : Container.State.EXITED,
- isRunning ? 1 : 0)) :
- Optional.empty();
-
- when(dockerOperations.getContainer(eq(context))).thenReturn(container);
+ doAnswer(invoc -> {
+ NodeAgentContext context = invoc.getArgument(0);
+ if (!hostName.equals(context.hostname().value()))
+ throw new IllegalArgumentException();
+ return dockerImage != null ?
+ Optional.of(new Container(
+ hostName,
+ dockerImage,
+ ContainerResources.from(MIN_CPU_CORES, MIN_MAIN_MEMORY_AVAILABLE_GB),
+ ContainerName.fromHostname(hostName),
+ isRunning ? Container.State.RUNNING : Container.State.EXITED,
+ isRunning ? 1 : 0)) :
+ Optional.empty();
+ }).when(dockerOperations).getContainer(any());
+ }
+
+ private NodeAgentContext createContext(NodeSpec nodeSpec) {
+ NodeAgentContext context = new NodeAgentContextImpl.Builder(nodeSpec).build();
+ when(contextSupplier.currentContext()).thenReturn(context);
+ return context;
}
}
diff --git a/node-repository/src/main/config/node-repository.xml b/node-repository/src/main/config/node-repository.xml
index 9276ce0e7c9..22ab615bfad 100644
--- a/node-repository/src/main/config/node-repository.xml
+++ b/node-repository/src/main/config/node-repository.xml
@@ -11,5 +11,10 @@
<binding>https://*/nodes/v2/*</binding>
</handler>
+<handler id="com.yahoo.vespa.hosted.provision.restapi.v2.LoadBalancersApiHandler" bundle="node-repository">
+ <binding>http://*/loadbalancers/v1/*</binding>
+ <binding>https://*/loadbalancers/v1/*</binding>
+</handler>
+
<preprocess:include file="node-flavors.xml" required="false" />
<preprocess:include file="node-repository-config.xml" required="false" />
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
index 7e518ee1728..442013b8a6a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
@@ -1,7 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterMembership;
@@ -77,7 +77,7 @@ public final class Node {
Objects.requireNonNull(history, "A null node history is not permitted");
Objects.requireNonNull(type, "A null node type is not permitted");
- this.ipAddresses = ImmutableSortedSet.copyOf(IP.naturalOrder, ipAddresses);
+ this.ipAddresses = ImmutableSet.copyOf(ipAddresses);
this.ipAddressPool = new IP.AddressPool(this, ipAddressPool);
this.hostname = hostname;
this.parentHostname = parentHostname;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java
index effc5b1a41d..4ac3a839ae1 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.lb;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.yahoo.config.provision.HostName;
+import com.yahoo.vespa.hosted.provision.maintenance.LoadBalancerExpirer;
import java.util.List;
import java.util.Objects;
@@ -50,8 +51,8 @@ public class LoadBalancer {
}
/**
- * Returns whether this load balancer is inactive. Inactive load balancers cannot be reactivated, and are
- * eventually deleted
+ * Returns whether this load balancer is inactive. Inactive load balancers cannot be re-activated, and are
+ * eventually removed by {@link LoadBalancerExpirer}.
*/
public boolean inactive() {
return inactive;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
index b589e5aed2f..a5a0d8cb2f8 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
@@ -17,6 +17,10 @@ public class LoadBalancerServiceMock implements LoadBalancerService {
private final Map<LoadBalancerId, LoadBalancer> loadBalancers = new HashMap<>();
+ public Map<LoadBalancerId, LoadBalancer> loadBalancers() {
+ return Collections.unmodifiableMap(loadBalancers);
+ }
+
@Override
public Protocol protocol() {
return Protocol.ipv4;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
index 9d87a835960..68d597fb839 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java
@@ -108,9 +108,12 @@ public class InfrastructureProvisioner extends Maintainer {
}
private void removeApplication(ApplicationId applicationId) {
- NestedTransaction nestedTransaction = new NestedTransaction();
- provisioner.remove(nestedTransaction, applicationId);
- nestedTransaction.commit();
- duperModel.infraApplicationRemoved(applicationId);
+ // Use the DuperModel as source-of-truth on whether it has also been activated (to avoid periodic removals)
+ if (duperModel.infraApplicationIsActive(applicationId)) {
+ NestedTransaction nestedTransaction = new NestedTransaction();
+ provisioner.remove(nestedTransaction, applicationId);
+ nestedTransaction.commit();
+ duperModel.infraApplicationRemoved(applicationId);
+ }
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
new file mode 100644
index 00000000000..4b66dff3032
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
@@ -0,0 +1,83 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.maintenance;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.curator.Lock;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService;
+import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Periodically remove inactive load balancers permanently.
+ *
+ * When an application is removed, any associated load balancers are only deactivated. This maintainer ensures that
+ * such resources are eventually freed.
+ *
+ * @author mpolden
+ */
+public class LoadBalancerExpirer extends Maintainer {
+
+ private final LoadBalancerService service;
+ private final CuratorDatabaseClient db;
+
+ public LoadBalancerExpirer(NodeRepository nodeRepository, Duration interval, JobControl jobControl,
+ LoadBalancerService service) {
+ super(nodeRepository, interval, jobControl);
+ this.service = Objects.requireNonNull(service, "service must be non-null");
+ this.db = nodeRepository.database();
+ }
+
+ @Override
+ protected void maintain() {
+ removeInactive();
+ }
+
+ private void removeInactive() {
+ List<LoadBalancerId> failed = new ArrayList<>();
+ Exception lastException = null;
+ try (Lock lock = db.lockLoadBalancers()) {
+ for (LoadBalancerId loadBalancer : inactiveLoadBlancers()) {
+ if (hasNodes(loadBalancer.application())) { // Defer removal if there are still nodes allocated to application
+ continue;
+ }
+ try {
+ service.remove(loadBalancer);
+ db.removeLoadBalancer(loadBalancer);
+ } catch (Exception e) {
+ failed.add(loadBalancer);
+ lastException = e;
+ }
+ }
+ }
+ if (!failed.isEmpty()) {
+ log.log(LogLevel.WARNING, String.format("Failed to remove %d load balancers: %s, retrying in %s",
+ failed.size(),
+ failed.stream()
+ .map(LoadBalancerId::serializedForm)
+ .collect(Collectors.joining(", ")),
+ interval()),
+ lastException);
+ }
+ }
+
+ private boolean hasNodes(ApplicationId application) {
+ return !nodeRepository().getNodes(application).isEmpty();
+ }
+
+ private List<LoadBalancerId> inactiveLoadBlancers() {
+ return db.readLoadBalancers().entrySet().stream()
+ .filter(entry -> entry.getValue().inactive())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Maintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Maintainer.java
index f5576ae00fc..49ede9962eb 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Maintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Maintainer.java
@@ -56,8 +56,7 @@ public abstract class Maintainer extends AbstractComponent implements Runnable {
try {
if (jobControl.isActive(name()))
maintain();
- }
- catch (RuntimeException e) {
+ } catch (Throwable e) {
log.log(Level.WARNING, this + " failed. Will retry in " + interval.toMinutes() + " minutes", e);
}
}
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 946c43ca8fc..2bc60de3c8d 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
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService;
import com.yahoo.vespa.hosted.provision.maintenance.retire.RetireIPv4OnlyNodes;
import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicy;
import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicyList;
@@ -50,6 +51,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final NodeRetirer nodeRetirer;
private final MetricsReporter metricsReporter;
private final InfrastructureProvisioner infrastructureProvisioner;
+ private final LoadBalancerExpirer loadBalancerExpirer;
private final JobControl jobControl;
private final InfrastructureVersions infrastructureVersions;
@@ -59,15 +61,17 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor,
Zone zone, Orchestrator orchestrator, Metric metric,
ConfigserverConfig configserverConfig,
- DuperModelInfraApi duperModelInfraApi) {
+ DuperModelInfraApi duperModelInfraApi,
+ LoadBalancerService loadBalancerService) {
this(nodeRepository, deployer, provisioner, hostLivenessTracker, serviceMonitor, zone, Clock.systemUTC(),
- orchestrator, metric, configserverConfig, duperModelInfraApi);
+ orchestrator, metric, configserverConfig, duperModelInfraApi, loadBalancerService);
}
public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, Provisioner provisioner,
HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor,
Zone zone, Clock clock, Orchestrator orchestrator, Metric metric,
- ConfigserverConfig configserverConfig, DuperModelInfraApi duperModelInfraApi) {
+ ConfigserverConfig configserverConfig, DuperModelInfraApi duperModelInfraApi,
+ LoadBalancerService loadBalancerService) {
DefaultTimes defaults = new DefaultTimes(zone);
jobControl = new JobControl(nodeRepository.database());
infrastructureVersions = new InfrastructureVersions(nodeRepository.database());
@@ -84,6 +88,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
nodeRebooter = new NodeRebooter(nodeRepository, clock, durationFromEnv("reboot_interval").orElse(defaults.rebootInterval), jobControl);
metricsReporter = new MetricsReporter(nodeRepository, metric, orchestrator, serviceMonitor, periodicApplicationMaintainer::pendingDeployments, durationFromEnv("metrics_interval").orElse(defaults.metricsInterval), jobControl);
infrastructureProvisioner = new InfrastructureProvisioner(provisioner, nodeRepository, infrastructureVersions, durationFromEnv("infrastructure_provision_interval").orElse(defaults.infrastructureProvisionInterval), jobControl, duperModelInfraApi);
+ loadBalancerExpirer = new LoadBalancerExpirer(nodeRepository, durationFromEnv("load_balancer_expiry").orElse(defaults.loadBalancerExpiry), jobControl, loadBalancerService);
// The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now
infrastructureProvisioner.maintain();
@@ -109,6 +114,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
provisionedExpirer.deconstruct();
metricsReporter.deconstruct();
infrastructureProvisioner.deconstruct();
+ loadBalancerExpirer.deconstruct();
}
public JobControl jobControl() { return jobControl; }
@@ -156,6 +162,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final Duration metricsInterval;
private final Duration retiredInterval;
private final Duration infrastructureProvisionInterval;
+ private final Duration loadBalancerExpiry;
private final NodeFailer.ThrottlePolicy throttlePolicy;
@@ -171,6 +178,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
metricsInterval = Duration.ofMinutes(1);
infrastructureProvisionInterval = Duration.ofMinutes(3);
throttlePolicy = NodeFailer.ThrottlePolicy.hosted;
+ loadBalancerExpiry = Duration.ofHours(1);
if (zone.environment().equals(Environment.prod) && zone.system() != SystemName.cd) {
inactiveExpiry = Duration.ofHours(4); // enough time for the application owner to discover and redeploy
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
index 074a20fc82d..fbc358893e1 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
@@ -1,7 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.node;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.UnsignedBytes;
import com.yahoo.vespa.hosted.provision.Node;
@@ -59,7 +59,7 @@ public class IP {
public AddressPool(Node owner, Set<String> addresses) {
this.owner = Objects.requireNonNull(owner, "owner must be non-null");
- this.addresses = ImmutableSortedSet.copyOf(naturalOrder, requireAddresses(addresses));
+ this.addresses = ImmutableSet.copyOf(requireAddresses(addresses));
}
/**
@@ -200,9 +200,9 @@ public class IP {
/** All IP addresses in this */
public Set<String> addresses() {
- ImmutableSortedSet.Builder<String> builder = ImmutableSortedSet.orderedBy(naturalOrder);
- builder.add(ipv6Address);
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
ipv4Address.ifPresent(builder::add);
+ builder.add(ipv6Address);
return builder.build();
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabase.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabase.java
index 2ceffc54dd5..2715b1131b3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabase.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabase.java
@@ -36,7 +36,7 @@ public class CuratorDatabase {
private final CuratorCounter changeGenerationCounter;
/** A partial cache of the Curator database, which is only valid if generations match */
- private final AtomicReference<CuratorDatabaseCache> cache = new AtomicReference<>();
+ private final AtomicReference<Cache> cache = new AtomicReference<>();
/** Whether we should return data from the cache or always read fro ZooKeeper */
private final boolean useCache;
@@ -110,12 +110,12 @@ public class CuratorDatabase {
// the data to read is protected by a lock which is held now, and during any writes of the data.
/** Returns the immediate, local names of the children under this node in any order */
- List<String> getChildren(Path path) { return getCache().getChildren(path); }
+ List<String> getChildren(Path path) { return getSession().getChildren(path); }
- Optional<byte[]> getData(Path path) { return getCache().getData(path); }
+ Optional<byte[]> getData(Path path) { return getSession().getData(path); }
/** Invalidates the current cache if outdated. */
- private CuratorDatabaseCache getCache() {
+ Session getSession() {
if (changeGenerationCounter.get() != cache.get().generation)
synchronized (cacheCreationLock) {
while (changeGenerationCounter.get() != cache.get().generation)
@@ -126,8 +126,8 @@ public class CuratorDatabase {
}
/** Caches must only be instantiated using this method */
- private CuratorDatabaseCache newCache(long generation) {
- return useCache ? new CuratorDatabaseCache(generation, curator) : new DeactivatedCache(generation, curator);
+ private Cache newCache(long generation) {
+ return useCache ? new Cache(generation, curator) : new NoCache(generation, curator);
}
/**
@@ -135,10 +135,10 @@ public class CuratorDatabase {
* This is merely a recording of what Curator returned at various points in time when
* it had the counter at this generation.
*/
- private static class CuratorDatabaseCache {
+ private static class Cache implements Session {
private final long generation;
-
+
/** The curator instance used to fetch missing data */
protected final Curator curator;
@@ -149,23 +149,17 @@ public class CuratorDatabase {
private final Map<Path, Optional<byte[]>> data = new ConcurrentHashMap<>();
/** Create an empty snapshot at a given generation (as an empty snapshot is a valid partial snapshot) */
- private CuratorDatabaseCache(long generation, Curator curator) {
+ private Cache(long generation, Curator curator) {
this.generation = generation;
this.curator = curator;
}
- public long generation() { return generation; }
-
- /**
- * Returns the children of this path, which may be empty.
- */
+ @Override
public List<String> getChildren(Path path) {
return children.computeIfAbsent(path, key -> ImmutableList.copyOf(curator.getChildren(path)));
}
- /**
- * Returns the a copy of the content of this child - which may be empty.
- */
+ @Override
public Optional<byte[]> getData(Path path) {
return data.computeIfAbsent(path, key -> curator.getData(path)).map(data -> Arrays.copyOf(data, data.length));
}
@@ -173,9 +167,9 @@ public class CuratorDatabase {
}
/** An implementation of the curator database cache which does no caching */
- private static class DeactivatedCache extends CuratorDatabaseCache {
-
- private DeactivatedCache(long generation, Curator curator) { super(generation, curator); }
+ private static class NoCache extends Cache {
+
+ private NoCache(long generation, Curator curator) { super(generation, curator); }
@Override
public List<String> getChildren(Path path) { return curator.getChildren(path); }
@@ -185,4 +179,17 @@ public class CuratorDatabase {
}
+ interface Session {
+
+ /**
+ * Returns the children of this path, which may be empty.
+ */
+ List<String> getChildren(Path path);
+
+ /**
+ * Returns the a copy of the content of this child - which may be empty.
+ */
+ Optional<byte[]> getData(Path path);
+
+ }
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
index c4031f3ccba..da4d2a0afb2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
@@ -251,9 +251,10 @@ public class CuratorDatabaseClient {
List<Node> nodes = new ArrayList<>();
if (states.length == 0)
states = Node.State.values();
+ CuratorDatabase.Session session = curatorDatabase.getSession();
for (Node.State state : states) {
- for (String hostname : curatorDatabase.getChildren(toPath(state))) {
- Optional<Node> node = getNode(hostname, state);
+ for (String hostname : session.getChildren(toPath(state))) {
+ Optional<Node> node = getNode(session, hostname, state);
node.ifPresent(nodes::add); // node might disappear between getChildren and getNode
}
}
@@ -270,21 +271,29 @@ public class CuratorDatabaseClient {
return nodes;
}
- /**
+ /**
* Returns a particular node, or empty if this noe is not in any of the given states.
* If no states are given this returns the node if it is present in any state.
*/
- public Optional<Node> getNode(String hostname, Node.State ... states) {
+ public Optional<Node> getNode(CuratorDatabase.Session session, String hostname, Node.State ... states) {
if (states.length == 0)
states = Node.State.values();
for (Node.State state : states) {
- Optional<byte[]> nodeData = curatorDatabase.getData(toPath(state, hostname));
+ Optional<byte[]> nodeData = session.getData(toPath(state, hostname));
if (nodeData.isPresent())
return nodeData.map((data) -> nodeSerializer.fromJson(state, data));
}
return Optional.empty();
}
+ /**
+ * Returns a particular node, or empty if this noe is not in any of the given states.
+ * If no states are given this returns the node if it is present in any state.
+ */
+ public Optional<Node> getNode(String hostname, Node.State ... states) {
+ return getNode(curatorDatabase.getSession(), hostname, states);
+ }
+
private Path toPath(Node.State nodeState) { return root.append(toDir(nodeState)); }
private Path toPath(Node node) {
@@ -449,10 +458,10 @@ public class CuratorDatabaseClient {
});
}
- public void removeLoadBalancer(LoadBalancer loadBalancer) {
+ public void removeLoadBalancer(LoadBalancerId loadBalancer) {
NestedTransaction transaction = new NestedTransaction();
CuratorTransaction curatorTransaction = curatorDatabase.newCuratorTransactionIn(transaction);
- curatorTransaction.add(CuratorOperations.delete(loadBalancerPath(loadBalancer.id()).getAbsolute()));
+ curatorTransaction.add(CuratorOperations.delete(loadBalancerPath(loadBalancer).getAbsolute()));
transaction.commit();
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
index 1c640a6f074..f96ecc0431a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
@@ -21,6 +21,7 @@ import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.Generation;
import com.yahoo.vespa.hosted.provision.node.History;
+import com.yahoo.vespa.hosted.provision.node.IP;
import com.yahoo.vespa.hosted.provision.node.Status;
import java.io.IOException;
@@ -141,7 +142,7 @@ public class NodeSerializer {
}
private void toSlime(Set<String> ipAddresses, Cursor array) {
- ipAddresses.forEach(array::addString);
+ ipAddresses.stream().sorted(IP.naturalOrder).forEach(array::addString);
}
// ---------------- Deserialization --------------------------------------------------
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java
index 483f19ed5b0..f3d8f42f3b7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java
@@ -9,7 +9,7 @@ import com.yahoo.vespa.hosted.provision.maintenance.JobControl;
import java.io.IOException;
import java.io.OutputStream;
-import java.net.URI;
+import java.util.TreeSet;
/** A response containing maintenance job status */
public class JobsResponse extends HttpResponse {
@@ -25,13 +25,12 @@ public class JobsResponse extends HttpResponse {
public void render(OutputStream stream) throws IOException {
Slime slime = new Slime();
Cursor root = slime.setObject();
-
Cursor jobArray = root.setArray("jobs");
- for (String jobName : jobControl.jobs())
+ for (String jobName : new TreeSet<>(jobControl.jobs()))
jobArray.addObject().setString("name", jobName);
Cursor inactiveArray = root.setArray("inactive");
- for (String jobName : jobControl.inactiveJobs())
+ for (String jobName : new TreeSet<>(jobControl.inactiveJobs()))
inactiveArray.addString(jobName);
new JsonFormat(true).encode(stream, slime);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java
new file mode 100644
index 00000000000..6ffac2c0fbc
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java
@@ -0,0 +1,52 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.restapi.v2;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.vespa.hosted.provision.NoSuchNodeException;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.yolean.Exceptions;
+
+import javax.inject.Inject;
+import java.util.logging.Level;
+
+/**
+ * @author mpolden
+ */
+public class LoadBalancersApiHandler extends LoggingRequestHandler {
+
+ private final NodeRepository nodeRepository;
+
+ @Inject
+ public LoadBalancersApiHandler(LoggingRequestHandler.Context parentCtx, NodeRepository nodeRepository) {
+ super(parentCtx);
+ this.nodeRepository = nodeRepository;
+ }
+
+ @Override
+ public HttpResponse handle(HttpRequest request) {
+ try {
+ switch (request.getMethod()) {
+ case GET: return handleGET(request);
+ default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
+ }
+ }
+ catch (NotFoundException | NoSuchNodeException e) {
+ return ErrorResponse.notFoundError(Exceptions.toMessageString(e));
+ }
+ catch (IllegalArgumentException e) {
+ return ErrorResponse.badRequest(Exceptions.toMessageString(e));
+ }
+ catch (RuntimeException e) {
+ log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e);
+ return ErrorResponse.internalServerError(Exceptions.toMessageString(e));
+ }
+ }
+
+ private HttpResponse handleGET(HttpRequest request) {
+ String path = request.getUri().getPath();
+ if (path.equals("/loadbalancers/v1/")) return new LoadBalancersResponse(request, nodeRepository);
+ throw new NotFoundException("Nothing at path '" + path + "'");
+ }
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java
new file mode 100644
index 00000000000..04a1cdaeeda
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java
@@ -0,0 +1,78 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.restapi.v2;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
+import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mpolden
+ */
+public class LoadBalancersResponse extends HttpResponse {
+
+ private final NodeRepository nodeRepository;
+ private final HttpRequest request;
+
+ public LoadBalancersResponse(HttpRequest request, NodeRepository nodeRepository) {
+ super(200);
+ this.request = request;
+ this.nodeRepository = nodeRepository;
+ }
+
+ private Optional<ApplicationId> application() {
+ return Optional.ofNullable(request.getProperty("application")).map(ApplicationFilter::toApplicationId);
+ }
+
+ private List<LoadBalancer> loadBalancers() {
+ return application().map(nodeRepository.database()::readLoadBalancers)
+ .orElseGet(() -> new ArrayList<>(nodeRepository.database().readLoadBalancers().values()));
+ }
+
+ @Override
+ public String getContentType() { return "application/json"; }
+
+ @Override
+ public void render(OutputStream stream) throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ Cursor loadBalancerArray = root.setArray("loadBalancers");
+
+ loadBalancers().forEach(lb -> {
+ Cursor lbObject = loadBalancerArray.addObject();
+ lbObject.setString("id", lb.id().serializedForm());
+ lbObject.setString("application", lb.id().application().application().value());
+ lbObject.setString("tenant", lb.id().application().tenant().value());
+ lbObject.setString("instance", lb.id().application().instance().value());
+ lbObject.setString("cluster", lb.id().cluster().value());
+ lbObject.setString("hostname", lb.hostname().value());
+
+ Cursor portArray = lbObject.setArray("ports");
+ lb.ports().forEach(portArray::addLong);
+
+ Cursor realArray = lbObject.setArray("reals");
+ lb.reals().forEach(real -> {
+ Cursor realObject = realArray.addObject();
+ realObject.setString("hostname", real.hostname().value());
+ realObject.setString("ipAddress", real.ipAddress());
+ realObject.setLong("port", real.port());
+ });
+
+ lbObject.setBool("inactive", lb.inactive());
+ });
+
+ new JsonFormat(true).encode(stream, slime);
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
index 733f5df7858..d2ab3c20080 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
@@ -6,10 +6,10 @@ import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.NodeType;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.History;
@@ -81,7 +81,7 @@ class NodesResponse extends HttpResponse {
@Override
public void render(OutputStream stream) throws IOException {
- stream.write(toJson());
+ new JsonFormat(true).encode(stream, slime);
}
@Override
@@ -89,10 +89,6 @@ class NodesResponse extends HttpResponse {
return "application/json";
}
- private byte[] toJson() throws IOException {
- return SlimeUtils.toJsonBytes(slime);
- }
-
private void statesToSlime(Cursor root) {
Cursor states = root.setObject("states");
for (Node.State state : Node.State.values())
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java
index 95f69dc1c2a..6225a8a4fc4 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java
@@ -24,32 +24,52 @@ import java.util.stream.Collectors;
/**
* Authorizer for config server REST APIs. This contains the rules for all API paths where the authorization process
- * requires information from the node-repository to make a decision
+ * may require information from the node-repository to make a decision
*
* @author mpolden
* @author bjorncs
*/
public class Authorizer implements BiPredicate<NodePrincipal, URI> {
-
private final NodeRepository nodeRepository;
private final Set<String> whitelistedHostnames;
+ private final AthenzIdentity controllerIdentity;
+ private final AthenzIdentity configServerIdentity = new AthenzService("vespa.vespa", "configserver");
+ private final AthenzIdentity proxyIdentity = new AthenzService("vespa.vespa", "proxy");
+ private final AthenzIdentity tenantIdentity = new AthenzService("vespa.vespa", "tenant-host");
private final Set<AthenzIdentity> trustedIdentities;
+ private final Set<AthenzIdentity> hostAdminIdentities;
// TODO Remove whitelisted hostnames as these nodes should be included through 'trustedIdentities'
public Authorizer(SystemName system, NodeRepository nodeRepository, Set<String> whitelistedHostnames) {
this.nodeRepository = nodeRepository;
this.whitelistedHostnames = whitelistedHostnames;
- this.trustedIdentities = getTrustedIdentities(system);
+ controllerIdentity = system == SystemName.main
+ ? new AthenzService("vespa.vespa", "hosting")
+ : new AthenzService("vespa.vespa.cd", "hosting");
+ this.trustedIdentities = new HashSet<>(Arrays.asList(controllerIdentity, configServerIdentity));
+ this.hostAdminIdentities = new HashSet<>(Arrays.asList(controllerIdentity, configServerIdentity, proxyIdentity, tenantIdentity));
}
/** Returns whether principal is authorized to access given URI */
@Override
public boolean test(NodePrincipal principal, URI uri) {
- // Trusted services can access everything
- if (principal.getAthenzIdentityName().isPresent()
- && trustedIdentities.contains(principal.getAthenzIdentityName().get())) {
- return true;
+ if (principal.getAthenzIdentityName().isPresent()) {
+ // All host admins can retrieve flags data
+ if (uri.getPath().equals("/flags/v1/data") || uri.getPath().equals("/flags/v1/data/")) {
+ return hostAdminIdentities.contains(principal.getAthenzIdentityName().get());
+ }
+
+ // Only controller can access everything else in flags
+ if (uri.getPath().startsWith("/flags/v1/")) {
+ return principal.getAthenzIdentityName().get().equals(controllerIdentity);
+ }
+
+ // Trusted services can access everything
+ if (trustedIdentities.contains(principal.getAthenzIdentityName().get())) {
+ return true;
+ }
}
+
if (principal.getHostname().isPresent()) {
String hostname = principal.getHostname().get();
if (isAthenzProviderApi(uri)) {
@@ -108,18 +128,6 @@ public class Authorizer implements BiPredicate<NodePrincipal, URI> {
return !resources.isEmpty() && resources.stream().anyMatch(resource -> predicate.test(resource, principal));
}
-
- private static Set<AthenzIdentity> getTrustedIdentities(SystemName system) {
- Set<AthenzIdentity> trustedIdentities = new HashSet<>();
- trustedIdentities.add(new AthenzService("vespa.vespa", "configserver"));
- AthenzService controllerIdentity =
- system == SystemName.main
- ? new AthenzService("vespa.vespa", "hosting")
- : new AthenzService("vespa.vespa.cd", "hosting");
- trustedIdentities.add(controllerIdentity);
- return trustedIdentities;
- }
-
private Optional<Node> getNode(String hostname) {
// Ignore potential path traversal. Node repository happily passes arguments unsanitized all the way down to
// curator...
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
index 110d0ca94d0..bc8772af952 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
@@ -9,31 +9,34 @@ package com.yahoo.vespa.hosted.provision.testutils;
*/
public class ContainerConfig {
- public static String servicesXmlV2(int port) {
- return "<jdisc version='1.0'>\n" +
- " <config name=\"container.handler.threadpool\">\n" +
- " <maxthreads>10</maxthreads>\n" +
- " </config> \n" +
- " <component id='com.yahoo.test.ManualClock'/>\n" +
- " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisioner'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance'/>\n" +
- " <component id='com.yahoo.config.provision.Zone'/>\n" +
- " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler'>\n" +
- " <binding>http://*/nodes/v2/*</binding>\n" +
- " </handler>\n" +
- " <http>\n" +
- " <server id='myServer' port='" + port + "'/>\n" +
- " </http>\n" +
- "</jdisc>";
- }
+ public static String servicesXmlV2(int port) {
+ return "<jdisc version='1.0'>\n" +
+ " <config name=\"container.handler.threadpool\">\n" +
+ " <maxthreads>10</maxthreads>\n" +
+ " </config> \n" +
+ " <component id='com.yahoo.test.ManualClock'/>\n" +
+ " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisioner'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance'/>\n" +
+ " <component id='com.yahoo.config.provision.Zone'/>\n" +
+ " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler'>\n" +
+ " <binding>http://*/nodes/v2/*</binding>\n" +
+ " </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.LoadBalancersApiHandler'>\n" +
+ " <binding>http://*/loadbalancers/v1/*</binding>\n" +
+ " </handler>\n" +
+ " <http>\n" +
+ " <server id='myServer' port='" + port + "'/>\n" +
+ " </http>\n" +
+ "</jdisc>";
+ }
}
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 1b8ae58a97d..183255db06b 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
@@ -18,6 +18,7 @@ import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.flag.FlagId;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Status;
@@ -111,6 +112,8 @@ public class MockNodeRepository extends NodeRepository {
dirtyRecursively("host55.yahoo.com", Agent.system, getClass().getSimpleName());
ApplicationId zoneApp = ApplicationId.from(TenantName.from("zoneapp"), ApplicationName.from("zoneapp"), InstanceName.from("zoneapp"));
+ // TODO: Remove this once feature flag is removed
+ this.flags().setEnabled(FlagId.exclusiveLoadBalancer, zoneApp, true);
ClusterSpec zoneCluster = ClusterSpec.request(ClusterSpec.Type.container,
ClusterSpec.Id.from("node-admin"),
Version.fromString("6.42"),
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java
index bc83e3525ad..4fd20d6991b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java
@@ -78,6 +78,7 @@ public class InfrastructureProvisionerTest {
public void remove_application_if_without_target_version() {
when(infrastructureVersions.getTargetVersionFor(eq(nodeType))).thenReturn(Optional.empty());
addNode(1, Node.State.active, Optional.of(target));
+ when(duperModelInfraApi.infraApplicationIsActive(eq(application.getApplicationId()))).thenReturn(true);
infrastructureProvisioner.maintain();
verify(duperModelInfraApi).infraApplicationRemoved(application.getApplicationId());
verifyRemoved(1);
@@ -85,12 +86,26 @@ public class InfrastructureProvisionerTest {
@Test
public void remove_application_if_without_nodes() {
+ remove_application_without_nodes(true);
+ }
+
+ @Test
+ public void skip_remove_unless_active() {
+ remove_application_without_nodes(false);
+ }
+
+ private void remove_application_without_nodes(boolean applicationIsActive) {
when(infrastructureVersions.getTargetVersionFor(eq(nodeType))).thenReturn(Optional.of(target));
addNode(1, Node.State.failed, Optional.of(target));
addNode(2, Node.State.parked, Optional.empty());
+ when(duperModelInfraApi.infraApplicationIsActive(eq(application.getApplicationId()))).thenReturn(applicationIsActive);
infrastructureProvisioner.maintain();
- verify(duperModelInfraApi).infraApplicationRemoved(application.getApplicationId());
- verifyRemoved(1);
+ if (applicationIsActive) {
+ verify(duperModelInfraApi).infraApplicationRemoved(application.getApplicationId());
+ verifyRemoved(1);
+ } else {
+ verifyRemoved(0);
+ }
}
@Test
@@ -199,6 +214,7 @@ public class InfrastructureProvisionerTest {
@Test
public void avoid_provisioning_if_no_usable_nodes() {
when(infrastructureVersions.getTargetVersionFor(eq(nodeType))).thenReturn(Optional.of(target));
+ when(duperModelInfraApi.infraApplicationIsActive(eq(application.getApplicationId()))).thenReturn(true);
infrastructureProvisioner.maintain();
verifyRemoved(1);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
new file mode 100644
index 00000000000..59323bfdeb5
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
@@ -0,0 +1,96 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.maintenance;
+
+import com.yahoo.component.Vtag;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.HostSpec;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.transaction.NestedTransaction;
+import com.yahoo.vespa.hosted.provision.flag.FlagId;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
+import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId;
+import com.yahoo.vespa.hosted.provision.node.Agent;
+import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class LoadBalancerExpirerTest {
+
+ private ProvisioningTester tester;
+
+ @Before
+ public void before() {
+ tester = new ProvisioningTester(Zone.defaultZone());
+ }
+
+ @Test
+ public void test_maintain() {
+ LoadBalancerExpirer expirer = new LoadBalancerExpirer(tester.nodeRepository(),
+ Duration.ofDays(1),
+ new JobControl(tester.nodeRepository().database()),
+ tester.loadBalancerService());
+ tester.nodeRepository().flags().setEnabled(FlagId.exclusiveLoadBalancer, true);
+ Supplier<Map<LoadBalancerId, LoadBalancer>> loadBalancers = () -> tester.nodeRepository().database().readLoadBalancers();
+
+ // Deploy two applications with load balancers
+ ClusterSpec.Id cluster = ClusterSpec.Id.from("qrs");
+ ApplicationId app1 = tester.makeApplicationId();
+ ApplicationId app2 = tester.makeApplicationId();
+ LoadBalancerId lb1 = new LoadBalancerId(app1, cluster);
+ LoadBalancerId lb2 = new LoadBalancerId(app2, cluster);
+ deployApplication(app1, cluster);
+ deployApplication(app2, cluster);
+ assertEquals(2, loadBalancers.get().size());
+
+ // Remove one application deactivates load balancers for that application
+ removeApplication(app1);
+ assertTrue(loadBalancers.get().get(lb1).inactive());
+ assertFalse(loadBalancers.get().get(lb2).inactive());
+
+ // Expirer defers removal while nodes are still allocated to application
+ expirer.maintain();
+ assertEquals(2, tester.loadBalancerService().loadBalancers().size());
+
+ // Expirer removes load balancers once nodes are deallocated
+ dirtyNodesOf(app1);
+ expirer.maintain();
+ assertFalse("Inactive load balancer removed", tester.loadBalancerService().loadBalancers().containsKey(lb1));
+
+ // Active load balancer is left alone
+ assertFalse(loadBalancers.get().get(lb2).inactive());
+ assertTrue("Active load balancer is not removed", tester.loadBalancerService().loadBalancers().containsKey(lb2));
+ }
+
+ private void dirtyNodesOf(ApplicationId application) {
+ tester.nodeRepository().setDirty(tester.nodeRepository().getNodes(application), Agent.system, "unit-test");
+ }
+
+ private void removeApplication(ApplicationId application) {
+ NestedTransaction transaction = new NestedTransaction();
+ tester.provisioner().remove(transaction, application);
+ transaction.commit();
+ }
+
+ private void deployApplication(ApplicationId application, ClusterSpec.Id cluster) {
+ tester.makeReadyNodes(10, "default");
+ List<HostSpec> hosts = tester.prepare(application, ClusterSpec.request(ClusterSpec.Type.container, cluster,
+ Vtag.currentVersion, false),
+ 2, 1,
+ "default");
+ tester.activate(application, hosts);
+ }
+
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
index 5612c8dc665..2c63b9fd62c 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
@@ -101,7 +101,7 @@ public class ProvisioningTester {
}
}
- static FlavorsConfig createConfig() {
+ public static FlavorsConfig createConfig() {
FlavorConfigBuilder b = new FlavorConfigBuilder();
b.addFlavor("default", 2., 4., 100, Flavor.Type.BARE_METAL).cost(3);
b.addFlavor("small", 1., 2., 50, Flavor.Type.BARE_METAL).cost(2);
@@ -170,11 +170,11 @@ public class ProvisioningTester {
deactivateTransaction.commit();
}
- Collection<String> toHostNames(Collection<HostSpec> hosts) {
+ public Collection<String> toHostNames(Collection<HostSpec> hosts) {
return hosts.stream().map(HostSpec::hostname).collect(Collectors.toSet());
}
- Set<String> toHostNames(List<Node> nodes) {
+ public Set<String> toHostNames(List<Node> nodes) {
return nodes.stream().map(Node::hostname).collect(Collectors.toSet());
}
@@ -182,7 +182,7 @@ public class ProvisioningTester {
* Asserts that each active node in this application has a restart count equaling the
* number of matches to the given filters
*/
- void assertRestartCount(ApplicationId application, HostFilter... filters) {
+ public void assertRestartCount(ApplicationId application, HostFilter... filters) {
for (Node node : nodeRepository.getNodes(application, Node.State.active)) {
int expectedRestarts = 0;
for (HostFilter filter : filters)
@@ -199,7 +199,7 @@ public class ProvisioningTester {
assertEquals(beforeFailCount + 1, failedNode.status().failCount());
}
- void assertMembersOf(ClusterSpec requestedCluster, Collection<HostSpec> hosts) {
+ public void assertMembersOf(ClusterSpec requestedCluster, Collection<HostSpec> hosts) {
Set<Integer> indices = new HashSet<>();
for (HostSpec host : hosts) {
ClusterSpec nodeCluster = host.membership().get().cluster();
@@ -214,14 +214,14 @@ public class ProvisioningTester {
assertEquals("Indexes in " + requestedCluster + " are disjunct", hosts.size(), indices.size());
}
- HostSpec removeOne(Set<HostSpec> hosts) {
+ public HostSpec removeOne(Set<HostSpec> hosts) {
Iterator<HostSpec> i = hosts.iterator();
HostSpec removed = i.next();
i.remove();
return removed;
}
- ApplicationId makeApplicationId() {
+ public ApplicationId makeApplicationId() {
return ApplicationId.from(
TenantName.from(UUID.randomUUID().toString()),
ApplicationName.from(UUID.randomUUID().toString()),
@@ -232,15 +232,15 @@ public class ProvisioningTester {
return makeReadyNodes(n, flavor, NodeType.tenant);
}
- List<Node> makeReadyNodes(int n, String flavor, NodeType type) {
+ public List<Node> makeReadyNodes(int n, String flavor, NodeType type) {
return makeReadyNodes(n, flavor, type, 0);
}
- List<Node> makeProvisionedNodes(int count, String flavor, NodeType type, int ipAddressPoolSize) {
+ public List<Node> makeProvisionedNodes(int count, String flavor, NodeType type, int ipAddressPoolSize) {
return makeProvisionedNodes(count, flavor, type, ipAddressPoolSize, false);
}
- List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ public List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
@@ -290,7 +290,7 @@ public class ProvisioningTester {
return nodes;
}
- List<Node> makeConfigServers(int n, String flavor, Version configServersVersion) {
+ public List<Node> makeConfigServers(int n, String flavor, Version configServersVersion) {
List<Node> nodes = new ArrayList<>(n);
MockNameResolver nameResolver = (MockNameResolver)nodeRepository().nameResolver();
@@ -322,33 +322,33 @@ public class ProvisioningTester {
return nodeRepository.getNodes(application.getApplicationId(), Node.State.active);
}
- List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize) {
+ public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize) {
return makeReadyNodes(n, flavor, type, ipAddressPoolSize, false);
}
- List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = makeProvisionedNodes(n, flavor, type, ipAddressPoolSize, dualStack);
nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
/** Creates a set of virtual docker nodes on a single docker host */
- List<Node> makeReadyDockerNodes(int n, String flavor, String dockerHostId) {
+ public List<Node> makeReadyDockerNodes(int n, String flavor, String dockerHostId) {
return makeReadyVirtualNodes(n, flavor, Optional.of(dockerHostId));
}
/** Creates a set of virtual nodes on a single parent host */
- List<Node> makeReadyVirtualNodes(int n, String flavor, Optional<String> parentHostId) {
+ public List<Node> makeReadyVirtualNodes(int n, String flavor, Optional<String> parentHostId) {
return makeReadyVirtualNodes(n, 0, flavor, parentHostId, index -> UUID.randomUUID().toString());
}
/** Creates a set of virtual nodes on a single parent host */
- List<Node> makeReadyVirtualNode(int index, String flavor, String parentHostId) {
+ public List<Node> makeReadyVirtualNode(int index, String flavor, String parentHostId) {
return makeReadyVirtualNodes(1, index, flavor, Optional.of(parentHostId), i -> String.format("node%03d", i));
}
/** Creates a set of virtual nodes on a single parent host */
- List<Node> makeReadyVirtualNodes(int count, int startIndex, String flavor, Optional<String> parentHostId,
+ public List<Node> makeReadyVirtualNodes(int count, int startIndex, String flavor, Optional<String> parentHostId,
Function<Integer, String> nodeNamer) {
List<Node> nodes = new ArrayList<>(count);
for (int i = startIndex; i < count + startIndex; i++) {
@@ -362,16 +362,16 @@ public class ProvisioningTester {
return nodes;
}
- List<Node> makeReadyVirtualNodes(int n, String flavor, String parentHostId) {
+ public List<Node> makeReadyVirtualNodes(int n, String flavor, String parentHostId) {
return makeReadyVirtualNodes(n, flavor, Optional.of(parentHostId));
}
/** Returns the hosts from the input list which are not retired */
- List<HostSpec> nonRetired(Collection<HostSpec> hosts) {
+ public List<HostSpec> nonRetired(Collection<HostSpec> hosts) {
return hosts.stream().filter(host -> ! host.membership().get().retired()).collect(Collectors.toList());
}
- void assertNumberOfNodesWithFlavor(List<HostSpec> hostSpecs, String flavor, int expectedCount) {
+ public void assertNumberOfNodesWithFlavor(List<HostSpec> hostSpecs, String flavor, int expectedCount) {
long actualNodesWithFlavor = hostSpecs.stream()
.map(HostSpec::hostname)
.map(this::getNodeFlavor)
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 ae7f3f14975..ec09805ff5d 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
@@ -706,6 +706,13 @@ public class RestApiTest {
}
}
+ @Test
+ public void test_load_balancers() throws Exception {
+ assertFile(new Request("http://localhost:8080/loadbalancers/v1/"), "load-balancers.json");
+ assertFile(new Request("http://localhost:8080/loadbalancers/v1/?application=zoneapp.zoneapp.zoneapp"), "load-balancers.json");
+ assertResponse(new Request("http://localhost:8080/loadbalancers/v1/?application=tenant.nonexistent.default"), "{\"loadBalancers\":[]}");
+ }
+
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/filter/AuthorizerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java
index 38128e66861..5e643bd09ab 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java
@@ -139,6 +139,30 @@ public class AuthorizerTest {
}
@Test
+ public void flags_authorization() {
+ // Tenant nodes cannot access flags resources
+ assertFalse(authorizedTenantNode("node1", "/flags/v1/data"));
+ assertFalse(authorizedTenantNode("node1", "/flags/v1/data/flagid"));
+ assertFalse(authorizedTenantNode("node1", "/flags/v1/foo"));
+
+ // Host node can access data
+ assertTrue(authorizedTenantHostNode("host1", "/flags/v1/data"));
+ assertFalse(authorizedTenantHostNode("host1", "/flags/v1/data/flagid"));
+ assertFalse(authorizedTenantHostNode("host1", "/flags/v1/foo"));
+ assertTrue(authorizedTenantHostNode("proxy1-host", "/flags/v1/data"));
+ assertFalse(authorizedTenantHostNode("proxy1-host", "/flags/v1/data/flagid"));
+ assertFalse(authorizedTenantHostNode("proxy1-host", "/flags/v1/foo"));
+ assertTrue(authorizedController("vespa.vespa.configserver", "/flags/v1/data"));
+ assertFalse(authorizedController("vespa.vespa.configserver", "/flags/v1/data/flagid"));
+ assertFalse(authorizedController("vespa.vespa.configserver", "/flags/v1/foo"));
+
+ // Controller can access everything
+ assertTrue(authorizedController("vespa.vespa.hosting", "/flags/v1/data"));
+ assertTrue(authorizedController("vespa.vespa.hosting", "/flags/v1/data/flagid"));
+ assertTrue(authorizedController("vespa.vespa.hosting", "/flags/v1/foo"));
+ }
+
+ @Test
public void routing_authorization() {
// Node of proxy or proxyhost type can access routing resource
assertFalse(authorizedTenantNode("node1", "/routing/v1/status"));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json
index 8fd09b4a274..a606777e9fd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json
@@ -4,7 +4,7 @@
"id": "exclusive-load-balancer",
"enabled": false,
"enabledHostnames": [],
- "enabledApplications": []
+ "enabledApplications": ["zoneapp:zoneapp:zoneapp"]
}
]
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json
index 78de52e4e85..4baf75f2169 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json
@@ -7,6 +7,7 @@
"host1"
],
"enabledApplications": [
+ "zoneapp:zoneapp:zoneapp",
"foo:bar:default"
]
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json
new file mode 100644
index 00000000000..c882f7652d8
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json
@@ -0,0 +1,43 @@
+{
+ "loadBalancers": [
+ {
+ "id": "zoneapp:zoneapp:zoneapp:node-admin",
+ "application": "zoneapp",
+ "tenant": "zoneapp",
+ "instance": "zoneapp",
+ "cluster": "node-admin",
+ "hostname": "lb-zoneapp.zoneapp.zoneapp-node-admin",
+ "ports": [
+ 4443
+ ],
+ "reals": [
+ {
+ "hostname": "dockerhost4.yahoo.com",
+ "ipAddress": "127.0.0.1",
+ "port": 4443
+ },
+ {
+ "hostname": "dockerhost5.yahoo.com",
+ "ipAddress": "127.0.0.1",
+ "port": 4443
+ },
+ {
+ "hostname": "dockerhost2.yahoo.com",
+ "ipAddress": "127.0.0.1",
+ "port": 4443
+ },
+ {
+ "hostname": "dockerhost3.yahoo.com",
+ "ipAddress": "127.0.0.1",
+ "port": 4443
+ },
+ {
+ "hostname": "dockerhost1.yahoo.com",
+ "ipAddress": "127.0.0.1",
+ "port": 4443
+ }
+ ],
+ "inactive": false
+ }
+ ]
+}
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 99cb9fd91f5..1432d2f4ea5 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
@@ -1,46 +1,49 @@
{
- "jobs":[
+ "jobs": [
{
- "name":"PeriodicApplicationMaintainer"
+ "name": "DirtyExpirer"
},
{
- "name":"FailedExpirer"
+ "name": "FailedExpirer"
},
{
- "name":"ReservationExpirer"
+ "name": "InactiveExpirer"
},
{
- "name":"RetiredExpirer"
+ "name": "InfrastructureProvisioner"
},
{
- "name":"NodeRebooter"
+ "name": "LoadBalancerExpirer"
},
{
- "name":"InactiveExpirer"
+ "name": "MetricsReporter"
},
{
- "name":"DirtyExpirer"
+ "name": "NodeFailer"
},
{
- "name":"NodeRetirer"
+ "name": "NodeRebooter"
},
{
- "name":"OperatorChangeApplicationMaintainer"
+ "name": "NodeRetirer"
},
{
- "name":"ProvisionedExpirer"
+ "name": "OperatorChangeApplicationMaintainer"
},
{
- "name":"MetricsReporter"
+ "name": "PeriodicApplicationMaintainer"
},
{
- "name":"InfrastructureProvisioner"
+ "name": "ProvisionedExpirer"
},
{
- "name":"NodeFailer"
+ "name": "ReservationExpirer"
+ },
+ {
+ "name": "RetiredExpirer"
}
],
- "inactive":[
+ "inactive": [
"NodeFailer"
]
}
diff --git a/searchcore/src/tests/proton/docsummary/docsummary.cpp b/searchcore/src/tests/proton/docsummary/docsummary.cpp
index db6116073e6..c0c706383f6 100644
--- a/searchcore/src/tests/proton/docsummary/docsummary.cpp
+++ b/searchcore/src/tests/proton/docsummary/docsummary.cpp
@@ -106,7 +106,7 @@ public:
TuneFileSummary(),
_fileHeaderContext,
_noTlSyncer,
- NULL),
+ nullptr),
_serialNum(1)
{
}
@@ -262,7 +262,7 @@ public:
op.setPrevDbDocumentId(prevDbdId);
_ddb->getFeedHandler().storeOperation(op, std::make_shared<search::IgnoreCallback>());
SearchView *sv(dynamic_cast<SearchView *>(_ddb->getReadySubDB()->getSearchView().get()));
- if (sv != NULL) {
+ if (sv != nullptr) {
// cf. FeedView::putAttributes()
DocIdLimit &docIdLimit = sv->getDocIdLimit();
if (docIdLimit.get() <= lid)
@@ -278,14 +278,11 @@ private:
ResultConfig _resultCfg;
std::set<vespalib::string> _markupFields;
- const ResultConfig &getResultConfig() const
- {
+ const ResultConfig &getResultConfig() const{
return _resultCfg;
}
- const std::set<vespalib::string> &
- getMarkupFields() const
- {
+ const std::set<vespalib::string> &getMarkupFields() const{
return _markupFields;
}
@@ -295,31 +292,12 @@ private:
GeneralResultPtr
getResult(const DocsumReply & reply, uint32_t id, uint32_t resultClassID);
- bool
- assertString(const std::string & exp,
- const std::string & fieldName,
- DocumentStoreAdapter &dsa,
- uint32_t id);
+ bool assertString(const std::string & exp, const std::string & fieldName, DocumentStoreAdapter &dsa, uint32_t id);
- bool
- assertString(const std::string &exp,
- const std::string &fieldName,
- const DocsumReply &reply,
- uint32_t id,
- uint32_t resultClassID);
+ void assertTensor(const Tensor::UP &exp, const std::string &fieldName, const DocsumReply &reply,
+ uint32_t id, uint32_t resultClassID);
- void
- assertTensor(const Tensor::UP &exp,
- const std::string &fieldName,
- const DocsumReply &reply,
- uint32_t id,
- uint32_t resultClassID);
-
- bool
- assertSlime(const std::string &exp,
- const DocsumReply &reply,
- uint32_t id,
- bool relaxed = false);
+ bool assertSlime(const std::string &exp, const DocsumReply &reply, uint32_t id,bool relaxed = false);
void requireThatAdapterHandlesAllFieldTypes();
void requireThatAdapterHandlesMultipleDocuments();
@@ -346,12 +324,10 @@ GeneralResultPtr
Test::getResult(DocumentStoreAdapter & dsa, uint32_t docId)
{
DocsumStoreValue docsum = dsa.getMappedDocsum(docId);
- ASSERT_TRUE(docsum.pt() != NULL);
- GeneralResultPtr retval(new GeneralResult(dsa.getResultClass(),
- 0, 0, 0));
+ ASSERT_TRUE(docsum.pt() != nullptr);
+ auto retval = std::make_unique<GeneralResult>(dsa.getResultClass());
// skip the 4 byte class id
- ASSERT_TRUE(retval->unpack(docsum.pt() + 4,
- docsum.len() - 4) == 0);
+ ASSERT_TRUE(retval->unpack(docsum.pt() + 4, docsum.len() - 4));
return retval;
}
@@ -359,46 +335,26 @@ Test::getResult(DocumentStoreAdapter & dsa, uint32_t docId)
GeneralResultPtr
Test::getResult(const DocsumReply & reply, uint32_t id, uint32_t resultClassID)
{
- GeneralResultPtr retval(new GeneralResult(getResultConfig().
- LookupResultClass(resultClassID),
- 0, 0, 0));
+ auto retval = std::make_unique<GeneralResult>(getResultConfig().LookupResultClass(resultClassID));
const DocsumReply::Docsum & docsum = reply.docsums[id];
// skip the 4 byte class id
- ASSERT_EQUAL(0, retval->unpack(docsum.data.c_str() + 4, docsum.data.size() - 4));
+ ASSERT_TRUE(retval->unpack(docsum.data.c_str() + 4, docsum.data.size() - 4));
return retval;
}
bool
Test::assertString(const std::string & exp, const std::string & fieldName,
- DocumentStoreAdapter &dsa,
- uint32_t id)
+ DocumentStoreAdapter &dsa, uint32_t id)
{
GeneralResultPtr res = getResult(dsa, id);
- return EXPECT_EQUAL(exp, std::string(res->GetEntry(fieldName.c_str())->
- _stringval,
- res->GetEntry(fieldName.c_str())->
- _stringlen));
+ return EXPECT_EQUAL(exp, std::string(res->GetEntry(fieldName.c_str())->_stringval,
+ res->GetEntry(fieldName.c_str())->_stringlen));
}
-
-bool
-Test::assertString(const std::string & exp, const std::string & fieldName,
- const DocsumReply & reply,
- uint32_t id, uint32_t resultClassID)
-{
- GeneralResultPtr res = getResult(reply, id, resultClassID);
- return EXPECT_EQUAL(exp, std::string(res->GetEntry(fieldName.c_str())->
- _stringval,
- res->GetEntry(fieldName.c_str())->
- _stringlen));
-}
-
-
void
Test::assertTensor(const Tensor::UP & exp, const std::string & fieldName,
- const DocsumReply & reply,
- uint32_t id, uint32_t)
+ const DocsumReply & reply, uint32_t id, uint32_t)
{
const DocsumReply::Docsum & docsum = reply.docsums[id];
uint32_t classId;
@@ -408,8 +364,7 @@ Test::assertTensor(const Tensor::UP & exp, const std::string & fieldName,
vespalib::Slime slime;
vespalib::Memory serialized(docsum.data.c_str() + sizeof(classId),
docsum.data.size() - sizeof(classId));
- size_t decodeRes = BinaryFormat::decode(serialized,
- slime);
+ size_t decodeRes = BinaryFormat::decode(serialized, slime);
ASSERT_EQUAL(decodeRes, serialized.size);
EXPECT_EQUAL(exp.get() != nullptr, slime.get()[fieldName].valid());
@@ -547,7 +502,7 @@ Test::requireThatAdapterHandlesMultipleDocuments()
}
{ // doc 2
DocsumStoreValue docsum = dsa.getMappedDocsum(2);
- EXPECT_TRUE(docsum.pt() == NULL);
+ EXPECT_TRUE(docsum.pt() == nullptr);
}
{ // doc 0 (again)
GeneralResultPtr res = getResult(dsa, 0);
@@ -640,7 +595,7 @@ Test::requireThatDocsumRequestIsProcessed()
EXPECT_TRUE(assertSlime("{a:40}", *rep, 1, false));
EXPECT_EQUAL(search::endDocId, rep->docsums[2].docid);
EXPECT_EQUAL(gid9, rep->docsums[2].gid);
- EXPECT_TRUE(rep->docsums[2].data.get() == NULL);
+ EXPECT_TRUE(rep->docsums[2].data.get() == nullptr);
}
@@ -874,11 +829,11 @@ Test::requireThatSummaryAdapterHandlesPutAndRemove()
dc._sa->put(1, 1, *exp);
IDocumentStore & store = dc._ddb->getReadySubDB()->getSummaryManager()->getBackingStore();
Document::UP act = store.read(1, *bc._repo);
- EXPECT_TRUE(act.get() != NULL);
+ EXPECT_TRUE(act.get() != nullptr);
EXPECT_EQUAL(exp->getType(), act->getType());
EXPECT_EQUAL("foo", act->getValue("f1")->toString());
dc._sa->remove(2, 1);
- EXPECT_TRUE(store.read(1, *bc._repo).get() == NULL);
+ EXPECT_TRUE(store.read(1, *bc._repo).get() == nullptr);
}
@@ -928,7 +883,7 @@ Test::requireThatAnnotationsAreUsed()
IDocumentStore & store = dc._ddb->getReadySubDB()->getSummaryManager()->getBackingStore();
Document::UP act = store.read(1, *bc._repo);
- EXPECT_TRUE(act.get() != NULL);
+ EXPECT_TRUE(act.get() != nullptr);
EXPECT_EQUAL(exp->getType(), act->getType());
EXPECT_EQUAL("foo bar", act->getValue("g")->getAsString());
EXPECT_EQUAL("foo bar", act->getValue("dynamicstring")->getAsString());
@@ -1082,7 +1037,7 @@ Test::requireThatUrisAreUsed()
IDocumentStore & store =
dc._ddb->getReadySubDB()->getSummaryManager()->getBackingStore();
Document::UP act = store.read(1, *bc._repo);
- EXPECT_TRUE(act.get() != NULL);
+ EXPECT_TRUE(act.get() != nullptr);
EXPECT_EQUAL(exp->getType(), act->getType());
DocumentStoreAdapter dsa(store, *bc._repo, getResultConfig(), "class0",
@@ -1140,7 +1095,7 @@ Test::requireThatPositionsAreUsed()
IDocumentStore & store =
dc._ddb->getReadySubDB()->getSummaryManager()->getBackingStore();
Document::UP act = store.read(1, *bc._repo);
- EXPECT_TRUE(act.get() != NULL);
+ EXPECT_TRUE(act.get() != nullptr);
EXPECT_EQUAL(exp->getType(), act->getType());
DocsumRequest req;
@@ -1219,7 +1174,7 @@ Test::requireThatRawFieldsWorks()
IDocumentStore & store = dc._ddb->getReadySubDB()->getSummaryManager()->getBackingStore();
Document::UP act = store.read(1, *bc._repo);
- EXPECT_TRUE(act.get() != NULL);
+ EXPECT_TRUE(act.get() != nullptr);
EXPECT_EQUAL(exp->getType(), act->getType());
DocumentStoreAdapter dsa(store, *bc._repo, getResultConfig(), "class0",
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/document_field_extractor.cpp b/searchcore/src/vespa/searchcore/proton/attribute/document_field_extractor.cpp
index 64c3455be84..bbd2221f631 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/document_field_extractor.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/document_field_extractor.cpp
@@ -3,6 +3,7 @@
#include "document_field_extractor.h"
#include <vespa/document/datatype/arraydatatype.h>
#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/boolfieldvalue.h>
#include <vespa/document/fieldvalue/bytefieldvalue.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/fieldvalue/doublefieldvalue.h>
@@ -17,6 +18,7 @@
#include <vespa/vespalib/util/exceptions.h>
using document::FieldValue;
+using document::BoolFieldValue;
using document::ByteFieldValue;
using document::ShortFieldValue;
using document::IntFieldValue;
@@ -45,6 +47,7 @@ class SetUndefinedValueVisitor : public FieldValueVisitor
{
void visit(document::AnnotationReferenceFieldValue &) override { }
void visit(ArrayFieldValue &) override { }
+ void visit(BoolFieldValue &) override { }
void visit(ByteFieldValue &value) override { value = getUndefined<int8_t>(); }
void visit(Document &) override { }
void visit(DoubleFieldValue &value) override { value = getUndefined<double>(); }
@@ -65,6 +68,7 @@ class SetUndefinedValueVisitor : public FieldValueVisitor
SetUndefinedValueVisitor setUndefinedValueVisitor;
const ArrayDataType arrayTypeByte(*DataType::BYTE);
+const ArrayDataType arrayTypeBool(*DataType::BOOL);
const ArrayDataType arrayTypeShort(*DataType::SHORT);
const ArrayDataType arrayTypeInt(*DataType::INT);
const ArrayDataType arrayTypeLong(*DataType::LONG);
@@ -78,6 +82,8 @@ getArrayType(const DataType &fieldType)
switch (fieldType.getId()) {
case DataType::Type::T_BYTE:
return &arrayTypeByte;
+ case DataType::Type::T_BOOL:
+ return &arrayTypeByte;
case DataType::Type::T_SHORT:
return &arrayTypeShort;
case DataType::Type::T_INT:
diff --git a/searchcore/src/vespa/searchcore/proton/docsummary/documentstoreadapter.cpp b/searchcore/src/vespa/searchcore/proton/docsummary/documentstoreadapter.cpp
index ea6a16e1547..c65d18a590f 100644
--- a/searchcore/src/vespa/searchcore/proton/docsummary/documentstoreadapter.cpp
+++ b/searchcore/src/vespa/searchcore/proton/docsummary/documentstoreadapter.cpp
@@ -24,9 +24,7 @@ const vespalib::string DOCUMENT_ID_FIELD("documentid");
}
bool
-DocumentStoreAdapter::writeStringField(const char * buf,
- uint32_t buflen,
- ResType type)
+DocumentStoreAdapter::writeStringField(const char * buf, uint32_t buflen, ResType type)
{
switch (type) {
case RES_STRING:
@@ -47,6 +45,8 @@ DocumentStoreAdapter::writeField(const FieldValue &value, ResType type)
switch (type) {
case RES_BYTE:
return _resultPacker.AddByte(value.getAsInt());
+ case RES_BOOL:
+ return _resultPacker.AddByte(value.getAsInt());
case RES_SHORT:
return _resultPacker.AddShort(value.getAsInt());
case RES_INT:
@@ -63,8 +63,7 @@ DocumentStoreAdapter::writeField(const FieldValue &value, ResType type)
case RES_JSONSTRING:
{
if (value.getClass().inherits(LiteralFieldValueB::classId)) {
- const LiteralFieldValueB & lfv =
- static_cast<const LiteralFieldValueB &>(value);
+ auto & lfv = static_cast<const LiteralFieldValueB &>(value);
vespalib::stringref s = lfv.getValueRef();
return writeStringField(s.data(), s.size(), type);
} else {
@@ -86,7 +85,7 @@ DocumentStoreAdapter::writeField(const FieldValue &value, ResType type)
{
vespalib::nbostream serialized;
if (value.getClass().inherits(TensorFieldValue::classId)) {
- const TensorFieldValue &tvalue = static_cast<const TensorFieldValue &>(value);
+ const auto &tvalue = static_cast<const TensorFieldValue &>(value);
const std::unique_ptr<Tensor> &tensor = tvalue.getAsTensorPtr();
if (tensor) {
vespalib::tensor::TypedBinaryFormat::serialize(serialized, *tensor);
@@ -95,9 +94,7 @@ DocumentStoreAdapter::writeField(const FieldValue &value, ResType type)
return _resultPacker.AddSerializedTensor(serialized.peek(), serialized.size());
}
default:
- LOG(warning,
- "Unknown docsum field type: %s. Add empty field",
- ResultConfig::GetResTypeName(type));
+ LOG(warning, "Unknown docsum field type: %s. Add empty field",ResultConfig::GetResTypeName(type));
return _resultPacker.AddEmpty();
}
return false;
@@ -114,46 +111,32 @@ DocumentStoreAdapter::convertFromSearchDoc(Document &doc, uint32_t docId)
if (fieldName == DOCUMENT_ID_FIELD) {
StringFieldValue value(doc.getId().toString());
if (!writeField(value, entry->_type)) {
- LOG(warning, "Error while writing field '%s' for docId %u",
- fieldName.c_str(), docId);
+ LOG(warning, "Error while writing field '%s' for docId %u", fieldName.c_str(), docId);
}
continue;
}
const Field *field = _fieldCache->getField(i);
if (!field) {
- LOG(debug,
- "Did not find field '%s' in the document "
- "for docId %u. Adding empty field",
+ LOG(debug, "Did not find field '%s' in the document for docId %u. Adding empty field",
fieldName.c_str(), docId);
_resultPacker.AddEmpty();
continue;
}
FieldValue::UP fieldValue = doc.getValue(*field);
- if (fieldValue.get() == NULL) {
- LOG(spam,
- "No field value for field '%s' in the document "
- "for docId %u. Adding empty field",
+ if ( ! fieldValue) {
+ LOG(spam, "No field value for field '%s' in the document for docId %u. Adding empty field",
fieldName.c_str(), docId);
_resultPacker.AddEmpty();
continue;
}
- LOG(spam,
- "writeField(%s): value(%s), type(%d)",
- fieldName.c_str(), fieldValue->toString().c_str(),
- entry->_type);
- FieldValue::UP convertedFieldValue =
- SummaryFieldConverter::convertSummaryField(markup, *fieldValue);
- if (convertedFieldValue.get() != NULL) {
+ LOG(spam, "writeField(%s): value(%s), type(%d)", fieldName.c_str(), fieldValue->toString().c_str(), entry->_type);
+ FieldValue::UP convertedFieldValue = SummaryFieldConverter::convertSummaryField(markup, *fieldValue);
+ if (convertedFieldValue) {
if (!writeField(*convertedFieldValue, entry->_type)) {
- LOG(warning,
- "Error while writing field '%s' for docId %u",
- fieldName.c_str(), docId);
+ LOG(warning, "Error while writing field '%s' for docId %u", fieldName.c_str(), docId);
}
} else {
- LOG(spam,
- "No converted field value for field '%s' "
- " in the document "
- "for docId %u. Adding empty field",
+ LOG(spam, "No converted field value for field '%s' in the document for docId %u. Adding empty field",
fieldName.c_str(), docId);
_resultPacker.AddEmpty();
}
@@ -171,46 +154,33 @@ DocumentStoreAdapter(const search::IDocumentStore & docStore,
_repo(repo),
_resultConfig(resultConfig),
_resultClass(resultConfig.
- LookupResultClass(resultConfig.
- LookupResultClassId(resultClassName.
- c_str()))),
+ LookupResultClass(resultConfig.LookupResultClassId(resultClassName.c_str()))),
_resultPacker(&_resultConfig),
_fieldCache(fieldCache),
_markupFields(markupFields)
{
}
-DocumentStoreAdapter::~DocumentStoreAdapter() {}
+DocumentStoreAdapter::~DocumentStoreAdapter() = default;
DocsumStoreValue
DocumentStoreAdapter::getMappedDocsum(uint32_t docId)
{
if (!_resultPacker.Init(getSummaryClassId())) {
- LOG(warning,
- "Error during init of result class '%s' with class id %u",
- _resultClass->GetClassName(), getSummaryClassId());
+ LOG(warning, "Error during init of result class '%s' with class id %u", _resultClass->GetClassName(), getSummaryClassId());
return DocsumStoreValue();
}
Document::UP document = _docStore.read(docId, _repo);
- if (document.get() == NULL) {
- LOG(debug,
- "Did not find summary document for docId %u. "
- "Returning empty docsum",
- docId);
+ if ( ! document) {
+ LOG(debug, "Did not find summary document for docId %u. Returning empty docsum", docId);
return DocsumStoreValue();
}
- LOG(spam,
- "getMappedDocSum(%u): document={\n%s\n}",
- docId,
- document->toString(true).c_str());
+ LOG(spam, "getMappedDocSum(%u): document={\n%s\n}", docId, document->toString(true).c_str());
convertFromSearchDoc(*document, docId);
const char * buf;
uint32_t buflen;
if (!_resultPacker.GetDocsumBlob(&buf, &buflen)) {
- LOG(warning,
- "Error while getting the docsum blob for docId %u. "
- "Returning empty docsum",
- docId);
+ LOG(warning, "Error while getting the docsum blob for docId %u. Returning empty docsum", docId);
return DocsumStoreValue();
}
return DocsumStoreValue(buf, buflen);
diff --git a/searchcore/src/vespa/searchcore/proton/docsummary/summarymanager.cpp b/searchcore/src/vespa/searchcore/proton/docsummary/summarymanager.cpp
index 54961d10fd3..1abe9540859 100644
--- a/searchcore/src/vespa/searchcore/proton/docsummary/summarymanager.cpp
+++ b/searchcore/src/vespa/searchcore/proton/docsummary/summarymanager.cpp
@@ -51,7 +51,7 @@ public:
SerialNum flushedSerialNum, Time lastFlushTime,
searchcorespi::index::IThreadService & summaryService,
std::shared_ptr<ICompactableLidSpace> target);
- ~ShrinkSummaryLidSpaceFlushTarget();
+ ~ShrinkSummaryLidSpaceFlushTarget() override;
Task::UP initFlush(SerialNum currentSerial) override;
};
@@ -65,7 +65,7 @@ ShrinkSummaryLidSpaceFlushTarget(const vespalib::string &name, Type type, Compon
{
}
-ShrinkSummaryLidSpaceFlushTarget::~ShrinkSummaryLidSpaceFlushTarget() {}
+ShrinkSummaryLidSpaceFlushTarget::~ShrinkSummaryLidSpaceFlushTarget() = default;
IFlushTarget::Task::UP
ShrinkSummaryLidSpaceFlushTarget::initFlush(SerialNum currentSerial)
@@ -93,7 +93,7 @@ SummarySetup(const vespalib::string & baseDir, const DocTypeName & docTypeName,
_repo(repo),
_markupFields()
{
- std::unique_ptr<ResultConfig> resultConfig(new ResultConfig());
+ auto resultConfig = std::make_unique<ResultConfig>();
if (!resultConfig->ReadConfig(summaryCfg, make_string("SummaryManager(%s)", baseDir.c_str()).c_str())) {
std::ostringstream oss;
config::OstreamConfigWriter writer(oss);
@@ -103,12 +103,11 @@ SummarySetup(const vespalib::string & baseDir, const DocTypeName & docTypeName,
baseDir.c_str(), oss.str().c_str()));
}
- _juniperConfig.reset(new juniper::Juniper(&_juniperProps, &_wordFolder));
- _docsumWriter.reset(new DynamicDocsumWriter(resultConfig.release(), NULL));
+ _juniperConfig = std::make_unique<juniper::Juniper>(&_juniperProps, &_wordFolder);
+ _docsumWriter = std::make_unique<DynamicDocsumWriter>(resultConfig.release(), nullptr);
DynamicDocsumConfig dynCfg(this, _docsumWriter.get());
dynCfg.configure(summarymapCfg);
- for (size_t i = 0; i < summarymapCfg.override.size(); ++i) {
- const SummarymapConfig::Override & o = summarymapCfg.override[i];
+ for (const auto & o : summarymapCfg.override) {
if (o.command == "dynamicteaser" || o.command == "textextractor") {
vespalib::string markupField = o.arguments;
if (markupField.empty())
@@ -118,11 +117,11 @@ SummarySetup(const vespalib::string & baseDir, const DocTypeName & docTypeName,
}
}
const DocumentType *docType = repo->getDocumentType(docTypeName.getName());
- if (docType != NULL) {
- _fieldCacheRepo.reset(new FieldCacheRepo(getResultConfig(), *docType));
+ if (docType != nullptr) {
+ _fieldCacheRepo = std::make_unique<FieldCacheRepo>(getResultConfig(), *docType);
} else if (getResultConfig().GetNumResultClasses() == 0) {
LOG(debug, "Create empty field cache repo for document type '%s'", docTypeName.toString().c_str());
- _fieldCacheRepo.reset(new FieldCacheRepo());
+ _fieldCacheRepo = std::make_unique<FieldCacheRepo>();
} else {
throw IllegalArgumentException(make_string("Did not find document type '%s' in current document type repo."
" Cannot setup field cache repo for the summary setup",
@@ -212,7 +211,7 @@ IFlushTarget::List SummaryManager::getFlushTargets(searchcorespi::index::IThread
}
void SummaryManager::reconfigure(const LogDocumentStore::Config & config) {
- LogDocumentStore & docStore = dynamic_cast<LogDocumentStore &> (*_docStore);
+ auto & docStore = dynamic_cast<LogDocumentStore &> (*_docStore);
docStore.reconfigure(config);
}
diff --git a/searchlib/src/tests/fef/termfieldmodel/termfieldmodel_test.cpp b/searchlib/src/tests/fef/termfieldmodel/termfieldmodel_test.cpp
index e689e613886..ca9e331bb62 100644
--- a/searchlib/src/tests/fef/termfieldmodel/termfieldmodel_test.cpp
+++ b/searchlib/src/tests/fef/termfieldmodel/termfieldmodel_test.cpp
@@ -286,4 +286,32 @@ TEST("require that MatchData soft_reset retains appropriate state") {
EXPECT_EQUAL(new_term->getDocId(), TermFieldMatchData::invalidId());
}
+TEST("require that compareWithExactness implements a strict weak ordering") {
+ TermFieldMatchDataPosition a(0, 1, 100, 1);
+ TermFieldMatchDataPosition b(0, 2, 100, 1);
+ TermFieldMatchDataPosition c(0, 2, 100, 1);
+ TermFieldMatchDataPosition d(0, 3, 100, 3);
+ TermFieldMatchDataPosition e(0, 3, 100, 3);
+ TermFieldMatchDataPosition f(0, 4, 100, 1);
+
+ d.setMatchExactness(0.75);
+ e.setMatchExactness(0.5);
+
+ bool (*cmp)(const TermFieldMatchDataPosition &a,
+ const TermFieldMatchDataPosition &b) = TermFieldMatchDataPosition::compareWithExactness;
+
+ EXPECT_EQUAL(true, cmp(a, b));
+ EXPECT_EQUAL(false, cmp(b, c));
+ EXPECT_EQUAL(true, cmp(c, d));
+ EXPECT_EQUAL(true, cmp(d, e));
+ EXPECT_EQUAL(true, cmp(e, f));
+
+ EXPECT_EQUAL(false, cmp(b, a));
+ EXPECT_EQUAL(false, cmp(c, b));
+ EXPECT_EQUAL(false, cmp(d, c));
+ EXPECT_EQUAL(false, cmp(e, d));
+ EXPECT_EQUAL(false, cmp(f, e));
+}
+
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/vespa/searchlib/datastore/CMakeLists.txt b/searchlib/src/vespa/searchlib/datastore/CMakeLists.txt
index 0698c57246a..5af7bd21d78 100644
--- a/searchlib/src/vespa/searchlib/datastore/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/datastore/CMakeLists.txt
@@ -6,5 +6,6 @@ vespa_add_library(searchlib_datastore OBJECT
bufferstate.cpp
datastore.cpp
datastorebase.cpp
+ entryref.cpp
DEPENDS
)
diff --git a/searchlib/src/vespa/searchlib/datastore/entryref.cpp b/searchlib/src/vespa/searchlib/datastore/entryref.cpp
new file mode 100644
index 00000000000..f7224191d1b
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/datastore/entryref.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "entryref.hpp"
+
+namespace search::datastore {
+
+template EntryRefT<24u, 8u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<31u, 1u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<22u,10u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<19u,13u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<18u, 6u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<15u,17u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<10u,22u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT<10u,10u>::EntryRefT(uint64_t, uint32_t);
+template EntryRefT< 3u, 2u>::EntryRefT(uint64_t, uint32_t);
+
+}
diff --git a/searchlib/src/vespa/searchlib/datastore/entryref.h b/searchlib/src/vespa/searchlib/datastore/entryref.h
index f4dec5dbef3..457ffac4e26 100644
--- a/searchlib/src/vespa/searchlib/datastore/entryref.h
+++ b/searchlib/src/vespa/searchlib/datastore/entryref.h
@@ -3,7 +3,6 @@
#pragma once
#include <cstdint>
-#include <vespa/vespalib/util/assert.h>
namespace search::datastore {
@@ -28,12 +27,7 @@ template <uint32_t OffsetBits, uint32_t BufferBits = 32u - OffsetBits>
class EntryRefT : public EntryRef {
public:
EntryRefT() : EntryRef() {}
- EntryRefT(uint64_t offset_, uint32_t bufferId_) :
- EntryRef((offset_ << BufferBits) + bufferId_)
- {
- ASSERT_ONCE_OR_LOG(offset_ < offsetSize(), "EntryRefT.offset_overflow", 10000);
- ASSERT_ONCE_OR_LOG(bufferId_ < numBuffers(), "EntryRefT.bufferId_overflow", 10000);
- }
+ EntryRefT(uint64_t offset_, uint32_t bufferId_);
EntryRefT(const EntryRef & ref_) : EntryRef(ref_.ref()) {}
uint32_t hash() const { return offset() + (bufferId() << OffsetBits); }
uint64_t offset() const { return _ref >> BufferBits; }
diff --git a/searchlib/src/vespa/searchlib/datastore/entryref.hpp b/searchlib/src/vespa/searchlib/datastore/entryref.hpp
new file mode 100644
index 00000000000..a7bb9f9b3ef
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/datastore/entryref.hpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "entryref.h"
+#include <vespa/vespalib/util/assert.h>
+
+namespace search::datastore {
+
+template <uint32_t OffsetBits, uint32_t BufferBits>
+EntryRefT<OffsetBits, BufferBits>::EntryRefT(uint64_t offset_, uint32_t bufferId_) :
+ EntryRef((offset_ << BufferBits) + bufferId_)
+{
+ ASSERT_ONCE_OR_LOG(offset_ < offsetSize(), "EntryRefT.offset_overflow", 10000);
+ ASSERT_ONCE_OR_LOG(bufferId_ < numBuffers(), "EntryRefT.bufferId_overflow", 10000);
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/util/sigbushandler.cpp b/searchlib/src/vespa/searchlib/util/sigbushandler.cpp
index 9de3f16be67..7356baab131 100644
--- a/searchlib/src/vespa/searchlib/util/sigbushandler.cpp
+++ b/searchlib/src/vespa/searchlib/util/sigbushandler.cpp
@@ -97,7 +97,7 @@ SigBusHandler::handle(int sig, siginfo_t *si, void *ucv)
do {
// Protect against multiple threads.
TryLockGuard guard;
- if (!guard.gotLock()) {
+ if (!guard.gotLock() || _fired) {
raced = true;
break;
}
@@ -121,18 +121,19 @@ SigBusHandler::handle(int sig, siginfo_t *si, void *ucv)
sleep(5);
return;
}
- untrap(); // Further bus errors will trigger core dump
if (_unwind != nullptr) {
// Unit test is using siglongjmp based unwinding
sigjmp_buf *unwind = _unwind;
_unwind = nullptr;
+ untrap(); // Further bus errors will trigger core dump
siglongjmp(*unwind, 1);
} else {
// Normal case, sleep 3 seconds (i.e. allow main thread to detect
// issue and notify cluster controller) before returning and
// likely core dumping.
sleep(3);
+ untrap(); // Further bus errors will trigger core dump
}
}
diff --git a/searchsummary/src/tests/docsumformat/docsum-pack.cpp b/searchsummary/src/tests/docsumformat/docsum-pack.cpp
index 7a9834e3fd8..18b38db3fa1 100644
--- a/searchsummary/src/tests/docsumformat/docsum-pack.cpp
+++ b/searchsummary/src/tests/docsumformat/docsum-pack.cpp
@@ -10,7 +10,6 @@ LOG_SETUP("docsum-pack");
using namespace search::docsummary;
-
// needed to resolve external symbol from httpd.h on AIX
void FastS_block_usr2() {}
@@ -20,8 +19,8 @@ class MyApp : public FastOS_Application
private:
bool _rc;
uint32_t _cnt;
- search::docsummary::ResultConfig _config;
- search::docsummary::ResultPacker _packer;
+ ResultConfig _config;
+ ResultPacker _packer;
public:
MyApp();
@@ -33,33 +32,18 @@ public:
{ ReportTestResult(line, rc); return rc; }
// compare runtime info (,but ignore result class)
- bool Equal(search::docsummary::ResEntry *a, search::docsummary::ResEntry *b);
- bool Equal(search::docsummary::GeneralResult *a, search::docsummary::GeneralResult *b);
-
- void TestFieldIndex(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, int idx);
-
- void TestIntValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, uint32_t value);
-
- void TestDoubleValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, double value);
+ bool Equal(ResEntry *a, ResEntry *b);
+ bool Equal(GeneralResult *a, GeneralResult *b);
- void TestInt64Value(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, uint64_t value);
+ void TestIntValue(uint32_t line, GeneralResult *gres, const char *field, uint32_t value);
+ void TestDoubleValue(uint32_t line, GeneralResult *gres, const char *field, double value);
+ void TestInt64Value(uint32_t line, GeneralResult *gres, const char *field, uint64_t value);
+ void TestStringValue(uint32_t line, GeneralResult *gres, const char *field, const char *value);
+ void TestDataValue(uint32_t line, GeneralResult *gres, const char *field, const char *value);
- void TestStringValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, const char *value);
-
- void TestDataValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, const char *value);
-
- void TestBasic();
void TestFailLong();
void TestFailShort();
void TestFailOrder();
- void TestCompress();
- void TestCompat();
void TestBasicInplace();
void TestCompressInplace();
@@ -72,7 +56,8 @@ MyApp::MyApp()
_config(),
_packer(&_config)
{}
-MyApp::~MyApp() {}
+
+MyApp::~MyApp() = default;
void
MyApp::ReportTestResult(uint32_t line, bool rc)
@@ -89,7 +74,7 @@ MyApp::ReportTestResult(uint32_t line, bool rc)
bool
-MyApp::Equal(search::docsummary::ResEntry *a, search::docsummary::ResEntry *b)
+MyApp::Equal(ResEntry *a, ResEntry *b)
{
if (a->_type != b->_type)
return false;
@@ -106,7 +91,7 @@ MyApp::Equal(search::docsummary::ResEntry *a, search::docsummary::ResEntry *b)
bool
-MyApp::Equal(search::docsummary::GeneralResult *a, search::docsummary::GeneralResult *b)
+MyApp::Equal(GeneralResult *a, GeneralResult *b)
{
uint32_t numEntries = a->GetClass()->GetNumEntries();
@@ -125,56 +110,36 @@ MyApp::Equal(search::docsummary::GeneralResult *a, search::docsummary::GeneralRe
return true;
}
-
-void
-MyApp::TestFieldIndex(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, int idx)
-{
- bool rc = (gres != NULL &&
- gres->GetClass()->GetIndexFromName(field) == idx);
-
- RTR(line, rc);
-}
-
-
void
-MyApp::TestIntValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, uint32_t value)
+MyApp::TestIntValue(uint32_t line, GeneralResult *gres, const char *field, uint32_t value)
{
- search::docsummary::ResEntry *entry
- = (gres != NULL) ? gres->GetEntry(field) : NULL;
+ ResEntry *entry = (gres != nullptr) ? gres->GetEntry(field) : nullptr;
- bool rc = (entry != NULL &&
+ bool rc = (entry != nullptr &&
entry->_type == RES_INT &&
entry->_intval == value);
RTR(line, rc);
}
-
void
-MyApp::TestDoubleValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, double value)
+MyApp::TestDoubleValue(uint32_t line, GeneralResult *gres, const char *field, double value)
{
- search::docsummary::ResEntry *entry
- = (gres != NULL) ? gres->GetEntry(field) : NULL;
+ ResEntry *entry = (gres != nullptr) ? gres->GetEntry(field) : nullptr;
- bool rc = (entry != NULL &&
+ bool rc = (entry != nullptr &&
entry->_type == RES_DOUBLE &&
entry->_doubleval == value);
RTR(line, rc);
}
-
void
-MyApp::TestInt64Value(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, uint64_t value)
+MyApp::TestInt64Value(uint32_t line, GeneralResult *gres, const char *field, uint64_t value)
{
- search::docsummary::ResEntry *entry
- = (gres != NULL) ? gres->GetEntry(field) : NULL;
+ ResEntry *entry = (gres != nullptr) ? gres->GetEntry(field) : nullptr;
- bool rc = (entry != NULL &&
+ bool rc = (entry != nullptr &&
entry->_type == RES_INT64 &&
entry->_int64val == value);
@@ -183,36 +148,29 @@ MyApp::TestInt64Value(uint32_t line, search::docsummary::GeneralResult *gres,
void
-MyApp::TestStringValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, const char *value)
+MyApp::TestStringValue(uint32_t line, GeneralResult *gres, const char *field, const char *value)
{
- search::docsummary::ResEntry *entry
- = (gres != NULL) ? gres->GetEntry(field) : NULL;
+ ResEntry *entry = (gres != nullptr) ? gres->GetEntry(field) : nullptr;
- bool rc = (entry != NULL &&
+ bool rc = (entry != nullptr &&
entry->_type == RES_STRING &&
entry->_stringlen == strlen(value) &&
strncmp(entry->_stringval, value, entry->_stringlen) == 0);
- if (!rc && entry != NULL) {
- LOG(warning,
- "string value '%.*s' != '%s'",
- (int) entry->_stringlen,
- entry->_stringval, value);
+ if (!rc && entry != nullptr) {
+ LOG(warning,"string value '%.*s' != '%s'",
+ (int) entry->_stringlen, entry->_stringval, value);
}
RTR(line, rc);
}
-
void
-MyApp::TestDataValue(uint32_t line, search::docsummary::GeneralResult *gres,
- const char *field, const char *value)
+MyApp::TestDataValue(uint32_t line, GeneralResult *gres, const char *field, const char *value)
{
- search::docsummary::ResEntry *entry
- = (gres != NULL) ? gres->GetEntry(field) : NULL;
+ ResEntry *entry = (gres != nullptr) ? gres->GetEntry(field) : nullptr;
- bool rc = (entry != NULL &&
+ bool rc = (entry != nullptr &&
entry->_type == RES_DATA &&
entry->_datalen == strlen(value) &&
strncmp(entry->_dataval, value, entry->_datalen) == 0);
@@ -220,62 +178,6 @@ MyApp::TestDataValue(uint32_t line, search::docsummary::GeneralResult *gres,
RTR(line, rc);
}
-
-void
-MyApp::TestBasic()
-{
- const char *buf;
- uint32_t buflen;
-
- search::docsummary::urlresult *res;
- search::docsummary::GeneralResult *gres;
-
- uint32_t intval = 4;
- uint16_t shortval = 2;
- uint8_t byteval = 1;
- float floatval = 4.5;
- double doubleval = 8.75;
- uint64_t int64val = 8;
- const char *strval = "This is a string";
- const char *datval = "This is data";
- const char *lstrval = "This is a long string";
- const char *ldatval = "This is long data";
-
- RTR(__LINE__, _packer.Init(0));
- RTR(__LINE__, _packer.AddInteger(intval));
- RTR(__LINE__, _packer.AddShort(shortval));
- RTR(__LINE__, _packer.AddByte(byteval));
- RTR(__LINE__, _packer.AddFloat(floatval));
- RTR(__LINE__, _packer.AddDouble(doubleval));
- RTR(__LINE__, _packer.AddInt64(int64val));
- RTR(__LINE__, _packer.AddString(strval, strlen(strval)));
- RTR(__LINE__, _packer.AddData(datval, strlen(datval)));
- RTR(__LINE__, _packer.AddLongString(lstrval, strlen(lstrval)));
- RTR(__LINE__, _packer.AddLongData(ldatval, strlen(ldatval)));
- RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
-
- res = _config.Unpack(0, 0, 0, buf, buflen);
- gres = res->IsGeneral() ? (search::docsummary::GeneralResult *) res : NULL;
-
- RTR(__LINE__, gres != NULL);
- TestIntValue (__LINE__, gres, "integer", 4);
- TestIntValue (__LINE__, gres, "short", 2);
- TestIntValue (__LINE__, gres, "byte", 1);
- TestDoubleValue(__LINE__, gres, "float", floatval);
- TestDoubleValue(__LINE__, gres, "double", doubleval);
- TestInt64Value (__LINE__, gres, "int64", int64val);
- TestStringValue(__LINE__, gres, "string", strval);
- TestDataValue (__LINE__, gres, "data", datval);
- TestStringValue(__LINE__, gres, "longstring", lstrval);
- TestDataValue (__LINE__, gres, "longdata", ldatval);
- RTR(__LINE__, (gres != NULL &&
- gres->GetClass()->GetNumEntries() == 10));
- RTR(__LINE__, (gres != NULL &&
- gres->GetClass()->GetClassID() == 0));
- delete res;
-}
-
-
void
MyApp::TestFailLong()
{
@@ -308,7 +210,6 @@ MyApp::TestFailLong()
RTR(__LINE__, !_packer.GetDocsumBlob(&buf, &buflen));
}
-
void
MyApp::TestFailShort()
{
@@ -371,95 +272,6 @@ MyApp::TestFailOrder()
}
-void
-MyApp::TestCompress()
-{
- const char *buf;
- uint32_t buflen;
-
- search::docsummary::urlresult *res;
- search::docsummary::GeneralResult *gres;
-
- const char *lstrval = "string string string";
- const char *ldatval = "data data data";
-
- RTR(__LINE__, _packer.Init(2));
- RTR(__LINE__, _packer.AddLongString(lstrval, strlen(lstrval)));
- RTR(__LINE__, _packer.AddLongData(ldatval, strlen(ldatval)));
- RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
-
- res = _config.Unpack(0, 0, 0, buf, buflen);
- gres = res->IsGeneral() ? (search::docsummary::GeneralResult *) res : NULL;
-
- RTR(__LINE__, gres != NULL);
- TestStringValue(__LINE__, gres, "text", lstrval);
- TestDataValue (__LINE__, gres, "data", ldatval);
- RTR(__LINE__, (gres != NULL &&
- gres->GetClass()->GetNumEntries() == 2));
- RTR(__LINE__, (gres != NULL &&
- gres->GetClass()->GetClassID() == 2));
- delete res;
-}
-
-
-void
-MyApp::TestCompat()
-{
- const char *buf;
- uint32_t buflen;
-
- search::docsummary::urlresult *res1;
- search::docsummary::GeneralResult *gres1;
-
- search::docsummary::urlresult *res2;
- search::docsummary::GeneralResult *gres2;
-
- const char *strval = "string string string string";
- const char *datval = "data data data data";
-
- RTR(__LINE__, _packer.Init(1));
- RTR(__LINE__, _packer.AddData(strval, strlen(strval)));
- RTR(__LINE__, _packer.AddString(datval, strlen(datval)));
- RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
- res1 = _config.Unpack(0, 0, 0, buf, buflen);
- gres1 = res1->IsGeneral() ? (search::docsummary::GeneralResult *) res1 : NULL;
-
- RTR(__LINE__, _packer.Init(2));
- RTR(__LINE__, _packer.AddLongData(strval, strlen(strval)));
- RTR(__LINE__, _packer.AddLongString(datval, strlen(datval)));
- RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
- res2 = _config.Unpack(0, 0, 0, buf, buflen);
- gres2 = res2->IsGeneral() ? (search::docsummary::GeneralResult *) res2 : NULL;
-
- RTR(__LINE__, gres1 != NULL);
- RTR(__LINE__, gres2 != NULL);
-
- TestStringValue(__LINE__, gres1, "text", strval);
- TestDataValue (__LINE__, gres1, "data", datval);
- TestFieldIndex (__LINE__, gres1, "text", 0);
- TestFieldIndex (__LINE__, gres1, "data", 1);
- RTR(__LINE__, (gres1 != NULL &&
- gres1->GetClass()->GetNumEntries() == 2));
-
- TestStringValue(__LINE__, gres2, "text", strval);
- TestDataValue (__LINE__, gres2, "data", datval);
- TestFieldIndex (__LINE__, gres2, "text", 0);
- TestFieldIndex (__LINE__, gres2, "data", 1);
- RTR(__LINE__, (gres2 != NULL &&
- gres2->GetClass()->GetNumEntries() == 2));
-
- RTR(__LINE__, (gres1 != NULL &&
- gres1->GetClass()->GetClassID() == 1));
- RTR(__LINE__, (gres2 != NULL &&
- gres2->GetClass()->GetClassID() == 2));
-
- RTR(__LINE__, (gres1 != NULL && gres2 != NULL &&
- Equal(gres1, gres2)));
-
- delete res1;
- delete res2;
-}
-
void
MyApp::TestBasicInplace()
@@ -467,8 +279,8 @@ MyApp::TestBasicInplace()
const char *buf;
uint32_t buflen;
- const search::docsummary::ResultClass *resClass;
- search::docsummary::GeneralResult *gres;
+ const ResultClass *resClass;
+ GeneralResult *gres;
uint32_t intval = 4;
uint16_t shortval = 2;
@@ -495,18 +307,18 @@ MyApp::TestBasicInplace()
RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
resClass = _config.LookupResultClass(_config.GetClassID(buf, buflen));
- if (resClass == NULL) {
- gres = NULL;
+ if (resClass == nullptr) {
+ gres = nullptr;
} else {
DocsumStoreValue value(buf, buflen);
- gres = new search::docsummary::GeneralResult(resClass, 0, 0, 0);
+ gres = new GeneralResult(resClass);
if (!gres->inplaceUnpack(value)) {
delete gres;
- gres = NULL;
+ gres = nullptr;
}
}
- RTR(__LINE__, gres != NULL);
+ RTR(__LINE__, gres != nullptr);
TestIntValue (__LINE__, gres, "integer", 4);
TestIntValue (__LINE__, gres, "short", 2);
TestIntValue (__LINE__, gres, "byte", 1);
@@ -517,9 +329,9 @@ MyApp::TestBasicInplace()
TestDataValue (__LINE__, gres, "data", datval);
TestStringValue(__LINE__, gres, "longstring", lstrval);
TestDataValue (__LINE__, gres, "longdata", ldatval);
- RTR(__LINE__, (gres != NULL &&
+ RTR(__LINE__, (gres != nullptr &&
gres->GetClass()->GetNumEntries() == 10));
- RTR(__LINE__, (gres != NULL &&
+ RTR(__LINE__, (gres != nullptr &&
gres->GetClass()->GetClassID() == 0));
delete gres;
}
@@ -533,8 +345,8 @@ MyApp::TestCompressInplace()
search::RawBuf field1(32768);
search::RawBuf field2(32768);
- const search::docsummary::ResultClass *resClass;
- search::docsummary::GeneralResult *gres;
+ const ResultClass *resClass;
+ GeneralResult *gres;
const char *lstrval = "string string string";
const char *ldatval = "data data data";
@@ -545,48 +357,46 @@ MyApp::TestCompressInplace()
RTR(__LINE__, _packer.GetDocsumBlob(&buf, &buflen));
resClass = _config.LookupResultClass(_config.GetClassID(buf, buflen));
- if (resClass == NULL) {
- gres = NULL;
+ if (resClass == nullptr) {
+ gres = nullptr;
} else {
DocsumStoreValue value(buf, buflen);
- gres = new search::docsummary::GeneralResult(resClass, 0, 0, 0);
+ gres = new GeneralResult(resClass);
if (!gres->inplaceUnpack(value)) {
delete gres;
- gres = NULL;
+ gres = nullptr;
}
}
- search::docsummary::ResEntry *e1 = (gres == NULL) ? NULL : gres->GetEntry("text");
- search::docsummary::ResEntry *e2 = (gres == NULL) ? NULL : gres->GetEntry("data");
+ ResEntry *e1 = (gres == nullptr) ? nullptr : gres->GetEntry("text");
+ ResEntry *e2 = (gres == nullptr) ? nullptr : gres->GetEntry("data");
- if (e1 != NULL)
+ if (e1 != nullptr)
e1->_extract_field(&field1);
- if (e2 != NULL)
+ if (e2 != nullptr)
e2->_extract_field(&field2);
- RTR(__LINE__, gres != NULL);
- RTR(__LINE__, e1 != NULL);
- RTR(__LINE__, e2 != NULL);
+ RTR(__LINE__, gres != nullptr);
+ RTR(__LINE__, e1 != nullptr);
+ RTR(__LINE__, e2 != nullptr);
RTR(__LINE__, strcmp(field1.GetDrainPos(), lstrval) == 0);
RTR(__LINE__, strcmp(field2.GetDrainPos(), ldatval) == 0);
RTR(__LINE__, strlen(lstrval) == field1.GetUsedLen());
RTR(__LINE__, strlen(ldatval) == field2.GetUsedLen());
- RTR(__LINE__, (gres != NULL &&
+ RTR(__LINE__, (gres != nullptr &&
gres->GetClass()->GetNumEntries() == 2));
- RTR(__LINE__, (gres != NULL &&
+ RTR(__LINE__, (gres != nullptr &&
gres->GetClass()->GetClassID() == 2));
delete gres;
}
-
-
int
MyApp::Main()
{
_rc = true;
_cnt = 0;
- search::docsummary::ResultClass *resClass;
+ ResultClass *resClass;
resClass = _config.AddResultClass("c0", 0);
resClass->AddConfigEntry("integer", RES_INT);
@@ -608,12 +418,9 @@ MyApp::Main()
resClass->AddConfigEntry("text", RES_LONG_STRING);
resClass->AddConfigEntry("data", RES_LONG_DATA);
- TestBasic();
TestFailLong();
TestFailShort();
TestFailOrder();
- TestCompress();
- TestCompat();
TestBasicInplace();
TestCompressInplace();
@@ -621,7 +428,6 @@ MyApp::Main()
return (_rc ? 0 : 1);
}
-
int
main(int argc, char **argv)
{
diff --git a/searchsummary/src/tests/docsummary/positionsdfw_test.cpp b/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
index 7497a66d138..764ff4723cb 100644
--- a/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
+++ b/searchsummary/src/tests/docsummary/positionsdfw_test.cpp
@@ -24,8 +24,7 @@ using search::attribute::IAttributeFunctor;
using vespalib::string;
using std::vector;
-namespace search {
-namespace docsummary {
+namespace search::docsummary {
namespace {
@@ -136,7 +135,7 @@ void checkWritePositionField(Test &test, AttrType &attr,
vespalib::Slime target;
vespalib::slime::SlimeInserter inserter(target);
- writer->insertField(doc_id, nullptr, &state, res_type, inserter);
+ writer->insertField(doc_id, &state, res_type, inserter);
vespalib::Memory got = target.get().asString();
test.EXPECT_EQUAL(expected.size(), got.size);
@@ -154,7 +153,6 @@ void Test::requireThat2DPositionFieldIsWritten() {
}
} // namespace
-} // namespace docsummary
-} // namespace search
+}
TEST_APPHOOK(search::docsummary::Test);
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp
index 015eb70c74a..153bcea886f 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp
@@ -90,7 +90,7 @@ StructFields::~StructFields() = default;
}
AttributeCombinerDFW::AttributeCombinerDFW(const vespalib::string &fieldName)
- : IDocsumFieldWriter(),
+ : IDocsumFW(),
_stateIndex(0),
_fieldName(fieldName)
{
@@ -124,11 +124,7 @@ AttributeCombinerDFW::create(const vespalib::string &fieldName, IAttributeManage
}
void
-AttributeCombinerDFW::insertField(uint32_t docid,
- GeneralResult *,
- GetDocsumsState *state,
- ResType,
- vespalib::slime::Inserter &target)
+AttributeCombinerDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType, vespalib::slime::Inserter &target)
{
auto &fieldWriterState = state->_fieldWriterStates[_stateIndex];
if (!fieldWriterState) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h
index ef54522a923..e58b0f740bf 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h
@@ -15,7 +15,7 @@ class DynamicDocsumWriter;
* This class reads values from multiple struct field attributes and
* inserts them as an array of struct or a map of struct.
*/
-class AttributeCombinerDFW : public IDocsumFieldWriter
+class AttributeCombinerDFW : public IDocsumFW
{
protected:
uint32_t _stateIndex;
@@ -28,8 +28,7 @@ public:
bool IsGenerated() const override;
bool setFieldWriterStateIndex(uint32_t fieldWriterStateIndex) override;
static std::unique_ptr<IDocsumFieldWriter> create(const vespalib::string &fieldName, IAttributeManager &attrMgr);
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target) override;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) override;
};
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
index ee2e9d9fa92..19dd096c46c 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
@@ -21,6 +21,7 @@ using namespace search;
using search::attribute::IAttributeContext;
using search::attribute::IAttributeVector;
using search::attribute::BasicType;
+using vespalib::slime::Inserter;
namespace search::docsummary {
@@ -42,12 +43,8 @@ public:
SingleAttrDFW(const vespalib::string & attrName) :
AttrDFW(attrName)
{ }
- virtual void insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
- vespalib::slime::Inserter &target) override;
- virtual bool isDefaultValue(uint32_t docid, const GetDocsumsState * state) const override;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, Inserter &target) override;
+ bool isDefaultValue(uint32_t docid, const GetDocsumsState * state) const override;
};
bool SingleAttrDFW::isDefaultValue(uint32_t docid, const GetDocsumsState * state) const
@@ -56,11 +53,7 @@ bool SingleAttrDFW::isDefaultValue(uint32_t docid, const GetDocsumsState * state
}
void
-SingleAttrDFW::insertField(uint32_t docid,
- GeneralResult *,
- GetDocsumsState * state,
- ResType type,
- vespalib::slime::Inserter &target)
+SingleAttrDFW::insertField(uint32_t docid, GetDocsumsState * state, ResType type, Inserter &target)
{
const char *s="";
const IAttributeVector & v = vec(*state);
@@ -80,6 +73,11 @@ SingleAttrDFW::insertField(uint32_t docid,
target.insertLong(val);
break;
}
+ case RES_BOOL: {
+ uint8_t val = v.getInt(docid);
+ target.insertBool(val != 0);
+ break;
+ }
case RES_FLOAT: {
float val = v.getFloat(docid);
target.insertDouble(val);
@@ -141,20 +139,12 @@ class MultiAttrDFW : public AttrDFW
{
public:
MultiAttrDFW(const vespalib::string & attrName) : AttrDFW(attrName) {}
- virtual void insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
- vespalib::slime::Inserter &target) override;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, Inserter &target) override;
};
void
-MultiAttrDFW::insertField(uint32_t docid,
- GeneralResult *,
- GetDocsumsState *state,
- ResType,
- vespalib::slime::Inserter &target)
+MultiAttrDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType, Inserter &target)
{
using vespalib::slime::Cursor;
using vespalib::Memory;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.h b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.h
index 643170663b8..d8102734452 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.h
@@ -8,7 +8,7 @@ namespace search::attribute { class IAttributeVector; }
namespace search::docsummary {
-class AttrDFW : public IDocsumFieldWriter
+class AttrDFW : public IDocsumFW
{
private:
vespalib::string _attrName;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
index 18e7e471663..f24f5841bd2 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
@@ -29,22 +29,16 @@ IDocsumFieldWriter::setFieldWriterStateIndex(uint32_t)
//--------------------------------------------------------------------------
-EmptyDFW::EmptyDFW() { }
+EmptyDFW::EmptyDFW() = default;
-
-EmptyDFW::~EmptyDFW() { }
+EmptyDFW::~EmptyDFW() = default;
void
-EmptyDFW::insertField(uint32_t /*docid*/,
- GeneralResult *,
- GetDocsumsState *,
- ResType,
- vespalib::slime::Inserter &target)
+EmptyDFW::insertField(uint32_t, GetDocsumsState *, ResType, vespalib::slime::Inserter &target)
{
// insert explicitly-empty field?
// target.insertNix();
(void)target;
- return;
}
//--------------------------------------------------------------------------
@@ -54,11 +48,7 @@ CopyDFW::CopyDFW()
{
}
-
-CopyDFW::~CopyDFW()
-{
-}
-
+CopyDFW::~CopyDFW() = default;
bool
CopyDFW::Init(const ResultConfig & config, const char *inputField)
@@ -69,11 +59,10 @@ CopyDFW::Init(const ResultConfig & config, const char *inputField)
LOG(warning, "no docsum format contains field '%s'; copied fields will be empty", inputField);
}
- for (ResultConfig::const_iterator it(config.begin()), mt(config.end()); it != mt; it++) {
- const ResConfigEntry *entry =
- it->GetEntry(it->GetIndexFromEnumValue(_inputFieldEnumValue));
+ for (const auto & field : config) {
+ const ResConfigEntry *entry = field.GetEntry(field.GetIndexFromEnumValue(_inputFieldEnumValue));
- if (entry != NULL &&
+ if (entry != nullptr &&
!IsRuntimeCompatible(entry->_type, RES_INT) &&
!IsRuntimeCompatible(entry->_type, RES_DOUBLE) &&
!IsRuntimeCompatible(entry->_type, RES_INT64) &&
@@ -81,25 +70,21 @@ CopyDFW::Init(const ResultConfig & config, const char *inputField)
!IsRuntimeCompatible(entry->_type, RES_DATA)) {
LOG(warning, "cannot use docsum field '%s' as input to copy; type conflict with result class %d (%s)",
- inputField, it->GetClassID(), it->GetClassName());
+ inputField, field.GetClassID(), field.GetClassName());
return false;
}
}
return true;
}
-
void
-CopyDFW::insertField(uint32_t /*docid*/,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
- vespalib::slime::Inserter &target)
+CopyDFW::insertField(uint32_t /*docid*/, GeneralResult *gres, GetDocsumsState *state, ResType type,
+ vespalib::slime::Inserter &target)
{
int idx = gres->GetClass()->GetIndexFromEnumValue(_inputFieldEnumValue);
ResEntry *entry = gres->GetEntry(idx);
- if (entry != NULL &&
+ if (entry != nullptr &&
IsRuntimeCompatible(entry->_type, type))
{
switch (type) {
@@ -117,6 +102,9 @@ CopyDFW::insertField(uint32_t /*docid*/,
uint8_t val8 = entry->_intval;
target.insertLong(val8);
break; }
+ case RES_BOOL: {
+ target.insertBool(entry->_intval != 0);
+ break; }
case RES_FLOAT: {
float valfloat = entry->_doubleval;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
index 51079f7736e..870e4fa8d83 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
@@ -16,7 +16,7 @@ class GetDocsumsState;
class IDocsumFieldWriter
{
public:
- typedef std::unique_ptr<IDocsumFieldWriter> UP;
+ using UP = std::unique_ptr<IDocsumFieldWriter>;
IDocsumFieldWriter() : _index(0) { }
virtual ~IDocsumFieldWriter() {}
@@ -27,10 +27,7 @@ public:
{ return ResultConfig::IsRuntimeCompatible(a, b); }
virtual bool IsGenerated() const = 0;
- virtual void insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
+ virtual void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state, ResType type,
vespalib::slime::Inserter &target) = 0;
virtual const vespalib::string & getAttributeName() const { return _empty; }
virtual bool isDefaultValue(uint32_t docid, const GetDocsumsState * state) const {
@@ -46,20 +43,27 @@ private:
static const vespalib::string _empty;
};
+class IDocsumFW : public IDocsumFieldWriter
+{
+public:
+ virtual void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) = 0;
+ void insertField(uint32_t docid, GeneralResult *, GetDocsumsState *state, ResType type,
+ vespalib::slime::Inserter &target) override
+ {
+ insertField(docid, state, type, target);
+ }
+};
+
//--------------------------------------------------------------------------
-class EmptyDFW : public IDocsumFieldWriter
+class EmptyDFW : public IDocsumFW
{
public:
EmptyDFW();
- virtual ~EmptyDFW();
-
- virtual bool IsGenerated() const override { return true; }
- virtual void insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
- vespalib::slime::Inserter &target) override;
+ ~EmptyDFW() override;
+
+ bool IsGenerated() const override { return true; }
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) override;
};
//--------------------------------------------------------------------------
@@ -71,16 +75,13 @@ private:
public:
CopyDFW();
- virtual ~CopyDFW();
+ ~CopyDFW() override;
bool Init(const ResultConfig & config, const char *inputField);
- virtual bool IsGenerated() const override { return false; }
- virtual void insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType type,
- vespalib::slime::Inserter &target) override;
+ bool IsGenerated() const override { return false; }
+ void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state, ResType type,
+ vespalib::slime::Inserter &target) override;
};
//--------------------------------------------------------------------------
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
index 722ea9d9000..a188ab6e60a 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
@@ -78,23 +78,23 @@ DynamicDocsumWriter::resolveInputClass(ResolveClassInfo &rci, uint32_t id) const
}
}
-static void convertEntry(GetDocsumsState *state,
- const ResConfigEntry *resCfg,
- const ResEntry *entry,
- Inserter &inserter,
- Slime &slime)
+static void convertEntry(GetDocsumsState *state, const ResConfigEntry *resCfg, const ResEntry *entry,
+ Inserter &inserter, Slime &slime)
{
using vespalib::slime::BinaryFormat;
const char *ptr;
uint32_t len;
- LOG_ASSERT(resCfg != 0 && entry != 0);
+ LOG_ASSERT(resCfg != nullptr && entry != nullptr);
switch (resCfg->_type) {
case RES_INT:
case RES_SHORT:
case RES_BYTE:
inserter.insertLong(entry->_intval);
break;
+ case RES_BOOL:
+ inserter.insertBool(entry->_intval != 0);
+ break;
case RES_FLOAT:
case RES_DOUBLE:
inserter.insertDouble(entry->_doubleval);
@@ -130,12 +130,8 @@ static void convertEntry(GetDocsumsState *state,
void
-DynamicDocsumWriter::insertDocsum(const ResolveClassInfo & rci,
- uint32_t docid,
- GetDocsumsState *state,
- IDocsumStore *docinfos,
- vespalib::Slime & slime,
- vespalib::slime::Inserter & topInserter)
+DynamicDocsumWriter::insertDocsum(const ResolveClassInfo & rci, uint32_t docid, GetDocsumsState *state,
+ IDocsumStore *docinfos, vespalib::Slime & slime, vespalib::slime::Inserter & topInserter)
{
if (rci.allGenerated) {
// generate docsum entry on-the-fly
@@ -144,8 +140,7 @@ DynamicDocsumWriter::insertDocsum(const ResolveClassInfo & rci,
const ResConfigEntry *resCfg = rci.outputClass->GetEntry(i);
IDocsumFieldWriter *writer = _overrideTable[resCfg->_enumValue];
if (! writer->isDefaultValue(docid, state)) {
- const Memory field_name(resCfg->_bindname.data(),
- resCfg->_bindname.size());
+ const Memory field_name(resCfg->_bindname.data(), resCfg->_bindname.size());
ObjectInserter inserter(docsum, field_name);
writer->insertField(docid, nullptr, state, resCfg->_type, inserter);
}
@@ -154,7 +149,7 @@ DynamicDocsumWriter::insertDocsum(const ResolveClassInfo & rci,
// look up docsum entry
DocsumStoreValue value = docinfos->getMappedDocsum(docid);
// re-pack docsum blob
- GeneralResult gres(rci.inputClass, 0, docid, 0);
+ GeneralResult gres(rci.inputClass);
if (! gres.inplaceUnpack(value)) {
LOG(debug, "Unpack failed: illegal docsum entry for document %d. This is expected during lidspace compaction.", docid);
topInserter.insertNix();
@@ -272,10 +267,10 @@ DynamicDocsumWriter::Override(const char *fieldName, IDocsumFieldWriter *writer)
++_numFieldWriterStates;
}
- for (ResultConfig::iterator it(_resultConfig->begin()), mt(_resultConfig->end()); it != mt; it++) {
+ for (auto & entry : *_resultConfig) {
- if (it->GetIndexFromEnumValue(fieldEnumValue) >= 0) {
- ResultClass::DynamicInfo *info = it->getDynamicInfo();
+ if (entry.GetIndexFromEnumValue(fieldEnumValue) >= 0) {
+ ResultClass::DynamicInfo *info = entry.getDynamicInfo();
info->_overrideCnt++;
if (writer->IsGenerated())
info->_generateCnt++;
@@ -306,15 +301,11 @@ DynamicDocsumWriter::InitState(IAttributeManager & attrMan, GetDocsumsState *sta
uint32_t
-DynamicDocsumWriter::WriteDocsum(uint32_t docid,
- GetDocsumsState *state,
- IDocsumStore *docinfos,
- search::RawBuf *target)
+DynamicDocsumWriter::WriteDocsum(uint32_t docid, GetDocsumsState *state, IDocsumStore *docinfos, search::RawBuf *target)
{
vespalib::Slime slime;
vespalib::slime::SlimeInserter inserter(slime);
- ResolveClassInfo rci = resolveClassInfo(state->_args.getResultClassName(),
- docinfos->getSummaryClassId());
+ ResolveClassInfo rci = resolveClassInfo(state->_args.getResultClassName(), docinfos->getSummaryClassId());
insertDocsum(rci, docid, state, docinfos, slime, inserter);
return slime2RawBuf(slime, *target);
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
index 4112afc1895..b9177ac8782 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
@@ -23,7 +23,7 @@ struct ExplicitItemData
uint32_t _weight;
ExplicitItemData()
- : _index(NULL), _indexlen(0), _term(NULL), _termlen(0), _weight(0)
+ : _index(nullptr), _indexlen(0), _term(nullptr), _termlen(0), _weight(0)
{}
ExplicitItemData(const char *index, uint32_t indexlen, const char* term,
@@ -43,19 +43,16 @@ struct QueryItem
{
search::SimpleQueryStackDumpIterator *_si;
const ExplicitItemData *_data;
- QueryItem() : _si(NULL), _data(NULL) {}
- QueryItem(search::SimpleQueryStackDumpIterator *si) : _si(si), _data(NULL) {}
- QueryItem(ExplicitItemData *data) : _si(NULL), _data(data) {}
+ QueryItem() : _si(nullptr), _data(nullptr) {}
+ QueryItem(search::SimpleQueryStackDumpIterator *si) : _si(si), _data(nullptr) {}
+ QueryItem(ExplicitItemData *data) : _si(nullptr), _data(data) {}
private:
QueryItem(const QueryItem&);
QueryItem& operator= (const QueryItem&);
};
-};
-
-namespace search {
-class Property;
+}
-namespace fef {
+namespace search::fef {
class TermVisitor : public IPropertiesVisitor
{
public:
@@ -104,24 +101,22 @@ TermVisitor::visitProperty(const Property::Value &key, const Property &values)
}
-namespace docsummary {
+namespace search::docsummary {
class JuniperQueryAdapter : public juniper::IQuery
{
private:
- JuniperQueryAdapter(const JuniperQueryAdapter&);
- JuniperQueryAdapter operator= (const JuniperQueryAdapter&);
-
KeywordExtractor *_kwExtractor;
const vespalib::stringref _buf;
const search::fef::Properties *_highlightTerms;
juniper::IQueryVisitor *_visitor;
public:
- JuniperQueryAdapter(KeywordExtractor *kwExtractor,
- vespalib::stringref buf,
- const search::fef::Properties *highlightTerms = NULL)
- : _kwExtractor(kwExtractor), _buf(buf), _highlightTerms(highlightTerms), _visitor(NULL) {}
+ JuniperQueryAdapter(const JuniperQueryAdapter&) = delete;
+ JuniperQueryAdapter operator= (const JuniperQueryAdapter&) = delete;
+ JuniperQueryAdapter(KeywordExtractor *kwExtractor, vespalib::stringref buf,
+ const search::fef::Properties *highlightTerms = nullptr)
+ : _kwExtractor(kwExtractor), _buf(buf), _highlightTerms(highlightTerms), _visitor(nullptr) {}
// TODO: put this functionality into the stack dump iterator
bool SkipItem(search::SimpleQueryStackDumpIterator *iterator) const
@@ -136,28 +131,28 @@ public:
return true;
}
- virtual bool Traverse(juniper::IQueryVisitor *v) const override;
+ bool Traverse(juniper::IQueryVisitor *v) const override;
- virtual int Weight(const juniper::QueryItem* item) const override
+ int Weight(const juniper::QueryItem* item) const override
{
- if (item->_si != NULL) {
+ if (item->_si != nullptr) {
return item->_si->GetWeight().percent();
} else {
return item->_data->_weight;
}
}
- virtual juniper::ItemCreator Creator(const juniper::QueryItem* item) const override
+ juniper::ItemCreator Creator(const juniper::QueryItem* item) const override
{
// cast master: Knut Omang
- if (item->_si != NULL) {
+ if (item->_si != nullptr) {
return (juniper::ItemCreator) item->_si->getCreator();
} else {
return juniper::CREA_ORIG;
}
}
- virtual const char *Index(const juniper::QueryItem* item, size_t *len) const override
+ const char *Index(const juniper::QueryItem* item, size_t *len) const override
{
- if (item->_si != NULL) {
+ if (item->_si != nullptr) {
*len = item->_si->getIndexName().size();
return item->_si->getIndexName().data();
} else {
@@ -166,14 +161,14 @@ public:
}
}
- virtual bool UsefulIndex(const juniper::QueryItem* item) const override
+ bool UsefulIndex(const juniper::QueryItem* item) const override
{
vespalib::stringref index;
- if (_kwExtractor == NULL)
+ if (_kwExtractor == nullptr)
return true;
- if (item->_si != NULL) {
+ if (item->_si != nullptr) {
index = item->_si->getIndexName();
} else {
index = vespalib::stringref(item->_data->_index, item->_data->_indexlen);
@@ -182,8 +177,6 @@ public:
}
};
-
-
bool
JuniperQueryAdapter::Traverse(juniper::IQueryVisitor *v) const
{
@@ -308,7 +301,7 @@ JuniperDFW::JuniperDFW(juniper::Juniper * juniper)
}
-JuniperDFW::~JuniperDFW() { }
+JuniperDFW::~JuniperDFW() = default;
bool
JuniperDFW::Init(
@@ -319,10 +312,10 @@ JuniperDFW::Init(
{
bool rc = true;
const util::StringEnum & enums(config.GetFieldNameEnum());
- if (langFieldName != NULL)
+ if (langFieldName != nullptr)
_langFieldEnumValue = enums.Lookup(langFieldName);
_juniperConfig = _juniper->CreateConfig(fieldName);
- if (_juniperConfig.get() == NULL) {
+ if (_juniperConfig.get() == nullptr) {
LOG(warning, "could not create juniper config for field '%s'", fieldName);
rc = false;
}
@@ -350,7 +343,7 @@ JuniperTeaserDFW::Init(
const ResConfigEntry *entry =
it->GetEntry(it->GetIndexFromEnumValue(_inputFieldEnumValue));
- if (entry != NULL &&
+ if (entry != nullptr &&
!IsRuntimeCompatible(entry->_type, RES_STRING) &&
!IsRuntimeCompatible(entry->_type, RES_DATA))
{
@@ -363,15 +356,13 @@ JuniperTeaserDFW::Init(
}
vespalib::string
-DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state)
+DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid, GeneralResult *gres, GetDocsumsState *state)
{
- if (state->_dynteaser._query == NULL) {
+ if (state->_dynteaser._query == nullptr) {
JuniperQueryAdapter iq(state->_kwExtractor,
state->_args.getStackDump(),
&state->_args.highlightTerms());
- state->_dynteaser._query = _juniper->CreateQueryHandle(iq, NULL);
+ state->_dynteaser._query = _juniper->CreateQueryHandle(iq, nullptr);
}
if (docid != state->_dynteaser._docid ||
@@ -384,34 +375,31 @@ DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid,
_langFieldEnumValue, state->_dynteaser._lang,
(juniper::AnalyseCompatible(_juniperConfig.get(), state->_dynteaser._config) ? "no" : "yes"));
- if (state->_dynteaser._result != NULL)
+ if (state->_dynteaser._result != nullptr)
juniper::ReleaseResult(state->_dynteaser._result);
state->_dynteaser._docid = docid;
state->_dynteaser._input = _inputFieldEnumValue;
state->_dynteaser._lang = _langFieldEnumValue;
state->_dynteaser._config = _juniperConfig.get();
- state->_dynteaser._result = NULL;
+ state->_dynteaser._result = nullptr;
int idx = gres->GetClass()->GetIndexFromEnumValue(_inputFieldEnumValue);
ResEntry *entry = gres->GetEntry(idx);
- if (entry != NULL &&
- state->_dynteaser._query != NULL) {
+ if (entry != nullptr && state->_dynteaser._query != nullptr) {
// obtain Juniper input
const char *buf;
uint32_t buflen;
- entry->_resolve_field(&buf, &buflen,
- &state->_docSumFieldSpace);
+ entry->_resolve_field(&buf, &buflen, &state->_docSumFieldSpace);
if (LOG_WOULD_LOG(spam)) {
std::ostringstream hexDump;
hexDump << vespalib::HexDump(buf, buflen);
LOG(spam, "makeDynamicTeaser: docid=%d, input='%s', hexdump:\n%s",
- docid, std::string(buf, buflen).c_str(),
- hexDump.str().c_str());
+ docid, std::string(buf, buflen).c_str(), hexDump.str().c_str());
}
uint32_t langid = static_cast<uint32_t>(-1);
@@ -422,33 +410,29 @@ DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid,
}
}
- juniper::Summary *teaser = (state->_dynteaser._result != NULL)
+ juniper::Summary *teaser = (state->_dynteaser._result != nullptr)
? juniper::GetTeaser(state->_dynteaser._result, _juniperConfig.get())
- : NULL;
+ : nullptr;
if (LOG_WOULD_LOG(debug)) {
std::ostringstream hexDump;
- if (teaser != NULL) {
+ if (teaser != nullptr) {
hexDump << vespalib::HexDump(teaser->Text(), teaser->Length());
}
LOG(debug, "makeDynamicTeaser: docid=%d, teaser='%s', hexdump:\n%s",
- docid, (teaser != NULL ? std::string(teaser->Text(), teaser->Length()).c_str() : "NULL"),
+ docid, (teaser != nullptr ? std::string(teaser->Text(), teaser->Length()).c_str() : "nullptr"),
hexDump.str().c_str());
}
- if (teaser != NULL) {
- return vespalib::string(teaser->Text(),
- teaser->Length());
+ if (teaser != nullptr) {
+ return vespalib::string(teaser->Text(), teaser->Length());
} else {
return vespalib::string();
}
}
void
-DynamicTeaserDFW::insertField(uint32_t docid,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType,
+DynamicTeaserDFW::insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state, ResType,
vespalib::slime::Inserter &target)
{
vespalib::string teaser = makeDynamicTeaser(docid, gres, state);
@@ -456,6 +440,4 @@ DynamicTeaserDFW::insertField(uint32_t docid,
target.insertString(value);
}
-} // namespace docsummary
-} // namespace search
-
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.cpp
index bf010172fa9..ae3d6acde43 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.cpp
@@ -40,8 +40,7 @@ void fmtZcurve(int64_t zval, vespalib::slime::Inserter &target)
}
void
-GeoPositionDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState * dsState,
- ResType, vespalib::slime::Inserter &target)
+GeoPositionDFW::insertField(uint32_t docid, GetDocsumsState * dsState, ResType, vespalib::slime::Inserter &target)
{
using vespalib::slime::Cursor;
using vespalib::slime::ObjectSymbolInserter;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.h b/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.h
index 8f630cde3af..9bd85abaf17 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/geoposdfw.h
@@ -14,8 +14,7 @@ class GeoPositionDFW : public AttrDFW
public:
typedef std::unique_ptr<GeoPositionDFW> UP;
GeoPositionDFW(const vespalib::string & attrName);
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target) override;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) override;
static UP create(const char *attribute_name, IAttributeManager *attribute_manager);
};
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
index 48e79a5e34c..9c82c00c3ef 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.cpp
@@ -66,8 +66,7 @@ AbsDistanceDFW::findMinDistance(uint32_t docid, GetDocsumsState *state)
}
void
-AbsDistanceDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target)
+AbsDistanceDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target)
{
bool forceEmpty = true;
@@ -166,8 +165,7 @@ formatField(const attribute::IAttributeVector &attribute, uint32_t docid, ResTyp
}
void
-PositionsDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState * dsState,
- ResType type, vespalib::slime::Inserter &target)
+PositionsDFW::insertField(uint32_t docid, GetDocsumsState * dsState, ResType type, vespalib::slime::Inserter &target)
{
vespalib::asciistream val(formatField(vec(*dsState), docid, type));
target.insertString(vespalib::Memory(val.c_str(), val.size()));
@@ -175,8 +173,7 @@ PositionsDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState * dsS
//--------------------------------------------------------------------------
-PositionsDFW::UP createPositionsDFW(const char *attribute_name,
- IAttributeManager *attribute_manager)
+PositionsDFW::UP createPositionsDFW(const char *attribute_name, IAttributeManager *attribute_manager)
{
PositionsDFW::UP ret;
if (attribute_manager != nullptr) {
@@ -195,12 +192,10 @@ PositionsDFW::UP createPositionsDFW(const char *attribute_name,
return ret;
}
}
- ret.reset(new PositionsDFW(attribute_name));
- return ret;
+ return std::make_unique<PositionsDFW>(attribute_name);
}
-AbsDistanceDFW::UP createAbsDistanceDFW(const char *attribute_name,
- IAttributeManager *attribute_manager)
+AbsDistanceDFW::UP createAbsDistanceDFW(const char *attribute_name, IAttributeManager *attribute_manager)
{
AbsDistanceDFW::UP ret;
if (attribute_manager != nullptr) {
@@ -219,8 +214,7 @@ AbsDistanceDFW::UP createAbsDistanceDFW(const char *attribute_name,
return ret;
}
}
- ret.reset(new AbsDistanceDFW(attribute_name));
- return ret;
+ return std::make_unique<AbsDistanceDFW>(attribute_name);
}
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
index 69a7ba3f58f..999da6f1860 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/positionsdfw.h
@@ -14,7 +14,7 @@ public:
AbsDistanceDFW(const vespalib::string & attrName);
bool IsGenerated() const override { return true; }
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
+ void insertField(uint32_t docid, GetDocsumsState *state,
ResType type, vespalib::slime::Inserter &target) override;
};
@@ -28,8 +28,7 @@ public:
PositionsDFW(const vespalib::string & attrName);
bool IsGenerated() const override { return true; }
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target) override ;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) override;
};
PositionsDFW::UP createPositionsDFW(const char *attribute_name, IAttributeManager *index_man);
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
index 9748bdac3b3..a1c96bb3e5b 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
@@ -20,7 +20,7 @@ RankFeaturesDFW::init(IDocsumEnvironment * env)
}
void
-RankFeaturesDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState *state,
+RankFeaturesDFW::insertField(uint32_t docid, GetDocsumsState *state,
ResType type, vespalib::slime::Inserter &target)
{
if (state->_rankFeatures.get() == nullptr) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.h b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.h
index 04ee14c79ca..37790d2f9b8 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.h
@@ -19,8 +19,7 @@ public:
~RankFeaturesDFW();
void init(IDocsumEnvironment * env);
bool IsGenerated() const override { return true; }
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target) override;
+ void insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target) override;
};
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/resultclass.h b/searchsummary/src/vespa/searchsummary/docsummary/resultclass.h
index e7c7c799b5f..52e331cd365 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/resultclass.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/resultclass.h
@@ -18,6 +18,7 @@ namespace search::docsummary {
enum ResType {
RES_INT = 0,
RES_SHORT,
+ RES_BOOL,
RES_BYTE,
RES_FLOAT,
RES_DOUBLE,
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.cpp b/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.cpp
index 1c42709826f..f3834ef4a12 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.cpp
@@ -45,6 +45,7 @@ ResultConfig::GetResTypeName(ResType type)
case RES_INT: return "integer";
case RES_SHORT: return "short";
case RES_BYTE: return "byte";
+ case RES_BOOL: return "bool";
case RES_FLOAT: return "float";
case RES_DOUBLE: return "double";
case RES_INT64: return "int64";
@@ -73,15 +74,14 @@ ResultConfig::Reset()
ResultClass *
ResultConfig::AddResultClass(const char *name, uint32_t id)
{
- ResultClass *ret = NULL;
+ ResultClass *ret = nullptr;
if (id != NoClassID() && (_classLookup.find(id) == _classLookup.end())) {
ResultClass::UP rc(new ResultClass(name, id, _fieldEnum));
ret = rc.get();
_classLookup[id] = std::move(rc);
if (_nameLookup.find(name) != _nameLookup.end()) {
- LOG(warning, "Duplicate result class name: %s "
- "(now maps to class id %u)", name, id);
+ LOG(warning, "Duplicate result class name: %s (now maps to class id %u)", name, id);
}
_nameLookup[name] = id;
}
@@ -93,7 +93,7 @@ const ResultClass*
ResultConfig::LookupResultClass(uint32_t id) const
{
IdMap::const_iterator it(_classLookup.find(id));
- return (it != _classLookup.end()) ? it->second.get() : NULL;
+ return (it != _classLookup.end()) ? it->second.get() : nullptr;
}
uint32_t
@@ -113,8 +113,8 @@ ResultConfig::LookupResultClassId(const vespalib::string &name) const
void
ResultConfig::CreateEnumMaps()
{
- for (IdMap::iterator it(_classLookup.begin()), mt(_classLookup.end()); it != mt; it++) {
- it ->second->CreateEnumMap();
+ for (auto & entry : _classLookup) {
+ entry.second->CreateEnumMap();
}
}
@@ -137,14 +137,12 @@ ResultConfig::ReadConfig(const vespa::config::search::SummaryConfig &cfg, const
break;
}
ResultClass *resClass = AddResultClass(cfg.classes[i].name.c_str(), classID);
- if (resClass == NULL) {
- LOG(error,
- "%s: unable to add classes[%d] name %s",
- configId, i, cfg.classes[i].name.c_str());
+ if (resClass == nullptr) {
+ LOG(error,"%s: unable to add classes[%d] name %s", configId, i, cfg.classes[i].name.c_str());
rc = false;
break;
}
- for (unsigned int j = 0; rc && j < cfg.classes[i].fields.size(); j++) {
+ for (unsigned int j = 0; rc && (j < cfg.classes[i].fields.size()); j++) {
const char *fieldtype = cfg.classes[i].fields[j].type.c_str();
const char *fieldname = cfg.classes[i].fields[j].name.c_str();
LOG(debug, "Reconfiguring class '%s' field '%s' of type '%s'", cfg.classes[i].name.c_str(), fieldname, fieldtype);
@@ -152,6 +150,8 @@ ResultConfig::ReadConfig(const vespa::config::search::SummaryConfig &cfg, const
rc = resClass->AddConfigEntry(fieldname, RES_INT);
} else if (strcmp(fieldtype, "short") == 0) {
rc = resClass->AddConfigEntry(fieldname, RES_SHORT);
+ } else if (strcmp(fieldtype, "bool") == 0) {
+ rc = resClass->AddConfigEntry(fieldname, RES_BOOL);
} else if (strcmp(fieldtype, "byte") == 0) {
rc = resClass->AddConfigEntry(fieldname, RES_BYTE);
} else if (strcmp(fieldtype, "float") == 0) {
@@ -176,17 +176,13 @@ ResultConfig::ReadConfig(const vespa::config::search::SummaryConfig &cfg, const
rc = resClass->AddConfigEntry(fieldname, RES_TENSOR);
} else if (strcmp(fieldtype, "featuredata") == 0) {
rc = resClass->AddConfigEntry(fieldname, RES_FEATUREDATA);
- } else { // FAIL: unknown field type
- LOG(error,
- "%s %s.fields[%d]: unknown type '%s'",
- configId, cfg.classes[i].name.c_str(), j, fieldtype);
+ } else {
+ LOG(error, "%s %s.fields[%d]: unknown type '%s'", configId, cfg.classes[i].name.c_str(), j, fieldtype);
rc = false;
break;
}
- if (!rc) { // FAIL: duplicate field name
- LOG(error,
- "%s %s.fields[%d]: duplicate name '%s'",
- configId, cfg.classes[i].name.c_str(), j, fieldname);
+ if (!rc) {
+ LOG(error, "%s %s.fields[%d]: duplicate name '%s'", configId, cfg.classes[i].name.c_str(), j, fieldname);
break;
}
}
@@ -212,33 +208,5 @@ ResultConfig::GetClassID(const char *buf, uint32_t buflen)
return ret;
}
-urlresult*
-ResultConfig::Unpack(uint32_t partition,
- uint32_t docid,
- HitRank metric,
- const char *buf,
- uint32_t buflen) const
-{
- urlresult *ret = NULL;
- const ResultClass *resClass = NULL;
- uint32_t tmp32;
-
- if (buflen >= sizeof(tmp32)) {
- memcpy(&tmp32, buf, sizeof(tmp32));
- buf += sizeof(tmp32);
- buflen -= sizeof(tmp32);
- resClass = LookupResultClass(tmp32);
- }
-
- if (resClass != NULL && (buflen > 0)) {
- ret = new GeneralResult(resClass, partition, docid, metric);
- if (ret->unpack(buf, buflen) != 0) { // FAIL: unpack
- delete ret;
- ret = NULL;
- }
- }
-
- return (ret != NULL) ? ret : new badurlresult(partition, docid, metric);
-}
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.h b/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.h
index eac6d4b113f..31218d94e93 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/resultconfig.h
@@ -115,6 +115,9 @@ public:
return true;
}
switch (a) {
+ case RES_BYTE:
+ case RES_BOOL:
+ return (b == RES_BYTE || b == RES_BOOL);
case RES_STRING:
case RES_DATA:
return (b == RES_STRING || b == RES_DATA);
@@ -147,7 +150,8 @@ public:
case RES_INT:
case RES_SHORT:
case RES_BYTE:
- return (b == RES_INT || b == RES_SHORT || b == RES_BYTE);
+ case RES_BOOL:
+ return (b == RES_INT || b == RES_SHORT || b == RES_BYTE || b == RES_BOOL);
case RES_FLOAT:
case RES_DOUBLE:
return (b == RES_FLOAT || b == RES_DOUBLE);
@@ -269,29 +273,6 @@ public:
* @param buflen length of docsum blob.
**/
uint32_t GetClassID(const char *buf, uint32_t buflen);
-
- /**
- * Unpack docsum blob. The first n (0/8/16/32) bits are read from
- * the data given and used to look up the appropriate result
- * class. A GeneralResult object is created based on that
- * class and told to unpack the rest of the docsum blob. If this
- * operation succeeds, the GeneralResult object is
- * returned. It if fails, a badurlresult object is returned
- * instead.
- *
- * @return object representing the unpacked result.
- * @param partition partition path for current hit.
- * @param docid docid for current hit.
- * @param metric relevance estimate for current hit.
- * @param buf docsum blob.
- * @param buflen length of docsum blob.
- **/
- urlresult *
- Unpack(uint32_t partition,
- uint32_t docid,
- HitRank metric,
- const char *buf,
- uint32_t buflen) const;
};
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/resultpacker.cpp b/searchsummary/src/vespa/searchsummary/docsummary/resultpacker.cpp
index 178e1a90667..c9642b80e56 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/resultpacker.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/resultpacker.cpp
@@ -23,7 +23,7 @@ bool ResultPacker::CheckEntry(ResType type)
if (_error)
return false;
- bool rc = (_cfgEntry != NULL &&
+ bool rc = (_cfgEntry != nullptr &&
IsBinaryCompatible(_cfgEntry->_type, type));
if (rc) {
@@ -43,7 +43,7 @@ ResultPacker::SetFormatError(ResType type)
{
_error = true;
- if (_cfgEntry != NULL) {
+ if (_cfgEntry != nullptr) {
LOG(error,
"ResultPacker: format error: got '%s', expected '%s'",
GetResTypeName(type),
@@ -60,17 +60,15 @@ ResultPacker::ResultPacker(const ResultConfig *resConfig)
: _buf(32768),
_cbuf(32768),
_resConfig(resConfig),
- _resClass(NULL),
+ _resClass(nullptr),
_entryIdx(0),
- _cfgEntry(NULL),
+ _cfgEntry(nullptr),
_error(true)
{
}
-ResultPacker::~ResultPacker()
-{
-}
+ResultPacker::~ResultPacker() = default;
void
ResultPacker::InitPlain()
@@ -82,16 +80,16 @@ bool
ResultPacker::Init(uint32_t classID)
{
_buf.reset();
- _resClass = (_resConfig != NULL) ?
- _resConfig->LookupResultClass(classID) : NULL;
+ _resClass = (_resConfig != nullptr) ?
+ _resConfig->LookupResultClass(classID) : nullptr;
_entryIdx = 0;
- if (_resClass != NULL) {
+ if (_resClass != nullptr) {
uint32_t id = _resClass->GetClassID();
_buf.append(&id, sizeof(id));
_cfgEntry = _resClass->GetEntry(_entryIdx);
_error = false;
} else {
- _cfgEntry = NULL;
+ _cfgEntry = nullptr;
_error = true;
LOG(error, "ResultPacker: resultclass %d does not exist", classID);
@@ -104,22 +102,23 @@ ResultPacker::Init(uint32_t classID)
bool
ResultPacker::AddEmpty()
{
- if (!_error && _cfgEntry != NULL) {
+ if (!_error && _cfgEntry != nullptr) {
switch (_cfgEntry->_type) {
case RES_INT: return AddInteger(search::attribute::getUndefined<int32_t>());
case RES_SHORT: return AddShort(search::attribute::getUndefined<int16_t>());
+ case RES_BOOL: return AddByte(0);
case RES_BYTE: return AddByte(search::attribute::getUndefined<int8_t>());
case RES_FLOAT: return AddFloat(search::attribute::getUndefined<float>());
case RES_DOUBLE: return AddDouble(search::attribute::getUndefined<double>());
case RES_INT64: return AddInt64(search::attribute::getUndefined<int64_t>());
- case RES_STRING: return AddString(NULL, 0);
- case RES_DATA: return AddData(NULL, 0);
+ case RES_STRING: return AddString(nullptr, 0);
+ case RES_DATA: return AddData(nullptr, 0);
case RES_XMLSTRING:
case RES_JSONSTRING:
case RES_FEATUREDATA:
- case RES_LONG_STRING: return AddLongString(NULL, 0);
- case RES_TENSOR: return AddSerializedTensor(NULL, 0);
- case RES_LONG_DATA: return AddLongData(NULL, 0);
+ case RES_LONG_STRING: return AddLongString(nullptr, 0);
+ case RES_TENSOR: return AddSerializedTensor(nullptr, 0);
+ case RES_LONG_DATA: return AddLongData(nullptr, 0);
}
}
return AddInteger(0); // to provoke error condition
@@ -271,7 +270,7 @@ ResultPacker::GetDocsumBlob(const char **buf, uint32_t *buflen)
_resClass->GetNumEntries() - _entryIdx);
}
if (_error) {
- *buf = NULL;
+ *buf = nullptr;
*buflen = 0;
return false;
} else {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
index 7cf1e65fbc0..9992d782d6e 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
@@ -12,13 +12,11 @@ namespace search::docsummary {
SummaryFeaturesDFW::SummaryFeaturesDFW() :
- _env(NULL)
+ _env(nullptr)
{
}
-SummaryFeaturesDFW::~SummaryFeaturesDFW()
-{
-}
+SummaryFeaturesDFW::~SummaryFeaturesDFW() = default;
void
SummaryFeaturesDFW::init(IDocsumEnvironment * env)
@@ -30,8 +28,7 @@ static vespalib::string _G_cached("vespa.summaryFeatures.cached");
static vespalib::Memory _M_cached("vespa.summaryFeatures.cached");
void
-SummaryFeaturesDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState *state,
- ResType type, vespalib::slime::Inserter &target)
+SummaryFeaturesDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType type, vespalib::slime::Inserter &target)
{
if ( ! state->_summaryFeatures) {
state->_callback.FillSummaryFeatures(state, _env);
@@ -41,7 +38,7 @@ SummaryFeaturesDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState
}
const FeatureSet::StringVector &names = state->_summaryFeatures->getNames();
const feature_t *values = state->_summaryFeatures->getFeaturesByDocId(docid);
- if (type == RES_FEATUREDATA && values != NULL) {
+ if (type == RES_FEATUREDATA && values != nullptr) {
vespalib::slime::Cursor& obj = target.insertObject();
for (uint32_t i = 0; i < names.size(); ++i) {
vespalib::Memory name(names[i].c_str(), names[i].size());
@@ -55,7 +52,7 @@ SummaryFeaturesDFW::insertField(uint32_t docid, GeneralResult *, GetDocsumsState
return;
}
vespalib::JSONStringer & json(state->_jsonStringer);
- if (values != NULL) {
+ if (values != nullptr) {
json.clear();
json.beginObject();
for (uint32_t i = 0; i < names.size(); ++i) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.h b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.h
index e417e89cf04..f1452d4c0a9 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.h
@@ -10,7 +10,7 @@ namespace search::docsummary {
class IDocsumEnvironment;
-class FeaturesDFW : public IDocsumFieldWriter
+class FeaturesDFW : public IDocsumFW
{
protected:
void featureDump(vespalib::JSONStringer & json, vespalib::stringref name, double feature);
@@ -29,7 +29,7 @@ public:
~SummaryFeaturesDFW() override;
void init(IDocsumEnvironment * env);
bool IsGenerated() const override { return true; }
- void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
+ void insertField(uint32_t docid, GetDocsumsState *state,
ResType type, vespalib::slime::Inserter &target) override;
};
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/summaryfieldconverter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/summaryfieldconverter.cpp
index 290cc45648a..91a7fd45061 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/summaryfieldconverter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/summaryfieldconverter.cpp
@@ -9,6 +9,7 @@
#include <vespa/document/annotation/spantreevisitor.h>
#include <vespa/document/datatype/documenttype.h>
#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/boolfieldvalue.h>
#include <vespa/document/fieldvalue/bytefieldvalue.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/fieldvalue/doublefieldvalue.h>
@@ -42,6 +43,7 @@ using document::Annotation;
using document::AnnotationReferenceFieldValue;
using document::ArrayDataType;
using document::ArrayFieldValue;
+using document::BoolFieldValue;
using document::ByteFieldValue;
using document::DataType;
using document::Document;
@@ -229,7 +231,7 @@ class SummaryFieldValueConverter : protected ConstFieldValueVisitor
FieldValue::UP _field_value;
FieldValueConverter &_structuredFieldConverter;
- virtual void visit(const ArrayFieldValue &value) override {
+ void visit(const ArrayFieldValue &value) override {
_field_value = _structuredFieldConverter.convert(value);
}
@@ -237,17 +239,18 @@ class SummaryFieldValueConverter : protected ConstFieldValueVisitor
void visitPrimitive(const T &t) {
_field_value.reset(t.clone());
}
- virtual void visit(const IntFieldValue &value) override { visitPrimitive(value); }
- virtual void visit(const LongFieldValue &value) override { visitPrimitive(value); }
- virtual void visit(const ShortFieldValue &value) override { visitPrimitive(value); }
- virtual void visit(const ByteFieldValue &value) override {
+ void visit(const IntFieldValue &value) override { visitPrimitive(value); }
+ void visit(const LongFieldValue &value) override { visitPrimitive(value); }
+ void visit(const ShortFieldValue &value) override { visitPrimitive(value); }
+ void visit(const BoolFieldValue &value) override { visitPrimitive(value); }
+ void visit(const ByteFieldValue &value) override {
int8_t signedValue = value.getAsByte();
_field_value.reset(new ShortFieldValue(signedValue));
}
- virtual void visit(const DoubleFieldValue &value) override { visitPrimitive(value); }
- virtual void visit(const FloatFieldValue &value) override { visitPrimitive(value); }
+ void visit(const DoubleFieldValue &value) override { visitPrimitive(value); }
+ void visit(const FloatFieldValue &value) override { visitPrimitive(value); }
- virtual void visit(const StringFieldValue &value) override {
+ void visit(const StringFieldValue &value) override {
if (_tokenize) {
SummaryHandler handler(value.getValue(), _str);
handleIndexingTerms(handler, value);
@@ -256,33 +259,29 @@ class SummaryFieldValueConverter : protected ConstFieldValueVisitor
}
}
- virtual void visit(const AnnotationReferenceFieldValue & v ) override {
+ void visit(const AnnotationReferenceFieldValue & v ) override {
_field_value = _structuredFieldConverter.convert(v);
}
- virtual void visit(const Document & v) override {
+ void visit(const Document & v) override {
_field_value = _structuredFieldConverter.convert(v);
}
- virtual void
- visit(const PredicateFieldValue &value) override
- {
+ void visit(const PredicateFieldValue &value) override {
_str << value.toString();
}
- virtual void
- visit(const RawFieldValue &value) override
- {
+ void visit(const RawFieldValue &value) override {
visitPrimitive(value);
}
- virtual void visit(const MapFieldValue & v) override {
+ void visit(const MapFieldValue & v) override {
_field_value = _structuredFieldConverter.convert(v);
}
- virtual void visit(const StructFieldValue &value) override {
+ void visit(const StructFieldValue &value) override {
if (*value.getDataType() == *SearchDataType::URI) {
FieldValue::UP uriAllValue = value.getValue("all");
- if (uriAllValue.get() != NULL &&
+ if (uriAllValue &&
uriAllValue->inherits(IDENTIFIABLE_CLASSID(StringFieldValue)))
{
uriAllValue->accept(*this);
@@ -292,11 +291,11 @@ class SummaryFieldValueConverter : protected ConstFieldValueVisitor
_field_value = _structuredFieldConverter.convert(value);
}
- virtual void visit(const WeightedSetFieldValue &value) override {
+ void visit(const WeightedSetFieldValue &value) override {
_field_value = _structuredFieldConverter.convert(value);
}
- virtual void visit(const TensorFieldValue &value) override {
+ void visit(const TensorFieldValue &value) override {
visitPrimitive(value);
}
@@ -315,7 +314,7 @@ public:
if (_field_value.get()) {
return std::move(_field_value);
}
- return FieldValue::UP(new StringFieldValue(_str.str()));
+ return std::make_unique<StringFieldValue>(_str.str());
}
};
@@ -323,7 +322,7 @@ SummaryFieldValueConverter::SummaryFieldValueConverter(bool tokenize, FieldValue
: _str(), _tokenize(tokenize),
_structuredFieldConverter(subConverter)
{}
-SummaryFieldValueConverter::~SummaryFieldValueConverter() {}
+SummaryFieldValueConverter::~SummaryFieldValueConverter() = default;
using namespace vespalib::slime::convenience;
@@ -331,14 +330,14 @@ class SlimeFiller : public ConstFieldValueVisitor {
Inserter &_inserter;
bool _tokenize;
- virtual void visit(const AnnotationReferenceFieldValue & v ) override {
+ void visit(const AnnotationReferenceFieldValue & v ) override {
(void)v;
Cursor &c = _inserter.insertObject();
Memory key("error");
Memory val("cannot convert from annotation reference field");
c.setString(key, val);
}
- virtual void visit(const Document & v) override {
+ void visit(const Document & v) override {
(void)v;
Cursor &c = _inserter.insertObject();
Memory key("error");
@@ -346,7 +345,7 @@ class SlimeFiller : public ConstFieldValueVisitor {
c.setString(key, val);
}
- virtual void visit(const MapFieldValue & v) override {
+ void visit(const MapFieldValue & v) override {
Cursor &a = _inserter.insertArray();
Symbol keysym = a.resolve("key");
Symbol valsym = a.resolve("value");
@@ -364,7 +363,7 @@ class SlimeFiller : public ConstFieldValueVisitor {
}
}
- virtual void visit(const ArrayFieldValue &value) override {
+ void visit(const ArrayFieldValue &value) override {
Cursor &a = _inserter.insertArray();
if (value.size() > 0) {
ArrayInserter ai(a);
@@ -375,7 +374,7 @@ class SlimeFiller : public ConstFieldValueVisitor {
}
}
- virtual void visit(const StringFieldValue &value) override {
+ void visit(const StringFieldValue &value) override {
if (_tokenize) {
asciistream tmp;
SummaryHandler handler(value.getValue(), tmp);
@@ -386,48 +385,48 @@ class SlimeFiller : public ConstFieldValueVisitor {
}
}
- virtual void visit(const IntFieldValue &value) override {
+ void visit(const IntFieldValue &value) override {
int32_t v = value.getValue();
_inserter.insertLong(v);
}
- virtual void visit(const LongFieldValue &value) override {
+ void visit(const LongFieldValue &value) override {
int64_t v = value.getValue();
_inserter.insertLong(v);
}
- virtual void visit(const ShortFieldValue &value) override {
+ void visit(const ShortFieldValue &value) override {
int16_t v = value.getValue();
_inserter.insertLong(v);
}
- virtual void visit(const ByteFieldValue &value) override {
+ void visit(const ByteFieldValue &value) override {
int8_t v = value.getAsByte();
_inserter.insertLong(v);
}
- virtual void visit(const DoubleFieldValue &value) override {
+ void visit(const BoolFieldValue &value) override {
+ bool v = value.getValue();
+ _inserter.insertBool(v);
+ }
+ void visit(const DoubleFieldValue &value) override {
double v = value.getValue();
_inserter.insertDouble(v);
}
- virtual void visit(const FloatFieldValue &value) override {
+ void visit(const FloatFieldValue &value) override {
float v = value.getValue();
_inserter.insertDouble(v);
}
- virtual void
- visit(const PredicateFieldValue &value) override
- {
+ void visit(const PredicateFieldValue &value) override {
vespalib::slime::inject(value.getSlime().get(), _inserter);
}
- virtual void
- visit(const RawFieldValue &value) override
- {
+ void visit(const RawFieldValue &value) override {
std::pair<const char *, size_t> buf = value.getAsRaw();
_inserter.insertData(Memory(buf.first, buf.second));
}
- virtual void visit(const StructFieldValue &value) override {
+ void visit(const StructFieldValue &value) override {
if (*value.getDataType() == *SearchDataType::URI) {
FieldValue::UP uriAllValue = value.getValue("all");
- if (uriAllValue.get() != NULL &&
+ if (uriAllValue &&
uriAllValue->inherits(IDENTIFIABLE_CLASSID(StringFieldValue)))
{
uriAllValue->accept(*this);
@@ -444,7 +443,7 @@ class SlimeFiller : public ConstFieldValueVisitor {
}
}
- virtual void visit(const WeightedSetFieldValue &value) override {
+ void visit(const WeightedSetFieldValue &value) override {
Cursor &a = _inserter.insertArray();
if (value.size() > 0) {
Symbol isym = a.resolve("item");
@@ -460,7 +459,7 @@ class SlimeFiller : public ConstFieldValueVisitor {
}
}
- virtual void visit(const TensorFieldValue &value) override {
+ void visit(const TensorFieldValue &value) override {
const auto &tensor = value.getAsTensorPtr();
vespalib::nbostream s;
if (tensor) {
@@ -495,7 +494,7 @@ public:
search::RawBuf rbuf(4096);
search::SlimeOutputRawBufAdapter adapter(rbuf);
vespalib::slime::BinaryFormat::encode(slime, adapter);
- return FieldValue::UP(new RawFieldValue(rbuf.GetDrainPos(), rbuf.GetUsedLen()));
+ return std::make_unique<RawFieldValue>(rbuf.GetDrainPos(), rbuf.GetUsedLen());
}
};
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/textextractordfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/textextractordfw.cpp
index 121520c4d03..a0efda07f04 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/textextractordfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/textextractordfw.cpp
@@ -27,16 +27,13 @@ TextExtractorDFW::init(const vespalib::string & fieldName, const vespalib::strin
}
void
-TextExtractorDFW::insertField(uint32_t,
- GeneralResult *gres,
- GetDocsumsState *state,
- ResType,
+TextExtractorDFW::insertField(uint32_t, GeneralResult *gres, GetDocsumsState *state, ResType,
vespalib::slime::Inserter &target)
{
vespalib::string extracted;
ResEntry * entry = gres->GetEntryFromEnumValue(_inputFieldEnum);
- if (entry != NULL) {
- const char * buf = NULL;
+ if (entry != nullptr) {
+ const char * buf = nullptr;
uint32_t buflen = 0;
entry->_resolve_field(&buf, &buflen, &state->_docSumFieldSpace);
// extract the text
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/urlresult.cpp b/searchsummary/src/vespa/searchsummary/docsummary/urlresult.cpp
index 9cd5c58f971..074cc1cadf1 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/urlresult.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/urlresult.cpp
@@ -10,45 +10,6 @@ LOG_SETUP(".searchlib.docsummary.urlresult");
namespace search::docsummary {
-urlresult::urlresult(uint32_t partition, uint32_t docid, HitRank metric)
- : _partition(partition),
- _docid(docid),
- _metric(metric)
-{ }
-
-
-urlresult::~urlresult() { }
-
-
-/*===============================================================*/
-
-
-badurlresult::badurlresult()
- : urlresult(0, 0, 0)
-{ }
-
-
-badurlresult::badurlresult(uint32_t partition, uint32_t docid, HitRank metric)
- : urlresult(partition, docid, metric)
-{ }
-
-
-badurlresult::~badurlresult() { }
-
-
-int
-badurlresult::unpack(const char *buf, const size_t buflen)
-{
- (void) buf;
- (void) buflen;
- LOG(warning, "badurlresult::unpack");
- return 0;
-}
-
-
-/*===============================================================*/
-
-
void
GeneralResult::AllocEntries(uint32_t buflen, bool inplace)
{
@@ -60,10 +21,10 @@ GeneralResult::AllocEntries(uint32_t buflen, bool inplace)
if (cnt > 0) {
_entrycnt = cnt;
_entries = (ResEntry *) malloc(needMem);
- assert(_entries != NULL);
+ assert(_entries != nullptr);
if (inplace) {
- _buf = NULL;
- _bufEnd = NULL;
+ _buf = nullptr;
+ _bufEnd = nullptr;
} else {
_buf = ((char *)_entries) + cnt * sizeof(ResEntry);
_bufEnd = _buf + buflen + 1;
@@ -71,20 +32,19 @@ GeneralResult::AllocEntries(uint32_t buflen, bool inplace)
memset(_entries, 0, cnt * sizeof(ResEntry));
} else {
_entrycnt = 0;
- _entries = NULL;
- _buf = NULL;
- _bufEnd = NULL;
+ _entries = nullptr;
+ _buf = nullptr;
+ _bufEnd = nullptr;
}
}
-
void
GeneralResult::FreeEntries()
{
uint32_t cnt = _entrycnt;
- // (_buf == NULL) <=> (_inplace_unpack() || (cnt == 0))
- if (_buf != NULL) {
+ // (_buf == nullptr) <=> (_inplace_unpack() || (cnt == 0))
+ if (_buf != nullptr) {
for (uint32_t i = 0; i < cnt; i++) {
if (ResultConfig::IsVariableSize(_entries[i]._type) &&
!InBuf(_entries[i]._stringval))
@@ -94,41 +54,32 @@ GeneralResult::FreeEntries()
free(_entries); // free '_entries'/'_buf' chunk
}
-
-
-GeneralResult::GeneralResult(const ResultClass *resClass,
- uint32_t partition, uint32_t docid,
- HitRank metric)
- : urlresult(partition, docid, metric),
- _resClass(resClass),
+GeneralResult::GeneralResult(const ResultClass *resClass)
+ : _resClass(resClass),
_entrycnt(0),
- _entries(NULL),
- _buf(NULL),
- _bufEnd(NULL)
+ _entries(nullptr),
+ _buf(nullptr),
+ _bufEnd(nullptr)
{
}
-
GeneralResult::~GeneralResult()
{
FreeEntries();
}
-
ResEntry *
GeneralResult::GetEntry(uint32_t idx)
{
- return (idx < _entrycnt) ? &_entries[idx] : NULL;
+ return (idx < _entrycnt) ? &_entries[idx] : nullptr;
}
-
ResEntry *
GeneralResult::GetEntry(const char *name)
{
int idx = _resClass->GetIndexFromName(name);
- return (idx >= 0 && (uint32_t)idx < _entrycnt) ?
- &_entries[idx] : NULL;
+ return (idx >= 0 && (uint32_t)idx < _entrycnt) ? &_entries[idx] : nullptr;
}
@@ -136,398 +87,17 @@ ResEntry *
GeneralResult::GetEntryFromEnumValue(uint32_t value)
{
int idx = _resClass->GetIndexFromEnumValue(value);
-
- return (idx >= 0 && (uint32_t)idx < _entrycnt) ?
- &_entries[idx] : NULL;
+ return (idx >= 0 && (uint32_t)idx < _entrycnt) ? &_entries[idx] : nullptr;
}
-
-int
-GeneralResult::unpack(const char *buf, const size_t buflen)
-{
- bool rc = true;
- const char *ebuf = buf + buflen; // Ref to first after buffer
- const char *p = buf; // current position in buffer
-
- if (_entries != NULL)
- FreeEntries();
-
- AllocEntries(buflen);
-
- for (uint32_t i = 0; rc && i < _entrycnt; i++) {
- const ResConfigEntry *entry = _resClass->GetEntry(i);
-
- switch (entry->_type) {
-
- case RES_INT: {
-
- if (p + sizeof(_entries[i]._intval) <= ebuf) {
-
- memcpy(&_entries[i]._intval, p, sizeof(_entries[i]._intval));
- _entries[i]._type = RES_INT;
- p += sizeof(_entries[i]._intval);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(..._intval) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_SHORT: {
-
- uint16_t shortval;
- if (p + sizeof(shortval) <= ebuf) {
-
- memcpy(&shortval, p, sizeof(shortval));
- _entries[i]._intval = (uint32_t)shortval;
- _entries[i]._type = RES_INT; // type promotion
- p += sizeof(shortval);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(shortval) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_BYTE: {
-
- uint8_t byteval;
- if (p + sizeof(byteval) <= ebuf) {
-
- memcpy(&byteval, p, sizeof(byteval));
- _entries[i]._intval = (uint32_t)byteval;
- _entries[i]._type = RES_INT; // type promotion
- p += sizeof(byteval);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(byteval) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_FLOAT: {
-
- float floatval;
- if (p + sizeof(floatval) <= ebuf) {
-
- memcpy(&floatval, p, sizeof(floatval));
- _entries[i]._doubleval = (double)floatval;
- _entries[i]._type = RES_DOUBLE; // type promotion
- p += sizeof(floatval);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(floatval) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_DOUBLE: {
-
- if (p + sizeof(_entries[i]._doubleval) <= ebuf) {
-
- memcpy(&_entries[i]._doubleval, p, sizeof(_entries[i]._doubleval));
- _entries[i]._type = RES_DOUBLE;
- p += sizeof(_entries[i]._doubleval);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(..._doubleval) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_INT64: {
-
- if (p + sizeof(_entries[i]._int64val) <= ebuf) {
-
- memcpy(&_entries[i]._int64val, p, sizeof(_entries[i]._int64val));
- _entries[i]._type = RES_INT64;
- p += sizeof(_entries[i]._int64val);
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(..._int64val) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_STRING: {
-
- uint16_t slen;
- if (p + sizeof(slen) <= ebuf) {
-
- memcpy(&slen, p, sizeof(slen));
- p += sizeof(slen);
-
- if (p + slen <= ebuf) {
-
- _entries[i]._stringval = _buf + (p - buf);
- memcpy(_entries[i]._stringval, p, slen);
- _entries[i]._stringval[slen] = '\0';
- _entries[i]._stringlen = slen;
- _entries[i]._type = RES_STRING;
- p += slen;
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + slen > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(slen) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_DATA: {
-
- uint16_t dlen;
- if (p + sizeof(dlen) <= ebuf) {
-
- memcpy(&dlen, p, sizeof(dlen));
- p += sizeof(dlen);
-
- if (p + dlen <= ebuf) {
-
- _entries[i]._dataval = _buf + (p - buf);
- memcpy(_entries[i]._dataval, p, dlen);
- _entries[i]._dataval[dlen] = '\0'; // just in case.
- _entries[i]._datalen = dlen;
- _entries[i]._type = RES_DATA;
- p += dlen;
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + dlen > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(dlen) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_XMLSTRING:
- case RES_JSONSTRING:
- case RES_FEATUREDATA:
- case RES_LONG_STRING: {
-
- uint32_t lslen;
- bool compressed;
- if (p + sizeof(lslen) <= ebuf) {
-
- memcpy(&lslen, p, sizeof(lslen));
- p += sizeof(lslen);
-
- compressed = ((lslen & 0x80000000) != 0);
- lslen &= 0x7fffffff;
-
- if (p + lslen <= ebuf) {
-
- if (compressed) { // COMPRESSED
- uint32_t realLen = 0;
- if (lslen >= sizeof(realLen))
- memcpy(&realLen, p, sizeof(realLen));
- else
- LOG(warning, "Cannot uncompress docsum field %s; docsum field meta-data incomplete",
- entry->_bindname.c_str());
- if (realLen > 0) {
- _entries[i]._stringval = new char[realLen + 1];
- }
- if (_entries[i]._stringval != NULL) {
- uLongf rlen = realLen;
- if ((uncompress((Bytef *)_entries[i]._stringval, &rlen,
- (const Bytef *)(p + sizeof(realLen)),
- lslen - sizeof(realLen)) == Z_OK) &&
- rlen == realLen) {
- assert(rlen == realLen);
-
- // COMPRESSED LONG STRING FIELD OK
- _entries[i]._stringval[realLen] = '\0';
- _entries[i]._stringlen = realLen;
-
- } else {
- LOG(warning, "Cannot uncompress docsum field %s; decompression error",
- entry->_bindname.c_str());
- delete [] _entries[i]._stringval;
- _entries[i]._stringval = NULL;
- }
- }
- // insert empty field if decompress failed
- if (_entries[i]._stringval == NULL) {
- _entries[i]._stringval = _buf + (p - buf);
- _entries[i]._stringval[0] = '\0';
- _entries[i]._stringlen = 0;
- }
-
- } else { // UNCOMPRESSED
-
- _entries[i]._stringval = _buf + (p - buf);
- memcpy(_entries[i]._stringval, p, lslen);
- _entries[i]._stringval[lslen] = '\0';
- _entries[i]._stringlen = lslen;
-
- }
- _entries[i]._type = RES_STRING; // type normalization
- p += lslen;
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + lslen > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(lslen) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- case RES_TENSOR:
- case RES_LONG_DATA: {
-
- uint32_t ldlen;
- bool compressed;
- if (p + sizeof(ldlen) <= ebuf) {
-
- memcpy(&ldlen, p, sizeof(ldlen));
- p += sizeof(ldlen);
-
- compressed = ((ldlen & 0x80000000) != 0);
- ldlen &= 0x7fffffff;
-
- if (p + ldlen <= ebuf) {
-
- if (compressed) { // COMPRESSED
- uint32_t realLen = 0;
- if (ldlen >= sizeof(realLen))
- memcpy(&realLen, p, sizeof(realLen));
- else
- LOG(warning, "Cannot uncompress docsum field %s; docsum field meta-data incomplete",
- entry->_bindname.c_str());
- if (realLen > 0) {
- _entries[i]._dataval = new char [realLen + 1];
- }
- if (_entries[i]._dataval != NULL) {
- uLongf rlen = realLen;
- if ((uncompress((Bytef *)_entries[i]._dataval, &rlen,
- (const Bytef *)(p + sizeof(realLen)),
- ldlen - sizeof(realLen)) == Z_OK) &&
- rlen == realLen) {
- assert(rlen == realLen);
-
- // COMPRESSED LONG DATA FIELD OK
- _entries[i]._dataval[realLen] = '\0';
- _entries[i]._datalen = realLen;
-
- } else {
- LOG(warning, "Cannot uncompress docsum field %s; decompression error",
- entry->_bindname.c_str());
- delete [] _entries[i]._dataval;
- _entries[i]._dataval = NULL;
- }
- }
-
- // insert empty field if decompress failed
- if (_entries[i]._dataval == NULL) {
- _entries[i]._dataval = _buf + (p - buf);
- _entries[i]._dataval[0] = '\0';
- _entries[i]._datalen = 0;
- }
-
- } else { // UNCOMPRESSED
-
- _entries[i]._dataval = _buf + (p - buf);
- memcpy(_entries[i]._dataval, p, ldlen);
- _entries[i]._dataval[ldlen] = '\0'; // just in case
- _entries[i]._datalen = ldlen;
-
- }
- _entries[i]._type = RES_DATA; // type normalization
- p += ldlen;
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + ldlen > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
-
- } else {
-
- LOG(debug, "GeneralResult::unpack: p + sizeof(ldlen) > ebuf");
- LOG(error, "Document summary too short, couldn't unpack");
- rc = false;
- }
- break;
- }
-
- default:
- LOG(warning, "GeneralResult::unpack: no such type:%d", entry->_type);
- LOG(error, "Incorrect type in document summary, couldn't unpack");
- rc = false;
- break;
- } // END -- switch (entry->_type) {
- } // END -- for (uint32_t i = 0; rc && i < _entrycnt; i++) {
-
- if (rc && p != ebuf) {
- LOG(debug, "GeneralResult::unpack: p:%p != ebuf:%p", p, ebuf);
- LOG(error, "Document summary too long, couldn't unpack.");
- rc = false;
- }
-
- if (rc)
- return 0; // SUCCESS
-
- // clean up on failure
- FreeEntries();
- _entrycnt = 0;
- _entries = NULL;
- _buf = NULL;
- _bufEnd = NULL;
-
- return -1; // FAIL
-}
-
-
bool
-GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
+GeneralResult::unpack(const char *buf, const size_t buflen)
{
bool rc = true;
const char *ebuf = buf + buflen; // Ref to first after buffer
const char *p = buf; // current position in buffer
- if (_entries != NULL)
+ if (_entries != nullptr)
FreeEntries();
AllocEntries(buflen, true);
@@ -538,17 +108,12 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
switch (entry->_type) {
case RES_INT: {
-
if (p + sizeof(_entries[i]._intval) <= ebuf) {
-
memcpy(&_entries[i]._intval, p, sizeof(_entries[i]._intval));
_entries[i]._type = RES_INT;
p += sizeof(_entries[i]._intval);
-
} else {
-
- LOG(debug,
- "GeneralResult::_inplace_unpack: p + sizeof(..._intval) > ebuf");
+ LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(..._intval) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
@@ -556,39 +121,29 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_SHORT: {
-
uint16_t shortval;
if (p + sizeof(shortval) <= ebuf) {
-
memcpy(&shortval, p, sizeof(shortval));
_entries[i]._intval = (uint32_t)shortval;
_entries[i]._type = RES_INT; // type promotion
p += sizeof(shortval);
-
} else {
-
- LOG(debug,
- "GeneralResult::_inplace_unpack: p + sizeof(shortval) > ebuf");
+ LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(shortval) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
break;
}
-
+ case RES_BOOL:
case RES_BYTE: {
-
uint8_t byteval;
if (p + sizeof(byteval) <= ebuf) {
-
memcpy(&byteval, p, sizeof(byteval));
_entries[i]._intval = (uint32_t)byteval;
_entries[i]._type = RES_INT; // type promotion
p += sizeof(byteval);
-
} else {
-
- LOG(debug,
- "GeneralResult::_inplace_unpack: p + sizeof(byteval) > ebuf");
+ LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(byteval) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
@@ -596,17 +151,13 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_FLOAT: {
-
float floatval;
if (p + sizeof(floatval) <= ebuf) {
-
memcpy(&floatval, p, sizeof(floatval));
_entries[i]._doubleval = (double)floatval;
_entries[i]._type = RES_DOUBLE; // type promotion
p += sizeof(floatval);
-
} else {
-
LOG(debug, "GeneralResult::unpack: p + sizeof(floatval) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -615,15 +166,11 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_DOUBLE: {
-
if (p + sizeof(_entries[i]._doubleval) <= ebuf) {
-
memcpy(&_entries[i]._doubleval, p, sizeof(_entries[i]._doubleval));
_entries[i]._type = RES_DOUBLE;
p += sizeof(_entries[i]._doubleval);
-
} else {
-
LOG(debug, "GeneralResult::unpack: p + sizeof(..._doubleval) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -632,15 +179,11 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_INT64: {
-
if (p + sizeof(_entries[i]._int64val) <= ebuf) {
-
memcpy(&_entries[i]._int64val, p, sizeof(_entries[i]._int64val));
_entries[i]._type = RES_INT64;
p += sizeof(_entries[i]._int64val);
-
} else {
-
LOG(debug, "GeneralResult::unpack: p + sizeof(..._int64val) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -649,29 +192,21 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_STRING: {
-
uint16_t slen;
if (p + sizeof(slen) <= ebuf) {
-
memcpy(&slen, p, sizeof(slen));
p += sizeof(slen);
-
if (p + slen <= ebuf) {
-
_entries[i]._stringval = const_cast<char *>(p);
_entries[i]._stringlen = slen;
_entries[i]._type = RES_STRING;
p += slen;
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + slen > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(slen) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -680,29 +215,21 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_DATA: {
-
uint16_t dlen;
if (p + sizeof(dlen) <= ebuf) {
-
memcpy(&dlen, p, sizeof(dlen));
p += sizeof(dlen);
-
if (p + dlen <= ebuf) {
-
_entries[i]._dataval = const_cast<char *>(p);
_entries[i]._datalen = dlen;
_entries[i]._type = RES_DATA;
p += dlen;
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + dlen > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(dlen) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -714,32 +241,23 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
case RES_JSONSTRING:
case RES_FEATUREDATA:
case RES_LONG_STRING: {
-
uint32_t flen;
uint32_t lslen;
if (p + sizeof(flen) <= ebuf) {
-
memcpy(&flen, p, sizeof(flen));
p += sizeof(flen);
-
lslen = flen & 0x7fffffff;
-
if (p + lslen <= ebuf) {
-
_entries[i]._stringval = const_cast<char *>(p);
_entries[i]._stringlen = flen; // with compression flag
_entries[i]._type = RES_STRING; // type normalization
p += lslen;
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + lslen > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(lslen) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -748,32 +266,23 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
case RES_TENSOR :
case RES_LONG_DATA: {
-
uint32_t flen;
uint32_t ldlen;
if (p + sizeof(flen) <= ebuf) {
-
memcpy(&flen, p, sizeof(flen));
p += sizeof(flen);
-
ldlen = flen & 0x7fffffff;
-
if (p + ldlen <= ebuf) {
-
_entries[i]._dataval = const_cast<char *>(p);
_entries[i]._datalen = flen; // with compression flag
_entries[i]._type = RES_DATA; // type normalization
p += ldlen;
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + ldlen > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
}
-
} else {
-
LOG(debug, "GeneralResult::_inplace_unpack: p + sizeof(ldlen) > ebuf");
LOG(error, "Document summary too short, couldn't unpack");
rc = false;
@@ -782,9 +291,7 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
}
default:
- LOG(warning,
- "GeneralResult::_inplace_unpack: no such type:%d",
- entry->_type);
+ LOG(warning, "GeneralResult::_inplace_unpack: no such type:%d", entry->_type);
LOG(error, "Incorrect type in document summary, couldn't unpack");
rc = false;
break;
@@ -803,9 +310,9 @@ GeneralResult::_inplace_unpack(const char *buf, const size_t buflen)
// clean up on failure
FreeEntries();
_entrycnt = 0;
- _entries = NULL;
- _buf = NULL;
- _bufEnd = NULL;
+ _entries = nullptr;
+ _buf = nullptr;
+ _bufEnd = nullptr;
return false; // FAIL
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/urlresult.h b/searchsummary/src/vespa/searchsummary/docsummary/urlresult.h
index 4d1fca0992d..a4cdd1b7f69 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/urlresult.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/urlresult.h
@@ -7,37 +7,7 @@
namespace search::docsummary {
-class urlresult
-{
-protected:
- uint32_t _partition;
- uint32_t _docid;
- HitRank _metric;
-
-public:
- urlresult(uint32_t partition, uint32_t docid, HitRank metric);
- virtual ~urlresult();
-
- virtual bool IsGeneral() const { return false; }
- uint32_t GetPartition() const { return _partition; }
- uint32_t GetDocID() const { return _docid; }
- HitRank GetMetric() const { return _metric; }
- virtual int unpack(const char *buf, const size_t buflen) = 0;
-};
-
-
-class badurlresult : public urlresult
-{
-public:
- badurlresult();
- badurlresult(uint32_t partition, uint32_t docid, HitRank metric);
- ~badurlresult() override;
-
- int unpack(const char *buf, const size_t buflen) override;
-};
-
-
-class GeneralResult : public urlresult
+class GeneralResult
{
private:
GeneralResult(const GeneralResult &);
@@ -49,31 +19,27 @@ private:
char *_buf; // allocated in same chunk as _entries
char *_bufEnd; // first byte after _buf
- bool InBuf(void *pt) {
- return ((char *)pt >= _buf &&
- (char *)pt < _bufEnd);
+ bool InBuf(const void *pt) const {
+ return ((const char *)pt >= _buf &&
+ (const char *)pt < _bufEnd);
}
void AllocEntries(uint32_t buflen, bool inplace = false);
void FreeEntries();
- bool _inplace_unpack(const char *buf, const size_t buflen);
-
public:
- GeneralResult(const ResultClass *resClass, uint32_t partition,
- uint32_t docid, HitRank metric);
+ GeneralResult(const ResultClass *resClass);
~GeneralResult();
const ResultClass *GetClass() const { return _resClass; }
ResEntry *GetEntry(uint32_t idx);
ResEntry *GetEntry(const char *name);
ResEntry *GetEntryFromEnumValue(uint32_t val);
- bool IsGeneral() const override { return true; }
- int unpack(const char *buf, const size_t buflen) override;
+ bool unpack(const char *buf, const size_t buflen);
bool inplaceUnpack(const DocsumStoreValue &value) {
if (value.valid()) {
- return _inplace_unpack(value.fieldsPt(), value.fieldsSz());
+ return unpack(value.fieldsPt(), value.fieldsSz());
} else {
return false;
}
diff --git a/staging_vespalib/src/tests/state_server/state_server_test.cpp b/staging_vespalib/src/tests/state_server/state_server_test.cpp
index b688887c3fb..d4f665029cc 100644
--- a/staging_vespalib/src/tests/state_server/state_server_test.cpp
+++ b/staging_vespalib/src/tests/state_server/state_server_test.cpp
@@ -40,7 +40,7 @@ vespalib::string run_cmd(const vespalib::string &cmd) {
}
vespalib::string getPage(int port, const vespalib::string &path, const vespalib::string &extra_params = "") {
- return run_cmd(make_string("curl -s %s http://localhost:%d%s", extra_params.c_str(), port, path.c_str()));
+ return run_cmd(make_string("curl -s %s 'http://localhost:%d%s'", extra_params.c_str(), port, path.c_str()));
}
vespalib::string getFull(int port, const vespalib::string &path) { return getPage(port, path, "-D -"); }
@@ -123,6 +123,50 @@ TEST_FF("require that host is passed correctly", EchoHost(), HttpServer(0)) {
EXPECT_EQUAL(default_result, run_cmd(make_string("curl -s http://localhost:%d/my/path -H \"Host:\"", f2.port())));
}
+struct SamplingHandler : JsonGetHandler {
+ mutable std::mutex my_lock;
+ mutable vespalib::string my_host;
+ mutable vespalib::string my_path;
+ mutable std::map<vespalib::string,vespalib::string> my_params;
+ vespalib::string get(const vespalib::string &host, const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params) const override
+ {
+ {
+ auto guard = std::lock_guard(my_lock);
+ my_host = host;
+ my_path = path;
+ my_params = params;
+ }
+ return "[]";
+ }
+};
+
+TEST_FF("require that request parameters can be inspected", SamplingHandler(), HttpServer(0))
+{
+ auto token = f2.repo().bind("/foo", f1);
+ EXPECT_EQUAL("[]", getPage(f2.port(), "/foo?a=b&x=y&z"));
+ {
+ auto guard = std::lock_guard(f1.my_lock);
+ EXPECT_EQUAL(f1.my_path, "/foo");
+ EXPECT_EQUAL(f1.my_params.size(), 3u);
+ EXPECT_EQUAL(f1.my_params["a"], "b");
+ EXPECT_EQUAL(f1.my_params["x"], "y");
+ EXPECT_EQUAL(f1.my_params["z"], "");
+ EXPECT_EQUAL(f1.my_params.size(), 3u); // "z" was present
+ }
+}
+
+TEST_FF("require that request path is dequoted", SamplingHandler(), HttpServer(0))
+{
+ auto token = f2.repo().bind("/[foo]", f1);
+ EXPECT_EQUAL("[]", getPage(f2.port(), "/%5bfoo%5D"));
+ {
+ auto guard = std::lock_guard(f1.my_lock);
+ EXPECT_EQUAL(f1.my_path, "/[foo]");
+ EXPECT_EQUAL(f1.my_params.size(), 0u);
+ }
+}
+
//-----------------------------------------------------------------------------
TEST_FFFF("require that the state server wires the appropriate url prefixes",
diff --git a/staging_vespalib/src/vespa/vespalib/net/http_server.cpp b/staging_vespalib/src/vespa/vespalib/net/http_server.cpp
index 99a66fccde5..2a3bb5b3e0e 100644
--- a/staging_vespalib/src/vespa/vespalib/net/http_server.cpp
+++ b/staging_vespalib/src/vespa/vespalib/net/http_server.cpp
@@ -8,7 +8,7 @@ namespace vespalib {
void
HttpServer::get(Portal::GetRequest req)
{
- vespalib::string json_result = _handler_repo.get(req.get_host(), req.get_uri(), {});
+ vespalib::string json_result = _handler_repo.get(req.get_host(), req.get_path(), req.export_params());
if (json_result.empty()) {
req.respond_with_error(404, "Not Found");
} else {
diff --git a/staging_vespalib/src/vespa/vespalib/util/xmlserializable.cpp b/staging_vespalib/src/vespa/vespalib/util/xmlserializable.cpp
index 9272f7b0f94..35110be87ca 100644
--- a/staging_vespalib/src/vespa/vespalib/util/xmlserializable.cpp
+++ b/staging_vespalib/src/vespa/vespalib/util/xmlserializable.cpp
@@ -4,8 +4,7 @@
#include "xmlstream.h"
#include <sstream>
-namespace vespalib {
-namespace xml {
+namespace vespalib::xml {
std::string
XmlSerializable::toXml(const std::string& indent) const
@@ -16,5 +15,4 @@ XmlSerializable::toXml(const std::string& indent) const
return ost.str();
}
-} // xml
-} // vespalib
+}
diff --git a/staging_vespalib/src/vespa/vespalib/util/xmlserializable.h b/staging_vespalib/src/vespa/vespalib/util/xmlserializable.h
index c0d41910479..4c00a734b2b 100644
--- a/staging_vespalib/src/vespa/vespalib/util/xmlserializable.h
+++ b/staging_vespalib/src/vespa/vespalib/util/xmlserializable.h
@@ -4,8 +4,7 @@
#include <string>
-namespace vespalib {
-namespace xml {
+namespace vespalib::xml {
class XmlOutputStream;
@@ -26,8 +25,9 @@ public:
virtual std::string toXml(const std::string& indent = "") const;
};
-} // xml
+}
+namespace vespalib {
// The XmlSerializable and XmlOutputStream is often used in header files
// and is thus available in the vespalib namespace. To not pollute the
// vespalib namespace with all the other classes, use
diff --git a/vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java b/vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java
index 1d20a06d0da..bd149e5a41e 100644
--- a/vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java
+++ b/vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java
@@ -15,6 +15,8 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
@@ -38,6 +40,7 @@ public class ApplicationMojo extends AbstractMojo {
File applicationPackage = new File(project.getBasedir(), sourceDir);
File applicationDestination = new File(project.getBasedir(), destinationDir);
copyApplicationPackage(applicationPackage, applicationDestination);
+ addBuildMetaData(applicationDestination);
File componentsDir = createComponentsDir(applicationDestination);
copyModuleBundles(project.getBasedir(), componentsDir);
@@ -50,6 +53,24 @@ public class ApplicationMojo extends AbstractMojo {
}
}
+ /** Writes meta data about this package if the destination directory exists, and the "vespaversion" property is set. */
+ private void addBuildMetaData(File applicationDestination) throws MojoExecutionException {
+ String compileVersion = project.getProperties().getProperty("vespaversion");
+ if ( ! applicationDestination.exists() || compileVersion == null)
+ return;
+
+ String metaData = String.format("{\"compileVersion\": \"%s\",\n \"buildTime\": %d}",
+ compileVersion,
+ System.currentTimeMillis());
+ try {
+ Files.write(applicationDestination.toPath().resolve("build-meta.json"),
+ metaData.getBytes(StandardCharsets.UTF_8));
+ }
+ catch (IOException e) {
+ throw new MojoExecutionException("Failed writing compile version and build time.", e);
+ }
+ }
+
private void copyBundlesForSubModules(File componentsDir) throws MojoExecutionException {
List<String> modules = emptyListIfNull(project.getModules());
for (String module : modules) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java
index 98d9061be02..cd35a204b00 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java
@@ -18,6 +18,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
+import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
/**
@@ -117,6 +118,9 @@ public class SiaUtils {
public static List<AthenzService> findSiaServices(Path root) {
String keyFileSuffix = ".key.pem";
Path keysDirectory = root.resolve("keys");
+ if ( ! Files.exists(keysDirectory))
+ return emptyList();
+
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(keysDirectory)) {
return StreamSupport.stream(directoryStream.spliterator(), false)
.map(path -> path.getFileName().toString())
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java
index 9337bb94c23..f69e937f294 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java
@@ -11,8 +11,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import static java.util.Collections.emptyList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
@@ -26,6 +28,7 @@ public class SiaUtilsTest {
@Test
public void it_finds_all_identity_names_from_files_in_sia_keys_directory() throws IOException {
Path siaRoot = tempDirectory.getRoot().toPath();
+ assertThat(SiaUtils.findSiaServices(siaRoot), is(emptyList()));
Files.createDirectory(siaRoot.resolve("keys"));
AthenzService fooService = new AthenzService("my.domain.foo");
Files.createFile(SiaUtils.getPrivateKeyFile(siaRoot, fooService));
diff --git a/vespalib/src/tests/portal/http_request/http_request_test.cpp b/vespalib/src/tests/portal/http_request/http_request_test.cpp
index 6e1527efa4b..047fde5750c 100644
--- a/vespalib/src/tests/portal/http_request/http_request_test.cpp
+++ b/vespalib/src/tests/portal/http_request/http_request_test.cpp
@@ -2,6 +2,7 @@
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/portal/http_request.h>
+#include <vespa/vespalib/util/stringfmt.h>
using namespace vespalib;
using namespace vespalib::portal;
@@ -117,4 +118,58 @@ TEST("require that header line must contain separator") {
"missing separator\r\n"));
}
+TEST("require that uri parameters can be parsed") {
+ auto req = make_request("GET /my/path?foo=bar&baz HTTP/1.1\r\n\r\n");
+ EXPECT_EQUAL(req.get_uri(), "/my/path?foo=bar&baz");
+ EXPECT_EQUAL(req.get_path(), "/my/path");
+ EXPECT_TRUE(req.has_param("foo"));
+ EXPECT_TRUE(!req.has_param("bar"));
+ EXPECT_TRUE(req.has_param("baz"));
+ EXPECT_EQUAL(req.get_param("foo"), "bar");
+ EXPECT_EQUAL(req.get_param("bar"), "");
+ EXPECT_EQUAL(req.get_param("baz"), "");
+}
+
+TEST("require that byte values in uri segments (path, key, value) are dequoted as expected") {
+ vespalib::string str = "0123456789aBcDeF";
+ for (size_t a = 0; a < 16; ++a) {
+ for (size_t b = 0; b < 16; ++b) {
+ vespalib::string expect = " foo ";
+ expect.push_back((a * 16) + b);
+ expect.push_back((a * 16) + b);
+ expect.append(" bar ");
+ vespalib::string input = vespalib::make_string("+foo+%%%c%c%%%c%c+bar+",
+ str[a], str[b], str[a], str[b]);
+ vespalib::string uri = vespalib::make_string("%s?%s=%s&extra=yes",
+ input.c_str(), input.c_str(), input.c_str());
+ auto req = make_request(vespalib::make_string("GET %s HTTP/1.1\r\n\r\n",
+ uri.c_str()));
+ EXPECT_EQUAL(req.get_uri(), uri);
+ EXPECT_EQUAL(req.get_path(), expect);
+ EXPECT_TRUE(req.has_param(expect));
+ EXPECT_EQUAL(req.get_param(expect), expect);
+ EXPECT_TRUE(req.has_param("extra"));
+ EXPECT_EQUAL(req.get_param("extra"), "yes");
+ }
+ }
+}
+
+TEST("require that percent character becomes plain if not followed by exactly 2 hex digits") {
+ auto req = make_request("GET %/5%5:%@5%5G%`5%5g%5?% HTTP/1.1\r\n\r\n");
+ EXPECT_EQUAL(req.get_path(), "%/5%5:%@5%5G%`5%5g%5");
+ EXPECT_TRUE(req.has_param("%"));
+}
+
+TEST("require that last character of uri segments (path, key, value) can be quoted") {
+ auto req = make_request("GET /%41?%42=%43 HTTP/1.1\r\n\r\n");
+ EXPECT_EQUAL(req.get_path(), "/A");
+ EXPECT_EQUAL(req.get_param("B"), "C");
+}
+
+TEST("require that additional query and key/value separators are not special") {
+ auto req = make_request("GET /?" "?== HTTP/1.1\r\n\r\n");
+ EXPECT_EQUAL(req.get_path(), "/");
+ EXPECT_EQUAL(req.get_param("?"), "=");
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/portal/portal_test.cpp b/vespalib/src/tests/portal/portal_test.cpp
index 299340fd131..ee5d10a313a 100644
--- a/vespalib/src/tests/portal/portal_test.cpp
+++ b/vespalib/src/tests/portal/portal_test.cpp
@@ -346,4 +346,40 @@ TEST_MT_FFF("require that portal destruction waits for request completion", 3,
//-----------------------------------------------------------------------------
+TEST("require that query parameters can be inspected") {
+ auto portal = Portal::create(null_crypto(), 0);
+ MyGetHandler handler([](Portal::GetRequest request)
+ {
+ EXPECT_EQUAL(request.get_uri(), "/test?a=b&x=y");
+ EXPECT_EQUAL(request.get_path(), "/test");
+ EXPECT_TRUE(request.has_param("a"));
+ EXPECT_TRUE(request.has_param("x"));
+ EXPECT_TRUE(!request.has_param("b"));
+ EXPECT_EQUAL(request.get_param("a"), "b");
+ EXPECT_EQUAL(request.get_param("x"), "y");
+ EXPECT_EQUAL(request.get_param("b"), "");
+ auto params = request.export_params();
+ EXPECT_EQUAL(params.size(), 2u);
+ EXPECT_EQUAL(params["a"], "b");
+ EXPECT_EQUAL(params["x"], "y");
+ request.respond_with_content("a", "b");
+ });
+ auto bound = portal->bind("/test", handler);
+ auto result = fetch(portal->listen_port(), null_crypto(), "/test?a=b&x=y");
+ EXPECT_EQUAL(result, make_expected_response("a", "b"));
+}
+
+TEST("require that request path is dequoted before handler dispatching") {
+ auto portal = Portal::create(null_crypto(), 0);
+ MyGetHandler handler([](Portal::GetRequest request)
+ {
+ EXPECT_EQUAL(request.get_uri(), "/%5btest%5D");
+ EXPECT_EQUAL(request.get_path(), "/[test]");
+ request.respond_with_content("a", "b");
+ });
+ auto bound = portal->bind("/[test]", handler);
+ auto result = fetch(portal->listen_port(), null_crypto(), "/%5btest%5D");
+ EXPECT_EQUAL(result, make_expected_response("a", "b"));
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/vespa/vespalib/portal/http_request.cpp b/vespalib/src/vespa/vespalib/portal/http_request.cpp
index d49fc2e70f4..abd690897c6 100644
--- a/vespalib/src/vespa/vespalib/portal/http_request.cpp
+++ b/vespalib/src/vespa/vespalib/portal/http_request.cpp
@@ -15,11 +15,11 @@ void strip_cr(vespalib::string &str) {
}
}
-std::vector<vespalib::string> split(vespalib::stringref str, vespalib::stringref sep) {
+std::vector<vespalib::string> split(vespalib::stringref str, char sep) {
vespalib::string token;
std::vector<vespalib::string> list;
for (char c: str) {
- if (sep.find(c) == vespalib::stringref::npos) {
+ if (c != sep) {
token.push_back(c);
} else if (!token.empty()) {
list.push_back(token);
@@ -32,6 +32,49 @@ std::vector<vespalib::string> split(vespalib::stringref str, vespalib::stringref
return list;
}
+int decode_hex_digit(char c) {
+ if ((c >= '0') && (c <= '9')) {
+ return (c - '0');
+ }
+ if ((c >= 'a') && (c <= 'f')) {
+ return ((c - 'a') + 10);
+ }
+ if ((c >= 'A') && (c <= 'F')) {
+ return ((c - 'A') + 10);
+ }
+ return -1;
+}
+
+int decode_hex_num(vespalib::stringref src, size_t idx) {
+ if (src.size() < (idx + 2)) {
+ return -1;
+ }
+ int a = decode_hex_digit(src[idx]);
+ int b = decode_hex_digit(src[idx + 1]);
+ if ((a < 0) || (b < 0)) {
+ return -1;
+ }
+ return ((a << 4) | b);
+}
+
+vespalib::string dequote(vespalib::stringref src) {
+ vespalib::string dst;
+ for (size_t idx = 0; idx < src.size(); ++idx) {
+ char c = src[idx];
+ if (c == '+') {
+ c = ' ';
+ } else if (c == '%') {
+ int x = decode_hex_num(src, idx + 1);
+ if (x >= 0) {
+ c = x;
+ idx += 2;
+ }
+ }
+ dst.push_back(c);
+ }
+ return dst;
+}
+
} // namespace vespalib::portal::<unnamed>
void
@@ -49,13 +92,30 @@ HttpRequest::set_error()
void
HttpRequest::handle_request_line(const vespalib::string &line)
{
- auto parts = split(line, " ");
+ auto parts = split(line, ' ');
if (parts.size() != 3) {
return set_error(); // malformed request line
}
_method = parts[0];
_uri = parts[1];
_version = parts[2];
+ size_t query_sep = _uri.find("?");
+ if (query_sep == vespalib::string::npos) {
+ _path = dequote(_uri);
+ } else {
+ _path = dequote(_uri.substr(0, query_sep));
+ auto query = split(_uri.substr(query_sep + 1), '&');
+ for (const auto &param: query) {
+ size_t value_sep = param.find("=");
+ if (value_sep == vespalib::string::npos) {
+ _params[dequote(param)] = "";
+ } else {
+ auto key = param.substr(0, value_sep);
+ auto value = param.substr(value_sep + 1);
+ _params[dequote(key)] = dequote(value);
+ }
+ }
+ }
}
void
@@ -163,4 +223,20 @@ HttpRequest::get_header(const vespalib::string &name) const
return pos->second;
}
+bool
+HttpRequest::has_param(const vespalib::string &name) const
+{
+ return (_params.find(name) != _params.end());
+}
+
+const vespalib::string &
+HttpRequest::get_param(const vespalib::string &name) const
+{
+ auto pos = _params.find(name);
+ if (pos == _params.end()) {
+ return _empty;
+ }
+ return pos->second;
+}
+
} // namespace vespalib::portal
diff --git a/vespalib/src/vespa/vespalib/portal/http_request.h b/vespalib/src/vespa/vespalib/portal/http_request.h
index 51c7ab08da9..39467c3b248 100644
--- a/vespalib/src/vespa/vespalib/portal/http_request.h
+++ b/vespalib/src/vespa/vespalib/portal/http_request.h
@@ -14,6 +14,8 @@ private:
// http stuff
vespalib::string _method;
vespalib::string _uri;
+ vespalib::string _path;
+ std::map<vespalib::string, vespalib::string> _params;
vespalib::string _version;
std::map<vespalib::string, vespalib::string> _headers;
vespalib::string _host;
@@ -43,6 +45,10 @@ public:
const vespalib::string &get_header(const vespalib::string &name) const;
const vespalib::string &get_host() const { return _host; }
const vespalib::string &get_uri() const { return _uri; }
+ const vespalib::string &get_path() const { return _path; }
+ bool has_param(const vespalib::string &name) const;
+ const vespalib::string &get_param(const vespalib::string &name) const;
+ std::map<vespalib::string, vespalib::string> export_params() const { return _params; }
};
} // namespace vespalib::portal
diff --git a/vespalib/src/vespa/vespalib/portal/portal.cpp b/vespalib/src/vespa/vespalib/portal/portal.cpp
index 0d62d5728d1..ec2f1b78c03 100644
--- a/vespalib/src/vespa/vespalib/portal/portal.cpp
+++ b/vespalib/src/vespa/vespalib/portal/portal.cpp
@@ -48,6 +48,34 @@ Portal::GetRequest::get_uri() const
return _conn->get_request().get_uri();
}
+const vespalib::string &
+Portal::GetRequest::get_path() const
+{
+ assert(active());
+ return _conn->get_request().get_path();
+}
+
+bool
+Portal::GetRequest::has_param(const vespalib::string &name) const
+{
+ assert(active());
+ return _conn->get_request().has_param(name);
+}
+
+const vespalib::string &
+Portal::GetRequest::get_param(const vespalib::string &name) const
+{
+ assert(active());
+ return _conn->get_request().get_param(name);
+}
+
+std::map<vespalib::string, vespalib::string>
+Portal::GetRequest::export_params() const
+{
+ assert(active());
+ return _conn->get_request().export_params();
+}
+
void
Portal::GetRequest::respond_with_content(const vespalib::string &content_type,
const vespalib::string &content)
@@ -131,7 +159,7 @@ Portal::handle_http(portal::HttpConnection *conn)
conn->respond_with_error(501, "Not Implemented");
} else {
GetHandler *get_handler = nullptr;
- auto guard = lookup_get_handler(conn->get_request().get_uri(), get_handler);
+ auto guard = lookup_get_handler(conn->get_request().get_path(), get_handler);
if (guard.valid()) {
assert(get_handler != nullptr);
conn->resolve_host(_my_host);
diff --git a/vespalib/src/vespa/vespalib/portal/portal.h b/vespalib/src/vespa/vespalib/portal/portal.h
index 93424dda90c..fc3b81b37e6 100644
--- a/vespalib/src/vespa/vespalib/portal/portal.h
+++ b/vespalib/src/vespa/vespalib/portal/portal.h
@@ -60,6 +60,10 @@ public:
const vespalib::string &get_header(const vespalib::string &name) const;
const vespalib::string &get_host() const;
const vespalib::string &get_uri() const;
+ const vespalib::string &get_path() const;
+ bool has_param(const vespalib::string &name) const;
+ const vespalib::string &get_param(const vespalib::string &name) const;
+ std::map<vespalib::string, vespalib::string> export_params() const;
void respond_with_content(const vespalib::string &content_type,
const vespalib::string &content);
void respond_with_error(int code, const vespalib::string &msg);
diff --git a/vsm/src/vespa/vsm/vsm/docsumconfig.cpp b/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
index 25c13967c49..ab89f4d460f 100644
--- a/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
+++ b/vsm/src/vespa/vsm/vsm/docsumconfig.cpp
@@ -20,7 +20,7 @@ DynamicDocsumConfig::createFieldWriter(const string & fieldName, const string &
(overrideName == "absdist") ||
(overrideName == "subproject"))
{
- fieldWriter.reset(new EmptyDFW());
+ fieldWriter = std::make_unique<EmptyDFW>();
rc = true;
} else if ((overrideName == "attribute") ||
(overrideName == "attributecombiner") ||