summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2022-04-07 13:58:39 +0200
committerJon Bratseth <bratseth@gmail.com>2022-04-07 13:58:39 +0200
commitb68f4ba22e523c39e0ca734d7c8627477518c553 (patch)
tree6968efa48259acb6788c2edfab0858de8c8f4765
parenta6656689f92f2bcd45c07491aada64740669d5c5 (diff)
parent4c3de59b341522a53e3ebbf8ad40bd2b12aff86e (diff)
Merge branch 'master' into bratseth/inputs
-rw-r--r--application-model/pom.xml6
-rw-r--r--application-model/src/main/java/com/yahoo/vespa/applicationmodel/TenantId.java2
-rw-r--r--application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java10
-rw-r--r--bundle-plugin/pom.xml4
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/TestBundleDependencyScopeTranslator.java5
-rw-r--r--client/go/cmd/cert_test.go9
-rw-r--r--client/go/cmd/deploy.go25
-rw-r--r--client/go/cmd/document.go6
-rw-r--r--client/go/cmd/log_test.go4
-rw-r--r--client/go/cmd/query.go6
-rw-r--r--client/go/cmd/root.go14
-rw-r--r--client/go/cmd/testutil_test.go19
-rw-r--r--client/go/mock/vespa.go39
-rw-r--r--client/go/vespa/deploy.go48
-rw-r--r--client/go/vespa/deploy_test.go89
-rw-r--r--client/go/vespa/version.go5
-rw-r--r--cloud-tenant-base-dependencies-enforcer/pom.xml1
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java2
-rw-r--r--config-model/.gitignore1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java2
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java2
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java15
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/parser/ConvertParsedFields.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java216
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java5
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java4
-rw-r--r--config-model/src/main/javacc/IntermediateParser.jj1
-rw-r--r--config-model/src/test/configmodel/types/documenttypes.cfg895
-rw-r--r--config-model/src/test/configmodel/types/documenttypes_with_doc_field.cfg110
-rw-r--r--config-model/src/test/configmodel/types/references/documenttypes_multiple_imported_fields.cfg191
-rw-r--r--config-model/src/test/configmodel/types/references/documenttypes_refs_to_other_types.cfg158
-rw-r--r--config-model/src/test/configmodel/types/references/documenttypes_refs_to_same_type.cfg124
-rw-r--r--config-model/src/test/derived/duplicate_struct/documenttypes.cfg184
-rw-r--r--config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg205
-rw-r--r--config-model/src/test/derived/inheritfromparent/documenttypes.cfg155
-rw-r--r--config-model/src/test/derived/multi_struct/documenttypes.cfg318
-rw-r--r--config-model/src/test/derived/structandfieldset/attributes.cfg96
-rw-r--r--config-model/src/test/derived/structandfieldset/test.sd5
-rw-r--r--config-model/src/test/derived/structinheritance/documenttypes.cfg179
-rw-r--r--config-model/src/test/derived/tensor/documenttypes.cfg136
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java66
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java39
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java39
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java39
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/ApplicationIdTest.java2
-rw-r--r--configdefinitions/src/vespa/configserver.def4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java22
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStream.java124
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java11
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java15
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java104
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java13
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java5
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/documentapi/DocumentAccessProvider.java4
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/documentapi/VespaDocumentAccess.java3
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/LogFileHandler.java4
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/HttpURL.java451
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/Path.java80
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java4
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java8
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java202
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/PathTest.java17
-rw-r--r--container-dev/pom.xml4
-rw-r--r--container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java9
-rw-r--r--container-messagebus/src/test/java/com/yahoo/container/jdisc/messagebus/MbusClientProviderTest.java1
-rw-r--r--container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java26
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java1
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java8
-rw-r--r--container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java6
-rw-r--r--container-test/pom.xml5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MailerException.java14
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringClient.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceAllocation.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMeteringClient.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java6
-rw-r--r--controller-server/pom.xml2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java99
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReader.java138
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesHandler.java77
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java83
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java45
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java85
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java40
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java108
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java54
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntriesTest.java50
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReaderTest.java114
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java60
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java72
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java34
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java47
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/metering.json6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java2
-rw-r--r--dist/vespa.spec10
-rw-r--r--docproc/src/main/java/com/yahoo/docproc/jdisc/messagebus/MessageFactory.java5
-rw-r--r--document/src/main/java/com/yahoo/document/datatypes/StringFieldValue.java13
-rw-r--r--document/src/main/java/com/yahoo/document/idstring/IdString.java7
-rw-r--r--document/src/tests/repo/doctype_config_test.cpp10
-rw-r--r--document/src/tests/repo/import-dt.cfg139
-rw-r--r--document/src/vespa/document/base/fieldpath.cpp37
-rw-r--r--document/src/vespa/document/base/fieldpath.h37
-rw-r--r--document/src/vespa/document/fieldvalue/iteratorhandler.h2
-rw-r--r--document/src/vespa/document/fieldvalue/mapfieldvalue.cpp6
-rw-r--r--document/src/vespa/document/fieldvalue/variablemap.cpp20
-rw-r--r--document/src/vespa/document/fieldvalue/variablemap.h8
-rw-r--r--document/src/vespa/document/repo/documenttyperepo.cpp186
-rw-r--r--document/src/vespa/document/repo/documenttyperepo.h3
-rw-r--r--document/src/vespa/document/select/branch.cpp28
-rw-r--r--document/src/vespa/document/select/compare.cpp14
-rw-r--r--document/src/vespa/document/select/resultlist.cpp11
-rw-r--r--document/src/vespa/document/select/resultlist.h8
-rw-r--r--document/src/vespa/document/select/value.cpp16
-rw-r--r--document/src/vespa/document/select/valuenodes.cpp4
-rw-r--r--document/src/vespa/document/update/fieldpathupdate.cpp4
-rw-r--r--documentapi/abi-spec.json4
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/VisitorParameters.java11
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusDocumentAccess.java1
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusParams.java9
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadType.java2
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadTypeSet.java6
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentMessage.java15
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java18
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java8
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java10
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java10
-rw-r--r--documentapi/src/test/java/com/yahoo/documentapi/VisitorParametersTestCase.java6
-rw-r--r--documentapi/src/test/java/com/yahoo/documentapi/messagebus/loadtypes/test/LoadTypesTestCase.java2
-rw-r--r--documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java8
-rwxr-xr-xdocumentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java3
-rwxr-xr-xdocumentapi/src/test/java/com/yahoo/documentapi/messagebus/test/MessageBusVisitorSessionTestCase.java3
-rw-r--r--fastos/src/vespa/fastos/app.cpp12
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java11
-rw-r--r--fnet/src/tests/frt/detach_supervisor/detach_supervisor_test.cpp1
-rw-r--r--fnet/src/vespa/fnet/controlpacket.cpp5
-rw-r--r--fnet/src/vespa/fnet/controlpacket.h6
-rw-r--r--fnet/src/vespa/fnet/frt/supervisor.cpp6
-rw-r--r--fnet/src/vespa/fnet/transport.cpp18
-rw-r--r--fnet/src/vespa/fnet/transport.h17
-rw-r--r--fnet/src/vespa/fnet/transport_thread.cpp33
-rw-r--r--fnet/src/vespa/fnet/transport_thread.h23
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngine.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/ContainerData.java12
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java30
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectory.java10
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java39
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java30
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerFailTest.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/MultiContainerTest.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RebootTest.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java26
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java3
-rw-r--r--parent/pom.xml11
-rw-r--r--routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/nginx/NginxMetricsReporter.java2
-rwxr-xr-xscrewdriver/release-java-artifacts.sh10
-rw-r--r--screwdriver/settings-publish.xml2
-rwxr-xr-xscrewdriver/update-vespa-version-in-sample-apps.sh24
-rw-r--r--searchcore/src/apps/proton/proton.cpp21
-rw-r--r--searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp8
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/shared_threading_service.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/shared_threading_service.h6
-rw-r--r--searchlib/src/tests/fef/resolver/resolver_test.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributeiterators.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributeiterators.hpp19
-rw-r--r--searchlib/src/vespa/searchlib/attribute/flagattribute.cpp39
-rw-r--r--searchlib/src/vespa/searchlib/attribute/flagattribute.h23
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.cpp50
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.h39
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h10
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h10
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h38
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/expression/documentfieldnode.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/expression/documentfieldnode.h8
-rw-r--r--searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp26
-rw-r--r--searchlib/src/vespa/searchlib/fef/blueprintresolver.h6
-rw-r--r--searchlib/src/vespa/searchlib/fef/verify_feature.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/streamed_value_store.h2
-rw-r--r--storage/src/tests/storageserver/mergethrottlertest.cpp1
-rw-r--r--storage/src/vespa/storage/storageserver/documentapiconverter.cpp1
-rw-r--r--testutil/src/main/java/com/yahoo/test/OrderTester.java6
-rw-r--r--testutil/src/main/java/com/yahoo/test/TotalOrderTester.java2
-rw-r--r--vespa-hadoop/pom.xml16
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java12
-rwxr-xr-xvespaclient-core/src/main/java/com/yahoo/feedapi/MessageBusSessionFactory.java3
-rw-r--r--vespaclient-core/src/main/java/com/yahoo/feedapi/MessagePropertyProcessor.java20
-rwxr-xr-xvespaclient-java/src/main/java/com/yahoo/dummyreceiver/DummyReceiver.java3
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespafeeder/Arguments.java2
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespaget/DocumentRetriever.java16
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespaget/Main.java3
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java3
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisitTarget.java3
-rw-r--r--vespaclient-java/src/test/java/com/yahoo/vespaget/DocumentRetrieverTest.java3
-rw-r--r--vespajlib/abi-spec.json2
-rw-r--r--vespajlib/pom.xml5
-rw-r--r--vespajlib/src/main/java/ai/vespa/validation/Name.java2
-rw-r--r--vespajlib/src/main/java/ai/vespa/validation/Validation.java16
-rw-r--r--vespajlib/src/main/java/ai/vespa/validation/package-info.java5
-rw-r--r--vespajlib/src/main/java/com/yahoo/compress/ArchiveStreamReader.java216
-rw-r--r--vespajlib/src/main/java/com/yahoo/compress/ZstdOutputStream.java (renamed from vespajlib/src/main/java/com/yahoo/compress/ZstdOuputStream.java)6
-rw-r--r--vespajlib/src/main/java/com/yahoo/io/NativeIO.java103
-rw-r--r--vespajlib/src/main/java/com/yahoo/path/Path.java5
-rw-r--r--vespajlib/src/main/java/com/yahoo/text/Text.java65
-rw-r--r--vespajlib/src/test/java/com/yahoo/compress/ArchiveStreamReaderTest.java131
-rw-r--r--vespajlib/src/test/java/com/yahoo/compress/ZstdOutputStreamTest.java (renamed from vespajlib/src/test/java/com/yahoo/compress/ZstdOuputStreamTest.java)6
-rw-r--r--vespajlib/src/test/java/com/yahoo/text/TextTestCase.java70
-rw-r--r--vespalib/src/tests/slime/slime_binary_format_test.cpp4
-rw-r--r--vespalib/src/tests/wakeup/wakeup_bench.cpp4
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodestore.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_type.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_type.hpp16
-rw-r--r--vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp3
-rw-r--r--vespalib/src/vespa/vespalib/util/array.hpp4
-rw-r--r--vespalib/src/vespa/vespalib/util/arrayref.h2
-rw-r--r--vespalib/src/vespa/vespalib/util/fiddle.h4
-rw-r--r--vespalib/src/vespa/vespalib/util/rcuvector.h10
-rw-r--r--vespamalloc/src/vespamalloc/malloc/common.h2
-rw-r--r--vespamalloc/src/vespamalloc/malloc/datasegment.cpp1
-rw-r--r--vespamalloc/src/vespamalloc/malloc/freelist.hpp1
-rw-r--r--vespamalloc/src/vespamalloc/malloc/memorywatcher.h1
-rw-r--r--vespamalloc/src/vespamalloc/malloc/mmappool.cpp1
-rw-r--r--vespamalloc/src/vespamalloc/malloc/mmappool.h1
-rw-r--r--vespamalloc/src/vespamalloc/util/osmem.cpp2
-rw-r--r--vsm/src/tests/docsum/docsum.cpp8
-rw-r--r--vsm/src/vespa/vsm/common/documenttypemapping.cpp6
-rw-r--r--vsm/src/vespa/vsm/common/storagedocument.h4
-rw-r--r--vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp5
-rw-r--r--vsm/src/vespa/vsm/vsm/docsumfieldspec.h7
-rw-r--r--vsm/src/vespa/vsm/vsm/docsumfilter.cpp34
-rw-r--r--zkfacade/pom.xml4
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java2
-rw-r--r--zookeeper-command-line-client/pom.xml5
-rw-r--r--zookeeper-server/zookeeper-server-3.7.0/pom.xml2
312 files changed, 5083 insertions, 3554 deletions
diff --git a/application-model/pom.xml b/application-model/pom.xml
index 3abf9851d5c..7eac247e249 100644
--- a/application-model/pom.xml
+++ b/application-model/pom.xml
@@ -23,6 +23,12 @@
</dependency>
<dependency>
<groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespajlib</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
<artifactId>config-provisioning</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/TenantId.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/TenantId.java
index 7d94a0418e4..20aca379015 100644
--- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/TenantId.java
+++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/TenantId.java
@@ -31,8 +31,6 @@ public class TenantId {
return id;
}
- public TenantName toName() { return TenantName.from(id); }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
index 1fc4d08b405..5039b05b393 100644
--- a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
+++ b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
@@ -2,17 +2,15 @@
package com.yahoo.application.preprocessor;
import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Optional;
-
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
public class ApplicationPreprocessorTest {
diff --git a/bundle-plugin/pom.xml b/bundle-plugin/pom.xml
index b1b03b60ce6..d53c2c94d5c 100644
--- a/bundle-plugin/pom.xml
+++ b/bundle-plugin/pom.xml
@@ -21,12 +21,12 @@
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
- <version>3.5.0</version>
+ <version>3.8.5</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-archiver</artifactId>
- <version>3.5.0</version>
+ <version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/TestBundleDependencyScopeTranslator.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/TestBundleDependencyScopeTranslator.java
index d922b63c24c..bd6151aea9f 100644
--- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/TestBundleDependencyScopeTranslator.java
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/TestBundleDependencyScopeTranslator.java
@@ -35,7 +35,10 @@ public class TestBundleDependencyScopeTranslator implements Artifacts.ScopeTrans
this.dependencyScopes = dependencyScopes;
}
- @Override public String scopeOf(Artifact artifact) { return Objects.requireNonNull(dependencyScopes.get(artifact)); }
+ @Override
+ public String scopeOf(Artifact artifact) {
+ return Objects.requireNonNull(dependencyScopes.get(artifact), () -> "Could not lookup scope for " + artifact);
+ }
public static TestBundleDependencyScopeTranslator from(Map<String, Artifact> dependencies, String rawConfig) {
List<DependencyOverride> dependencyOverrides = toDependencyOverrides(rawConfig);
diff --git a/client/go/cmd/cert_test.go b/client/go/cmd/cert_test.go
index e5837170d15..ee0c21adaf5 100644
--- a/client/go/cmd/cert_test.go
+++ b/client/go/cmd/cert_test.go
@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -25,7 +26,7 @@ func TestCert(t *testing.T) {
}
func testCert(t *testing.T, subcommand []string) {
- pkgDir := mockApplicationPackage(t, false)
+ appDir, pkgDir := mock.ApplicationPackageDir(t, false, false)
cli, stdout, stderr := newTestCLI(t)
args := append(subcommand, "-a", "t1.a1.i1", pkgDir)
@@ -35,7 +36,6 @@ func testCert(t *testing.T, subcommand []string) {
app, err := vespa.ApplicationFromString("t1.a1.i1")
assert.Nil(t, err)
- appDir := filepath.Join(pkgDir, "src", "main", "application")
pkgCertificate := filepath.Join(appDir, "security", "clients.pem")
homeDir := cli.config.homeDir
certificate := filepath.Join(homeDir, app.String(), "data-plane-public-cert.pem")
@@ -59,7 +59,7 @@ func TestCertCompressedPackage(t *testing.T) {
}
func testCertCompressedPackage(t *testing.T, subcommand []string) {
- pkgDir := mockApplicationPackage(t, true)
+ _, pkgDir := mock.ApplicationPackageDir(t, true, false)
zipFile := filepath.Join(pkgDir, "target", "application.zip")
err := os.MkdirAll(filepath.Dir(zipFile), 0755)
assert.Nil(t, err)
@@ -88,11 +88,10 @@ func TestCertAdd(t *testing.T) {
err := cli.Run("auth", "cert", "-N", "-a", "t1.a1.i1")
assert.Nil(t, err)
- pkgDir := mockApplicationPackage(t, false)
+ appDir, pkgDir := mock.ApplicationPackageDir(t, false, false)
stdout.Reset()
err = cli.Run("auth", "cert", "add", "-a", "t1.a1.i1", pkgDir)
assert.Nil(t, err)
- appDir := filepath.Join(pkgDir, "src", "main", "application")
pkgCertificate := filepath.Join(appDir, "security", "clients.pem")
assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\n", pkgCertificate), stdout.String())
diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go
index a287165bb5e..16da69fded6 100644
--- a/client/go/cmd/deploy.go
+++ b/client/go/cmd/deploy.go
@@ -12,12 +12,14 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
+ "github.com/vespa-engine/vespa/client/go/version"
"github.com/vespa-engine/vespa/client/go/vespa"
)
func newDeployCmd(cli *CLI) *cobra.Command {
var (
logLevelArg string
+ versionArg string
)
cmd := &cobra.Command{
Use: "deploy [application-directory]",
@@ -32,7 +34,12 @@ If application directory is not specified, it defaults to working directory.
When deploying to Vespa Cloud the system can be overridden by setting the
environment variable VESPA_CLI_CLOUD_SYSTEM. This is intended for internal use
-only.`,
+only.
+
+In Vespa Cloud you may override the Vespa runtime version for your deployment.
+This option should only be used if you have a reason for using a specific
+version. By default Vespa Cloud chooses a suitable version for you.
+`,
Example: `$ vespa deploy .
$ vespa deploy -t cloud
$ vespa deploy -t cloud -z dev.aws-us-east-1c # -z can be omitted here as this zone is the default
@@ -50,6 +57,13 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
return err
}
opts := cli.createDeploymentOptions(pkg, target)
+ if versionArg != "" {
+ version, err := version.Parse(versionArg)
+ if err != nil {
+ return err
+ }
+ opts.Version = version
+ }
var result vespa.PrepareResult
err = cli.spinner(cli.Stderr, "Uploading application package ...", func() error {
@@ -75,10 +89,11 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
opts.Target.Deployment().Application.Instance, opts.Target.Deployment().Zone.Environment, opts.Target.Deployment().Zone.Region,
result.ID)))
}
- return waitForQueryService(cli, result.ID)
+ return waitForQueryService(cli, target, result.ID)
},
}
cmd.Flags().StringVarP(&logLevelArg, "log-level", "l", "error", `Log level for Vespa logs. Must be "error", "warning", "info" or "debug"`)
+ cmd.Flags().StringVarP(&versionArg, "version", "V", "", `Override the Vespa runtime version to use in Vespa Cloud`)
return cmd
}
@@ -142,15 +157,15 @@ func newActivateCmd(cli *CLI) *cobra.Command {
return err
}
cli.printSuccess("Activated ", color.CyanString(pkg.Path), " with session ", sessionID)
- return waitForQueryService(cli, sessionID)
+ return waitForQueryService(cli, target, sessionID)
},
}
}
-func waitForQueryService(cli *CLI, sessionOrRunID int64) error {
+func waitForQueryService(cli *CLI, target vespa.Target, sessionOrRunID int64) error {
if cli.flags.waitSecs > 0 {
log.Println()
- _, err := cli.service(vespa.QueryService, sessionOrRunID, "")
+ _, err := cli.service(target, vespa.QueryService, sessionOrRunID, "")
return err
}
return nil
diff --git a/client/go/cmd/document.go b/client/go/cmd/document.go
index ac135edad4c..32648393492 100644
--- a/client/go/cmd/document.go
+++ b/client/go/cmd/document.go
@@ -174,7 +174,11 @@ func newDocumentGetCmd(cli *CLI) *cobra.Command {
}
func documentService(cli *CLI) (*vespa.Service, error) {
- return cli.service(vespa.DocumentService, 0, "")
+ target, err := cli.target(targetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ return cli.service(target, vespa.DocumentService, 0, "")
}
func operationOptions(stderr io.Writer, printCurl bool, timeoutSecs int) vespa.OperationOptions {
diff --git a/client/go/cmd/log_test.go b/client/go/cmd/log_test.go
index d3e7b630869..d7b5e20fab5 100644
--- a/client/go/cmd/log_test.go
+++ b/client/go/cmd/log_test.go
@@ -10,7 +10,7 @@ import (
)
func TestLog(t *testing.T) {
- pkgDir := mockApplicationPackage(t, false)
+ _, pkgDir := mock.ApplicationPackageDir(t, false, false)
httpClient := &mock.HTTPClient{}
httpClient.NextResponseString(200, `1632738690.905535 host1a.dev.aws-us-east-1c 806/53 logserver-container Container.com.yahoo.container.jdisc.ConfiguredApplication info Switching to the latest deployed set of configurations and components. Application config generation: 52532`)
cli, stdout, stderr := newTestCLI(t)
@@ -34,7 +34,7 @@ func TestLogOldClient(t *testing.T) {
cli, _, stderr := newTestCLI(t)
cli.version = version.MustParse("7.0.0")
- pkgDir := mockApplicationPackage(t, false)
+ _, pkgDir := mock.ApplicationPackageDir(t, false, false)
httpClient := &mock.HTTPClient{}
httpClient.NextResponseString(200, `{"minVersion": "8.0.0"}`)
httpClient.NextResponseString(200, `1632738690.905535 host1a.dev.aws-us-east-1c 806/53 logserver-container Container.com.yahoo.container.jdisc.ConfiguredApplication info Switching to the latest deployed set of configurations and components. Application config generation: 52532`)
diff --git a/client/go/cmd/query.go b/client/go/cmd/query.go
index c5868e2f71c..7abf17d5358 100644
--- a/client/go/cmd/query.go
+++ b/client/go/cmd/query.go
@@ -58,7 +58,11 @@ func printCurl(stderr io.Writer, url string, service *vespa.Service) error {
}
func query(cli *CLI, arguments []string, timeoutSecs int, curl bool) error {
- service, err := cli.service(vespa.QueryService, 0, "")
+ target, err := cli.target(targetOptions{})
+ if err != nil {
+ return err
+ }
+ service, err := cli.service(target, vespa.QueryService, 0, "")
if err != nil {
return err
}
diff --git a/client/go/cmd/root.go b/client/go/cmd/root.go
index 387c72a1f17..452b1f30834 100644
--- a/client/go/cmd/root.go
+++ b/client/go/cmd/root.go
@@ -406,19 +406,15 @@ func (c *CLI) system(targetType string) (vespa.System, error) {
return vespa.System{}, fmt.Errorf("no default system found for %s target", targetType)
}
-// service returns the service identified by given name and optionally cluster. This function blocks according to the
-// wait period configured in this CLI. The parameter sessionOrRunID specifies either the session ID (local target) or
-// run ID (cloud target) to wait for.
-func (c *CLI) service(name string, sessionOrRunID int64, cluster string) (*vespa.Service, error) {
- t, err := c.target(targetOptions{})
- if err != nil {
- return nil, err
- }
+// service returns the service of given name located at target. If non-empty, cluster specifies a cluster to query. This
+// function blocks according to the wait period configured in this CLI. The parameter sessionOrRunID specifies either
+// the session ID (local target) or run ID (cloud target) to wait for.
+func (c *CLI) service(target vespa.Target, name string, sessionOrRunID int64, cluster string) (*vespa.Service, error) {
timeout := time.Duration(c.flags.waitSecs) * time.Second
if timeout > 0 {
log.Printf("Waiting up to %s %s for %s service to become available ...", color.CyanString(strconv.Itoa(c.flags.waitSecs)), color.CyanString("seconds"), color.CyanString(name))
}
- s, err := t.Service(name, timeout, sessionOrRunID, cluster)
+ s, err := target.Service(name, timeout, sessionOrRunID, cluster)
if err != nil {
return nil, fmt.Errorf("service '%s' is unavailable: %w", name, err)
}
diff --git a/client/go/cmd/testutil_test.go b/client/go/cmd/testutil_test.go
index 68f79187d3a..e5c69e38e93 100644
--- a/client/go/cmd/testutil_test.go
+++ b/client/go/cmd/testutil_test.go
@@ -3,7 +3,6 @@ package cmd
import (
"bytes"
- "os"
"path/filepath"
"testing"
@@ -29,21 +28,3 @@ func newTestCLI(t *testing.T, envVars ...string) (*CLI, *bytes.Buffer, *bytes.Bu
cli.exec = &mock.Exec{}
return cli, &stdout, &stderr
}
-
-func mockApplicationPackage(t *testing.T, java bool) string {
- dir := t.TempDir()
- appDir := filepath.Join(dir, "src", "main", "application")
- if err := os.MkdirAll(appDir, 0755); err != nil {
- t.Fatal(err)
- }
- servicesXML := filepath.Join(appDir, "services.xml")
- if _, err := os.Create(servicesXML); err != nil {
- t.Fatal(err)
- }
- if java {
- if _, err := os.Create(filepath.Join(dir, "pom.xml")); err != nil {
- t.Fatal(err)
- }
- }
- return dir
-}
diff --git a/client/go/mock/vespa.go b/client/go/mock/vespa.go
new file mode 100644
index 00000000000..ca09a389360
--- /dev/null
+++ b/client/go/mock/vespa.go
@@ -0,0 +1,39 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package mock
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+// ApplicationPackageDir creates a mock application package directory using test helper t, returning the path to the
+// "application" directory and the root directory where it was created. If java is true, create a file that indicates
+// this package contains Java code. If cert is true, create an empty certificate file.
+func ApplicationPackageDir(t *testing.T, java, cert bool) (string, string) {
+ t.Helper()
+ rootDir := t.TempDir()
+ appDir := filepath.Join(rootDir, "src", "main", "application")
+ if err := os.MkdirAll(appDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ servicesXML := filepath.Join(appDir, "services.xml")
+ if _, err := os.Create(servicesXML); err != nil {
+ t.Fatal(err)
+ }
+ if java {
+ if _, err := os.Create(filepath.Join(rootDir, "pom.xml")); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if cert {
+ securityDir := filepath.Join(appDir, "security")
+ if err := os.MkdirAll(securityDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := os.Create(filepath.Join(securityDir, "clients.pem")); err != nil {
+ t.Fatal(err)
+ }
+ }
+ return appDir, rootDir
+}
diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go
index d479c86a4c7..4fa0bb5b839 100644
--- a/client/go/vespa/deploy.go
+++ b/client/go/vespa/deploy.go
@@ -12,11 +12,13 @@ import (
"mime/multipart"
"net/http"
"net/url"
+ "path/filepath"
"strconv"
"strings"
"time"
"github.com/vespa-engine/vespa/client/go/util"
+ "github.com/vespa-engine/vespa/client/go/version"
)
var DefaultApplication = ApplicationID{Tenant: "default", Application: "application", Instance: "default"}
@@ -42,6 +44,7 @@ type DeploymentOptions struct {
Target Target
ApplicationPackage ApplicationPackage
Timeout time.Duration
+ Version version.Version
HTTPClient util.HTTPClient
}
@@ -259,21 +262,56 @@ func checkDeploymentOpts(opts DeploymentOptions) error {
if opts.Target.Type() == TargetCloud && !opts.ApplicationPackage.HasCertificate() {
return fmt.Errorf("%s: missing certificate in package", opts)
}
+ if !opts.IsCloud() && !opts.Version.IsZero() {
+ return fmt.Errorf("%s: custom runtime version is not supported by %s target", opts, opts.Target.Type())
+ }
return nil
}
-func uploadApplicationPackage(url *url.URL, opts DeploymentOptions) (PrepareResult, error) {
+func newDeploymentRequest(url *url.URL, opts DeploymentOptions) (*http.Request, error) {
zipReader, err := opts.ApplicationPackage.zipReader(false)
if err != nil {
- return PrepareResult{}, err
+ return nil, err
}
+ var body io.Reader
header := http.Header{}
- header.Add("Content-Type", "application/zip")
- request := &http.Request{
+ if opts.IsCloud() {
+ var buf bytes.Buffer
+ form := multipart.NewWriter(&buf)
+ formFile, err := form.CreateFormFile("applicationZip", filepath.Base(opts.ApplicationPackage.Path))
+ if err != nil {
+ return nil, err
+ }
+ if _, err := io.Copy(formFile, zipReader); err != nil {
+ return nil, err
+ }
+ if !opts.Version.IsZero() {
+ deployOptions := fmt.Sprintf(`{"vespaVersion":"%s"}`, opts.Version.String())
+ if err := form.WriteField("deployOptions", deployOptions); err != nil {
+ return nil, err
+ }
+ }
+ if err := form.Close(); err != nil {
+ return nil, err
+ }
+ header.Set("Content-Type", form.FormDataContentType())
+ body = &buf
+ } else {
+ header.Set("Content-Type", "application/zip")
+ body = zipReader
+ }
+ return &http.Request{
URL: url,
Method: "POST",
Header: header,
- Body: io.NopCloser(zipReader),
+ Body: io.NopCloser(body),
+ }, nil
+}
+
+func uploadApplicationPackage(url *url.URL, opts DeploymentOptions) (PrepareResult, error) {
+ request, err := newDeploymentRequest(url, opts)
+ if err != nil {
+ return PrepareResult{}, err
}
service, err := opts.Target.Service(DeployService, opts.Timeout, 0, "")
if err != nil {
diff --git a/client/go/vespa/deploy_test.go b/client/go/vespa/deploy_test.go
index f27a2f2927d..297d028ee91 100644
--- a/client/go/vespa/deploy_test.go
+++ b/client/go/vespa/deploy_test.go
@@ -2,14 +2,75 @@
package vespa
import (
+ "io"
+ "mime"
+ "mime/multipart"
+ "net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/mock"
+ "github.com/vespa-engine/vespa/client/go/version"
)
+func TestDeploy(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ target := LocalTarget(&httpClient)
+ appDir, _ := mock.ApplicationPackageDir(t, false, false)
+ opts := DeploymentOptions{
+ Target: target,
+ ApplicationPackage: ApplicationPackage{Path: appDir},
+ HTTPClient: &httpClient,
+ }
+ _, err := Deploy(opts)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, len(httpClient.Requests))
+ req := httpClient.LastRequest
+ assert.Equal(t, "http://127.0.0.1:19071/application/v2/tenant/default/prepareandactivate", req.URL.String())
+ assert.Equal(t, "application/zip", req.Header.Get("content-type"))
+ buf := make([]byte, 5)
+ req.Body.Read(buf)
+ assert.Equal(t, "PK\x03\x04\x14", string(buf))
+}
+
+func TestDeployCloud(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ target := createCloudTarget(t, "http://vespacloud", io.Discard)
+ cloudTarget, ok := target.(*cloudTarget)
+ require.True(t, ok)
+ cloudTarget.httpClient = &httpClient
+ appDir, _ := mock.ApplicationPackageDir(t, false, true)
+ opts := DeploymentOptions{
+ Target: target,
+ ApplicationPackage: ApplicationPackage{Path: appDir},
+ HTTPClient: &httpClient,
+ }
+ _, err := Deploy(opts)
+ require.Nil(t, err)
+ assert.Equal(t, 1, len(httpClient.Requests))
+ req := httpClient.LastRequest
+ assert.Equal(t, "http://vespacloud/application/v4/tenant/t1/application/a1/instance/i1/deploy/dev-us-north-1", req.URL.String())
+
+ values := parseMultiPart(t, req)
+ zipData := values["applicationZip"]
+ assert.Equal(t, "PK\x03\x04\x14", string(zipData[:5]))
+ _, hasDeployOptions := values["deployOptions"]
+ assert.False(t, hasDeployOptions)
+
+ opts.Version = version.MustParse("1.2.3")
+ _, err = Deploy(opts)
+ require.Nil(t, err)
+ req = httpClient.LastRequest
+ values = parseMultiPart(t, req)
+ zipData = values["applicationZip"]
+ assert.Equal(t, "PK\x03\x04\x14", string(zipData[:5]))
+ assert.Equal(t, string(values["deployOptions"]), `{"vespaVersion":"1.2.3"}`)
+}
+
func TestApplicationFromString(t *testing.T) {
app, err := ApplicationFromString("t1.a1.i1")
assert.Nil(t, err)
@@ -65,6 +126,7 @@ type pkgFixture struct {
}
func assertFindApplicationPackage(t *testing.T, zipOrDir string, fixture pkgFixture) {
+ t.Helper()
if fixture.existingFile != "" {
writeFile(t, fixture.existingFile)
}
@@ -77,6 +139,7 @@ func assertFindApplicationPackage(t *testing.T, zipOrDir string, fixture pkgFixt
}
func writeFile(t *testing.T, name string) {
+ t.Helper()
err := os.MkdirAll(filepath.Dir(name), 0755)
assert.Nil(t, err)
if !strings.HasSuffix(name, string(os.PathSeparator)) {
@@ -84,3 +147,29 @@ func writeFile(t *testing.T, name string) {
assert.Nil(t, err)
}
}
+
+func parseMultiPart(t *testing.T, req *http.Request) map[string][]byte {
+ t.Helper()
+
+ mediaType, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
+ require.Nil(t, err)
+ assert.Equal(t, mediaType, "multipart/form-data")
+
+ values := make(map[string][]byte)
+ mr := multipart.NewReader(req.Body, params["boundary"])
+ for {
+ p, err := mr.NextPart()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ data, err := io.ReadAll(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ values[p.FormName()] = data
+ }
+ return values
+}
diff --git a/client/go/vespa/version.go b/client/go/vespa/version.go
deleted file mode 100644
index b20c6d360d7..00000000000
--- a/client/go/vespa/version.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package vespa
-
-// Version is the Vespa CLI version number
-var Version string = "0.0.0-devel" // Overriden by linker flag as part of build
diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml
index 21e5de31534..b5281050459 100644
--- a/cloud-tenant-base-dependencies-enforcer/pom.xml
+++ b/cloud-tenant-base-dependencies-enforcer/pom.xml
@@ -236,6 +236,7 @@
<include>org.antlr:antlr-runtime:3.5.2:jar:test</include>
<include>org.antlr:antlr4-runtime:4.9.3:jar:test</include>
<include>org.apache.commons:commons-exec:1.3:jar:test</include>
+ <include>org.apache.commons:commons-compress:1.21:jar:test</include>
<include>org.apache.commons:commons-math3:3.6.1:jar:test</include>
<include>org.apache.httpcomponents.client5:httpclient5:${httpclient5.version}:jar:test</include>
<include>org.apache.httpcomponents.core5:httpcore5:${httpclient5.version}:jar:test</include>
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index a410b025e6b..9453f489029 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -120,7 +120,7 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"arnej"}) default boolean avoidRenamingSummaryFeatures() { return false; }
@ModelFeatureFlag(owners = {"bjorncs", "baldersheim"}, removeAfter = "7.569") default boolean mergeGroupingResultInSearchInvoker() { return true; }
@ModelFeatureFlag(owners = {"arnej"}) default boolean experimentalSdParsing() { return false; }
- @ModelFeatureFlag(owners = {"hmusum"}, removeAfter = "7.564") default String adminClusterNodeArchitecture() { return adminClusterArchitecture().name(); }
+ @ModelFeatureFlag(owners = {"hmusum"}, removeAfter = "7.571") default String adminClusterNodeArchitecture() { return adminClusterArchitecture().name(); }
@ModelFeatureFlag(owners = {"hmusum"}) default Architecture adminClusterArchitecture() { return Architecture.getDefault(); }
}
diff --git a/config-model/.gitignore b/config-model/.gitignore
index 4cf50da0853..6edd041cbe8 100644
--- a/config-model/.gitignore
+++ b/config-model/.gitignore
@@ -5,3 +5,4 @@
/src/test/integration/*/copy/
/src/test/integration/*/models.generated/
*.cfg.actual
+/var/
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
index cb1e2e047ad..8366dde383b 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
@@ -81,7 +81,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private boolean useV8GeoPositions = false;
private List<String> environmentVariables = List.of();
private boolean avoidRenamingSummaryFeatures = false;
- private boolean experimentalSdParsing = false;
+ private boolean experimentalSdParsing = true;
private Architecture adminClusterNodeResourcesArchitecture = Architecture.getDefault();
@Override public ModelContext.FeatureFlags featureFlags() { return this; }
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
index 824bf248b5c..1838f1e36b7 100644
--- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
+++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
@@ -99,7 +99,7 @@ public class MockApplicationPackage implements ApplicationPackage {
@SuppressWarnings("deprecation") // not redundant
@Override
public String getApplicationName() {
- return "mock application";
+ return "mock-application";
}
@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 dad8385e0e1..7acf5557236 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
@@ -100,17 +100,21 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
}
}
+ private void applyRanking(ImmutableSDField field, Attribute attribute) {
+ Ranking ranking = field.getRanking();
+ if (ranking != null && ranking.isFilter()) {
+ attribute.setEnableBitVectors(true);
+ attribute.setEnableOnlyBitVector(true);
+ }
+ }
+
private void deriveAttribute(ImmutableSDField field, Attribute fieldAttribute) {
Attribute attribute = getAttribute(fieldAttribute.getName());
if (attribute == null) {
attributes.put(fieldAttribute.getName(), fieldAttribute);
attribute = getAttribute(fieldAttribute.getName());
}
- Ranking ranking = field.getRanking();
- if (ranking != null && ranking.isFilter()) {
- attribute.setEnableBitVectors(true);
- attribute.setEnableOnlyBitVector(true);
- }
+ applyRanking(field, attribute);
}
private void deriveImportedAttributes(ImmutableSDField field) {
@@ -134,6 +138,7 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
}
Attribute attribute = field.getAttributes().get(field.getName());
if (attribute != null) {
+ applyRanking(field, attribute);
attributes.put(attribute.getName(), attribute.convertToArray());
}
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ConvertParsedFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ConvertParsedFields.java
index dee9b648228..92e099bc1fe 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ConvertParsedFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ConvertParsedFields.java
@@ -159,6 +159,15 @@ public class ConvertParsedFields {
for (var structField : parsed.getStructFields()) {
convertStructField(field, structField);
}
+ if (parsed.hasLiteral()) {
+ field.getRanking().setLiteral(true);
+ }
+ if (parsed.hasFilter()) {
+ field.getRanking().setFilter(true);
+ }
+ if (parsed.hasNormal()) {
+ field.getRanking().setNormal(true);
+ }
}
private void convertStructField(SDField field, ParsedField parsed) {
@@ -196,15 +205,6 @@ public class ConvertParsedFields {
summaryField.addDestination("default");
summaryField.setTransform(summaryField.getTransform().bold());
}
- if (parsed.hasLiteral()) {
- field.getRanking().setLiteral(true);
- }
- if (parsed.hasFilter()) {
- field.getRanking().setFilter(true);
- }
- if (parsed.hasNormal()) {
- field.getRanking().setNormal(true);
- }
}
static void convertSummaryFieldSettings(SummaryField summary, ParsedSummaryField parsed) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java
index 630e9f0c097..ba41fa0f5b3 100644
--- a/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java
+++ b/config-model/src/main/java/com/yahoo/vespa/configmodel/producers/DocumentTypes.java
@@ -27,29 +27,16 @@ public class DocumentTypes {
}
public DocumenttypesConfig.Builder produce(DocumentModel model, DocumenttypesConfig.Builder builder) {
- /* later:
- if (some flag) {
- return produceDocTypes(model, builder);
- }
- */
builder.usev8geopositions(this.useV8GeoPositions);
Map<NewDocumentType.Name, NewDocumentType> produced = new HashMap<>();
+ var indexMap = new IdxMap();
for (NewDocumentType documentType : model.getDocumentManager().getTypes()) {
- produceInheritOrder(documentType, builder, produced);
+ docTypeInheritOrder(documentType, builder, produced, indexMap);
}
+ indexMap.verifyAllDone();
return builder;
}
- private void produceInheritOrder(NewDocumentType documentType, DocumenttypesConfig.Builder builder, Map<NewDocumentType.Name, NewDocumentType> produced) {
- if (!produced.containsKey(documentType.getFullName())) {
- for (NewDocumentType inherited : documentType.getInherited()) {
- produceInheritOrder(inherited, builder, produced);
- }
- buildConfig(documentType, builder);
- produced.put(documentType.getFullName(), documentType);
- }
- }
-
static private <T> List<T> sortedList(Collection<T> unsorted, Comparator<T> cmp) {
var list = new ArrayList<T>();
list.addAll(unsorted);
@@ -57,203 +44,6 @@ public class DocumentTypes {
return list;
}
- private void buildConfig(NewDocumentType documentType, DocumenttypesConfig.Builder builder) {
- if (documentType == VespaDocumentType.INSTANCE) {
- return;
- }
- DocumenttypesConfig.Documenttype.Builder db = new DocumenttypesConfig.Documenttype.Builder();
- db.
- id(documentType.getId()).
- name(documentType.getName()).
- headerstruct(documentType.getContentStruct().getId());
- Set<Integer> built = new HashSet<>();
- for (NewDocumentType inherited : documentType.getInherited()) {
- db.inherits(new DocumenttypesConfig.Documenttype.Inherits.Builder().id(inherited.getId()));
- markAsBuilt(built, inherited.getAllTypes());
- }
- for (DataType dt : sortedList(documentType.getTypes(), (a,b) -> a.getName().compareTo(b.getName()))) {
- buildConfig(dt, db, built);
- }
- for (AnnotationType annotation : sortedList(documentType.getAnnotations(), (a,b) -> a.getName().compareTo(b.getName()))) {
- DocumenttypesConfig.Documenttype.Annotationtype.Builder atb = new DocumenttypesConfig.Documenttype.Annotationtype.Builder();
- db.annotationtype(atb);
- buildConfig(annotation, atb);
- }
- buildConfig(documentType.getFieldSets(), db);
- buildImportedFieldsConfig(documentType.getImportedFieldNames(), db);
- builder.documenttype(db);
- }
-
- private void buildConfig(Set<FieldSet> fieldSets, DocumenttypesConfig.Documenttype.Builder db) {
- for (FieldSet fs : fieldSets) {
- buildConfig(fs, db);
- }
- }
-
- private void buildConfig(FieldSet fs, DocumenttypesConfig.Documenttype.Builder db) {
- db.fieldsets(fs.getName(), new DocumenttypesConfig.Documenttype.Fieldsets.Builder().fields(fs.getFieldNames()));
- }
-
- private void markAsBuilt(Set<Integer> built, DataTypeCollection typeCollection) {
- for (DataType type : typeCollection.getTypes()) {
- built.add(type.getId());
- }
- }
-
- private void buildConfig(AnnotationType annotation, DocumenttypesConfig.Documenttype.Annotationtype.Builder builder) {
- builder.
- id(annotation.getId()).
- name(annotation.getName());
- DataType dt = annotation.getDataType();
- if (dt != null) {
- builder.datatype(dt.getId());
- }
- for (AnnotationType inherited : annotation.getInheritedTypes()) {
- builder.inherits(new DocumenttypesConfig.Documenttype.Annotationtype.Inherits.Builder().id(inherited.getId()));
- }
- }
-
- private void buildConfig(DataType type, DocumenttypesConfig.Documenttype.Builder documentBuilder, Set<Integer> built) {
- if ((VespaDocumentType.INSTANCE.getDataType(type.getId()) == null) && !built.contains(type.getId())) {
- built.add(type.getId());
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder = new DocumenttypesConfig.Documenttype.Datatype.Builder();
- dataTypeBuilder.id(type.getId());
- if (type instanceof TemporaryUnknownType) {
- throw new IllegalArgumentException("Can not create config for temporary data type: " + type.getName());
- }
- if (type instanceof OwnedTemporaryType) {
- throw new IllegalArgumentException("Can not create config for temporary data type: " + type.getName());
- }
- if (type instanceof StructDataType) {
- buildConfig((StructDataType) type, dataTypeBuilder, documentBuilder, built);
- } else if (type instanceof ArrayDataType) {
- buildConfig((ArrayDataType) type, dataTypeBuilder, documentBuilder, built);
- } else if (type instanceof WeightedSetDataType) {
- buildConfig((WeightedSetDataType) type, dataTypeBuilder, documentBuilder, built);
- } else if (type instanceof MapDataType) {
- buildConfig((MapDataType) type, dataTypeBuilder, documentBuilder, built);
- } else if (type instanceof AnnotationReferenceDataType) {
- buildConfig((AnnotationReferenceDataType) type, dataTypeBuilder);
- } else if (type instanceof TensorDataType) {
- // The type of the tensor is not stored here but instead in each field as detailed type information
- // to provide better compatibility. A tensor field can have its tensorType changed (in compatible ways)
- // without changing the field type and thus requiring data refeed
- return;
- } else if (type instanceof NewDocumentReferenceDataType) {
- var refType = (NewDocumentReferenceDataType) type;
- if (refType.isTemporary()) {
- throw new IllegalArgumentException("Still temporary: " + refType);
- }
- buildConfig(refType, documentBuilder);
- return;
- } else {
- return;
- }
- documentBuilder.datatype(dataTypeBuilder);
- }
- }
-
- private void buildImportedFieldsConfig(Collection<String> fieldNames, DocumenttypesConfig.Documenttype.Builder builder) {
- for (String fieldName : fieldNames) {
- var ib = new DocumenttypesConfig.Documenttype.Importedfield.Builder();
- ib.name(fieldName);
- builder.importedfield(ib);
- }
- }
-
- private void buildConfig(StructDataType type,
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder,
- DocumenttypesConfig.Documenttype.Builder documentBuilder,
- Set<Integer> built) {
- dataTypeBuilder.type(DocumenttypesConfig.Documenttype.Datatype.Type.Enum.STRUCT);
- DocumenttypesConfig.Documenttype.Datatype.Sstruct.Builder structBuilder = new DocumenttypesConfig.Documenttype.Datatype.Sstruct.Builder();
- dataTypeBuilder.sstruct(structBuilder);
- structBuilder.name(type.getName());
- for (com.yahoo.document.Field field : type.getFields()) {
- DocumenttypesConfig.Documenttype.Datatype.Sstruct.Field.Builder builder =
- new DocumenttypesConfig.Documenttype.Datatype.Sstruct.Field.Builder();
- builder.name(field.getName()).
- id(field.getId()).
- datatype(field.getDataType().getId());
- if (field.getDataType() instanceof TensorDataType) {
- builder.detailedtype(((TensorDataType) field.getDataType()).getTensorType().toString());
- }
- structBuilder.field(builder);
- buildConfig(field.getDataType(), documentBuilder, built);
- }
- }
-
- private void buildConfig(ArrayDataType type,
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder,
- DocumenttypesConfig.Documenttype.Builder documentBuilder,
- Set<Integer> built) {
- dataTypeBuilder.
- type(DocumenttypesConfig.Documenttype.Datatype.Type.Enum.ARRAY).
- array(new DocumenttypesConfig.Documenttype.Datatype.Array.Builder().
- element(new DocumenttypesConfig.Documenttype.Datatype.Array.Element.Builder().id(type.getNestedType().getId())));
- buildConfig(type.getNestedType(), documentBuilder, built);
- }
-
- private void buildConfig(WeightedSetDataType type,
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder,
- DocumenttypesConfig.Documenttype.Builder documentBuilder,
- Set<Integer> built) {
- dataTypeBuilder.type(DocumenttypesConfig.Documenttype.Datatype.Type.Enum.WSET).
- wset(new DocumenttypesConfig.Documenttype.Datatype.Wset.Builder().
- key(new DocumenttypesConfig.Documenttype.Datatype.Wset.Key.Builder().
- id(type.getNestedType().getId())).
- createifnonexistent(type.createIfNonExistent()).
- removeifzero(type.removeIfZero()));
- buildConfig(type.getNestedType(), documentBuilder, built);
- }
-
- private void buildConfig(MapDataType type,
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder,
- DocumenttypesConfig.Documenttype.Builder documentBuilder,
- Set<Integer> built) {
- dataTypeBuilder.
- type(DocumenttypesConfig.Documenttype.Datatype.Type.Enum.MAP).
- map(new DocumenttypesConfig.Documenttype.Datatype.Map.Builder().
- key(new DocumenttypesConfig.Documenttype.Datatype.Map.Key.Builder().
- id(type.getKeyType().getId())).
- value(new DocumenttypesConfig.Documenttype.Datatype.Map.Value.Builder().
- id(type.getValueType().getId())));
- buildConfig(type.getKeyType(), documentBuilder, built);
- buildConfig(type.getValueType(), documentBuilder, built);
- }
-
- private void buildConfig(AnnotationReferenceDataType type,
- DocumenttypesConfig.Documenttype.Datatype.Builder dataTypeBuilder) {
- dataTypeBuilder.
- type(DocumenttypesConfig.Documenttype.Datatype.Type.Enum.ANNOTATIONREF).
- annotationref(new DocumenttypesConfig.Documenttype.Datatype.Annotationref.Builder().
- annotation(new DocumenttypesConfig.Documenttype.Datatype.Annotationref.Annotation.Builder().
- id(type.getAnnotationType().getId())));
- }
-
- private void buildConfig(NewDocumentReferenceDataType type,
- DocumenttypesConfig.Documenttype.Builder documentBuilder) {
- NewDocumentReferenceDataType refType = type;
- DocumenttypesConfig.Documenttype.Referencetype.Builder refBuilder =
- new DocumenttypesConfig.Documenttype.Referencetype.Builder();
- refBuilder.id(refType.getId());
- refBuilder.target_type_id(type.getTargetTypeId());
- documentBuilder.referencetype(refBuilder);
- }
-
- // Alternate (new) way to build config:
-
- private DocumenttypesConfig.Builder produceDocTypes(DocumentModel model, DocumenttypesConfig.Builder builder) {
- builder.usev8geopositions(this.useV8GeoPositions);
- Map<NewDocumentType.Name, NewDocumentType> produced = new HashMap<>();
- var indexMap = new IdxMap();
- for (NewDocumentType documentType : model.getDocumentManager().getTypes()) {
- docTypeInheritOrder(documentType, builder, produced, indexMap);
- }
- indexMap.verifyAllDone();
- return builder;
- }
-
private void docTypeInheritOrder(NewDocumentType documentType,
DocumenttypesConfig.Builder builder,
Map<NewDocumentType.Name, NewDocumentType> produced,
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java
index 47bcc64f663..8b8b9e5f40d 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java
@@ -13,6 +13,7 @@ import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
*
* @author Gunnar Gauslaa Bergem
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class Clients extends ConfigModel {
private static final long serialVersionUID = 1L;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java
index 88739e92567..b41d805f2a7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java
@@ -212,8 +212,9 @@ public class StorageGroup {
if (group.isPresent() && nodes.isPresent())
throw new IllegalArgumentException("Both <group> and <nodes> is specified: Only one of these tags can be used in the same configuration");
- if (group.isPresent() && (group.get().stringAttribute("name") != null || group.get().integerAttribute("distribution-key") != null))
- deployState.getDeployLogger().logApplicationPackage(Level.INFO, "'distribution-key' attribute on a content cluster's root group is ignored");
+ if (group.isPresent() && (group.get().integerAttribute("distribution-key") != null)) {
+ deployState.getDeployLogger().logApplicationPackage(Level.INFO, "'distribution-key' attribute on a content cluster's root group is ignored");
+ }
GroupBuilder groupBuilder = collectGroup(owner.isHosted(), group, nodes, null, null);
StorageGroup storageGroup = owner.isHosted()
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
index ce2bc351d2b..73bd064b967 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
@@ -62,7 +62,6 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
-import static com.yahoo.config.provision.NodeResources.Architecture;
import static com.yahoo.config.provision.NodeResources.DiskSpeed;
import static com.yahoo.config.provision.NodeResources.StorageType;
import static java.util.stream.Collectors.toList;
@@ -337,8 +336,7 @@ public class ContentCluster extends AbstractConfigProducer<AbstractConfigProduce
DeployState deployState,
String clusterName) {
if (admin.getClusterControllers() == null) {
- NodeResources nodeResources = clusterControllerResources
- .with(Architecture.valueOf(deployState.featureFlags().adminClusterNodeArchitecture()));
+ NodeResources nodeResources = clusterControllerResources.with(deployState.featureFlags().adminClusterArchitecture());
NodesSpecification spec = NodesSpecification.requiredFromSharedParents(deployState.zone().environment().isProduction() ? 3 : 1,
nodeResources,
contentElement,
diff --git a/config-model/src/main/javacc/IntermediateParser.jj b/config-model/src/main/javacc/IntermediateParser.jj
index 68b11178cc2..9c7ee206da1 100644
--- a/config-model/src/main/javacc/IntermediateParser.jj
+++ b/config-model/src/main/javacc/IntermediateParser.jj
@@ -938,6 +938,7 @@ void structFieldBody(ParsedField field) : { }
attribute(field) |
matchSettings(field.matchSettings()) |
queryCommand(field) |
+ rank(field) |
structField(field) |
summaryTo(field) )
}
diff --git a/config-model/src/test/configmodel/types/documenttypes.cfg b/config-model/src/test/configmodel/types/documenttypes.cfg
index 0501aa58784..7ea2fa42a2a 100644
--- a/config-model/src/test/configmodel/types/documenttypes.cfg
+++ b/config-model/src/test/configmodel/types/documenttypes.cfg
@@ -1,577 +1,322 @@
enablecompression false
usev8geopositions false
-documenttype[0].id -853072901
-documenttype[0].name "types"
-documenttype[0].version 0
-documenttype[0].headerstruct 1328581348
-documenttype[0].bodystruct 0
-documenttype[0].inherits[0].id 8
-documenttype[0].datatype[0].id -1486737430
-documenttype[0].datatype[0].type ARRAY
-documenttype[0].datatype[0].array.element.id 2
-documenttype[0].datatype[0].map.key.id 0
-documenttype[0].datatype[0].map.value.id 0
-documenttype[0].datatype[0].wset.key.id 0
-documenttype[0].datatype[0].wset.createifnonexistent false
-documenttype[0].datatype[0].wset.removeifzero false
-documenttype[0].datatype[0].annotationref.annotation.id 0
-documenttype[0].datatype[0].sstruct.name ""
-documenttype[0].datatype[0].sstruct.version 0
-documenttype[0].datatype[0].sstruct.compression.type NONE
-documenttype[0].datatype[0].sstruct.compression.level 0
-documenttype[0].datatype[0].sstruct.compression.threshold 95
-documenttype[0].datatype[0].sstruct.compression.minsize 200
-documenttype[0].datatype[1].id 1707615575
-documenttype[0].datatype[1].type ARRAY
-documenttype[0].datatype[1].array.element.id -1486737430
-documenttype[0].datatype[1].map.key.id 0
-documenttype[0].datatype[1].map.value.id 0
-documenttype[0].datatype[1].wset.key.id 0
-documenttype[0].datatype[1].wset.createifnonexistent false
-documenttype[0].datatype[1].wset.removeifzero false
-documenttype[0].datatype[1].annotationref.annotation.id 0
-documenttype[0].datatype[1].sstruct.name ""
-documenttype[0].datatype[1].sstruct.version 0
-documenttype[0].datatype[1].sstruct.compression.type NONE
-documenttype[0].datatype[1].sstruct.compression.level 0
-documenttype[0].datatype[1].sstruct.compression.threshold 95
-documenttype[0].datatype[1].sstruct.compression.minsize 200
-documenttype[0].datatype[2].id -794985308
-documenttype[0].datatype[2].type ARRAY
-documenttype[0].datatype[2].array.element.id 1707615575
-documenttype[0].datatype[2].map.key.id 0
-documenttype[0].datatype[2].map.value.id 0
-documenttype[0].datatype[2].wset.key.id 0
-documenttype[0].datatype[2].wset.createifnonexistent false
-documenttype[0].datatype[2].wset.removeifzero false
-documenttype[0].datatype[2].annotationref.annotation.id 0
-documenttype[0].datatype[2].sstruct.name ""
-documenttype[0].datatype[2].sstruct.version 0
-documenttype[0].datatype[2].sstruct.compression.type NONE
-documenttype[0].datatype[2].sstruct.compression.level 0
-documenttype[0].datatype[2].sstruct.compression.threshold 95
-documenttype[0].datatype[2].sstruct.compression.minsize 200
-documenttype[0].datatype[3].id -372512406
-documenttype[0].datatype[3].type MAP
-documenttype[0].datatype[3].array.element.id 0
-documenttype[0].datatype[3].map.key.id 0
-documenttype[0].datatype[3].map.value.id 1707615575
-documenttype[0].datatype[3].wset.key.id 0
-documenttype[0].datatype[3].wset.createifnonexistent false
-documenttype[0].datatype[3].wset.removeifzero false
-documenttype[0].datatype[3].annotationref.annotation.id 0
-documenttype[0].datatype[3].sstruct.name ""
-documenttype[0].datatype[3].sstruct.version 0
-documenttype[0].datatype[3].sstruct.compression.type NONE
-documenttype[0].datatype[3].sstruct.compression.level 0
-documenttype[0].datatype[3].sstruct.compression.threshold 95
-documenttype[0].datatype[3].sstruct.compression.minsize 200
-documenttype[0].datatype[4].id 1416345047
-documenttype[0].datatype[4].type ARRAY
-documenttype[0].datatype[4].array.element.id -372512406
-documenttype[0].datatype[4].map.key.id 0
-documenttype[0].datatype[4].map.value.id 0
-documenttype[0].datatype[4].wset.key.id 0
-documenttype[0].datatype[4].wset.createifnonexistent false
-documenttype[0].datatype[4].wset.removeifzero false
-documenttype[0].datatype[4].annotationref.annotation.id 0
-documenttype[0].datatype[4].sstruct.name ""
-documenttype[0].datatype[4].sstruct.version 0
-documenttype[0].datatype[4].sstruct.compression.type NONE
-documenttype[0].datatype[4].sstruct.compression.level 0
-documenttype[0].datatype[4].sstruct.compression.threshold 95
-documenttype[0].datatype[4].sstruct.compression.minsize 200
-documenttype[0].datatype[5].id 339965458
-documenttype[0].datatype[5].type MAP
-documenttype[0].datatype[5].array.element.id 0
-documenttype[0].datatype[5].map.key.id 2
-documenttype[0].datatype[5].map.value.id 2
-documenttype[0].datatype[5].wset.key.id 0
-documenttype[0].datatype[5].wset.createifnonexistent false
-documenttype[0].datatype[5].wset.removeifzero false
-documenttype[0].datatype[5].annotationref.annotation.id 0
-documenttype[0].datatype[5].sstruct.name ""
-documenttype[0].datatype[5].sstruct.version 0
-documenttype[0].datatype[5].sstruct.compression.type NONE
-documenttype[0].datatype[5].sstruct.compression.level 0
-documenttype[0].datatype[5].sstruct.compression.threshold 95
-documenttype[0].datatype[5].sstruct.compression.minsize 200
-documenttype[0].datatype[6].id 69621385
-documenttype[0].datatype[6].type ARRAY
-documenttype[0].datatype[6].array.element.id 339965458
-documenttype[0].datatype[6].map.key.id 0
-documenttype[0].datatype[6].map.value.id 0
-documenttype[0].datatype[6].wset.key.id 0
-documenttype[0].datatype[6].wset.createifnonexistent false
-documenttype[0].datatype[6].wset.removeifzero false
-documenttype[0].datatype[6].annotationref.annotation.id 0
-documenttype[0].datatype[6].sstruct.name ""
-documenttype[0].datatype[6].sstruct.version 0
-documenttype[0].datatype[6].sstruct.compression.type NONE
-documenttype[0].datatype[6].sstruct.compression.level 0
-documenttype[0].datatype[6].sstruct.compression.threshold 95
-documenttype[0].datatype[6].sstruct.compression.minsize 200
-documenttype[0].datatype[7].id 49942803
-documenttype[0].datatype[7].type ARRAY
-documenttype[0].datatype[7].array.element.id 16
-documenttype[0].datatype[7].map.key.id 0
-documenttype[0].datatype[7].map.value.id 0
-documenttype[0].datatype[7].wset.key.id 0
-documenttype[0].datatype[7].wset.createifnonexistent false
-documenttype[0].datatype[7].wset.removeifzero false
-documenttype[0].datatype[7].annotationref.annotation.id 0
-documenttype[0].datatype[7].sstruct.name ""
-documenttype[0].datatype[7].sstruct.version 0
-documenttype[0].datatype[7].sstruct.compression.type NONE
-documenttype[0].datatype[7].sstruct.compression.level 0
-documenttype[0].datatype[7].sstruct.compression.threshold 95
-documenttype[0].datatype[7].sstruct.compression.minsize 200
-documenttype[0].datatype[8].id -1245117006
-documenttype[0].datatype[8].type ARRAY
-documenttype[0].datatype[8].array.element.id 0
-documenttype[0].datatype[8].map.key.id 0
-documenttype[0].datatype[8].map.value.id 0
-documenttype[0].datatype[8].wset.key.id 0
-documenttype[0].datatype[8].wset.createifnonexistent false
-documenttype[0].datatype[8].wset.removeifzero false
-documenttype[0].datatype[8].annotationref.annotation.id 0
-documenttype[0].datatype[8].sstruct.name ""
-documenttype[0].datatype[8].sstruct.version 0
-documenttype[0].datatype[8].sstruct.compression.type NONE
-documenttype[0].datatype[8].sstruct.compression.level 0
-documenttype[0].datatype[8].sstruct.compression.threshold 95
-documenttype[0].datatype[8].sstruct.compression.minsize 200
-documenttype[0].datatype[9].id -2092985853
-documenttype[0].datatype[9].type STRUCT
-documenttype[0].datatype[9].array.element.id 0
-documenttype[0].datatype[9].map.key.id 0
-documenttype[0].datatype[9].map.value.id 0
-documenttype[0].datatype[9].wset.key.id 0
-documenttype[0].datatype[9].wset.createifnonexistent false
-documenttype[0].datatype[9].wset.removeifzero false
-documenttype[0].datatype[9].annotationref.annotation.id 0
-documenttype[0].datatype[9].sstruct.name "mystruct"
-documenttype[0].datatype[9].sstruct.version 0
-documenttype[0].datatype[9].sstruct.compression.type NONE
-documenttype[0].datatype[9].sstruct.compression.level 0
-documenttype[0].datatype[9].sstruct.compression.threshold 95
-documenttype[0].datatype[9].sstruct.compression.minsize 200
-documenttype[0].datatype[9].sstruct.field[0].name "bytearr"
-documenttype[0].datatype[9].sstruct.field[0].id 1079701754
-documenttype[0].datatype[9].sstruct.field[0].datatype 49942803
-documenttype[0].datatype[9].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[9].sstruct.field[1].name "mymap"
-documenttype[0].datatype[9].sstruct.field[1].id 1954178122
-documenttype[0].datatype[9].sstruct.field[1].datatype 339965458
-documenttype[0].datatype[9].sstruct.field[1].detailedtype ""
-documenttype[0].datatype[9].sstruct.field[2].name "title"
-documenttype[0].datatype[9].sstruct.field[2].id 567626448
-documenttype[0].datatype[9].sstruct.field[2].datatype 2
-documenttype[0].datatype[9].sstruct.field[2].detailedtype ""
-documenttype[0].datatype[9].sstruct.field[3].name "structfield"
-documenttype[0].datatype[9].sstruct.field[3].id 1726890940
-documenttype[0].datatype[9].sstruct.field[3].datatype 2
-documenttype[0].datatype[9].sstruct.field[3].detailedtype ""
-documenttype[0].datatype[10].id 759956026
-documenttype[0].datatype[10].type ARRAY
-documenttype[0].datatype[10].array.element.id -2092985853
-documenttype[0].datatype[10].map.key.id 0
-documenttype[0].datatype[10].map.value.id 0
-documenttype[0].datatype[10].wset.key.id 0
-documenttype[0].datatype[10].wset.createifnonexistent false
-documenttype[0].datatype[10].wset.removeifzero false
-documenttype[0].datatype[10].annotationref.annotation.id 0
-documenttype[0].datatype[10].sstruct.name ""
-documenttype[0].datatype[10].sstruct.version 0
-documenttype[0].datatype[10].sstruct.compression.type NONE
-documenttype[0].datatype[10].sstruct.compression.level 0
-documenttype[0].datatype[10].sstruct.compression.threshold 95
-documenttype[0].datatype[10].sstruct.compression.minsize 200
-documenttype[0].datatype[11].id 109267174
-documenttype[0].datatype[11].type STRUCT
-documenttype[0].datatype[11].array.element.id 0
-documenttype[0].datatype[11].map.key.id 0
-documenttype[0].datatype[11].map.value.id 0
-documenttype[0].datatype[11].wset.key.id 0
-documenttype[0].datatype[11].wset.createifnonexistent false
-documenttype[0].datatype[11].wset.removeifzero false
-documenttype[0].datatype[11].annotationref.annotation.id 0
-documenttype[0].datatype[11].sstruct.name "sct"
-documenttype[0].datatype[11].sstruct.version 0
-documenttype[0].datatype[11].sstruct.compression.type NONE
-documenttype[0].datatype[11].sstruct.compression.level 0
-documenttype[0].datatype[11].sstruct.compression.threshold 95
-documenttype[0].datatype[11].sstruct.compression.minsize 200
-documenttype[0].datatype[11].sstruct.field[0].name "s1"
-documenttype[0].datatype[11].sstruct.field[0].id 2146820765
-documenttype[0].datatype[11].sstruct.field[0].datatype 2
-documenttype[0].datatype[11].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[11].sstruct.field[1].name "s2"
-documenttype[0].datatype[11].sstruct.field[1].id 45366795
-documenttype[0].datatype[11].sstruct.field[1].datatype 2
-documenttype[0].datatype[11].sstruct.field[1].detailedtype ""
-documenttype[0].datatype[12].id -1244829667
-documenttype[0].datatype[12].type ARRAY
-documenttype[0].datatype[12].array.element.id 109267174
-documenttype[0].datatype[12].map.key.id 0
-documenttype[0].datatype[12].map.value.id 0
-documenttype[0].datatype[12].wset.key.id 0
-documenttype[0].datatype[12].wset.createifnonexistent false
-documenttype[0].datatype[12].wset.removeifzero false
-documenttype[0].datatype[12].annotationref.annotation.id 0
-documenttype[0].datatype[12].sstruct.name ""
-documenttype[0].datatype[12].sstruct.version 0
-documenttype[0].datatype[12].sstruct.compression.type NONE
-documenttype[0].datatype[12].sstruct.compression.level 0
-documenttype[0].datatype[12].sstruct.compression.threshold 95
-documenttype[0].datatype[12].sstruct.compression.minsize 200
-documenttype[0].datatype[13].id 2138385264
-documenttype[0].datatype[13].type MAP
-documenttype[0].datatype[13].array.element.id 0
-documenttype[0].datatype[13].map.key.id 0
-documenttype[0].datatype[13].map.value.id 5
-documenttype[0].datatype[13].wset.key.id 0
-documenttype[0].datatype[13].wset.createifnonexistent false
-documenttype[0].datatype[13].wset.removeifzero false
-documenttype[0].datatype[13].annotationref.annotation.id 0
-documenttype[0].datatype[13].sstruct.name ""
-documenttype[0].datatype[13].sstruct.version 0
-documenttype[0].datatype[13].sstruct.compression.type NONE
-documenttype[0].datatype[13].sstruct.compression.level 0
-documenttype[0].datatype[13].sstruct.compression.threshold 95
-documenttype[0].datatype[13].sstruct.compression.minsize 200
-documenttype[0].datatype[14].id -1865479609
-documenttype[0].datatype[14].type MAP
-documenttype[0].datatype[14].array.element.id 0
-documenttype[0].datatype[14].map.key.id 2
-documenttype[0].datatype[14].map.value.id 4
-documenttype[0].datatype[14].wset.key.id 0
-documenttype[0].datatype[14].wset.createifnonexistent false
-documenttype[0].datatype[14].wset.removeifzero false
-documenttype[0].datatype[14].annotationref.annotation.id 0
-documenttype[0].datatype[14].sstruct.name ""
-documenttype[0].datatype[14].sstruct.version 0
-documenttype[0].datatype[14].sstruct.compression.type NONE
-documenttype[0].datatype[14].sstruct.compression.level 0
-documenttype[0].datatype[14].sstruct.compression.threshold 95
-documenttype[0].datatype[14].sstruct.compression.minsize 200
-documenttype[0].datatype[15].id 294108848
-documenttype[0].datatype[15].type STRUCT
-documenttype[0].datatype[15].array.element.id 0
-documenttype[0].datatype[15].map.key.id 0
-documenttype[0].datatype[15].map.value.id 0
-documenttype[0].datatype[15].wset.key.id 0
-documenttype[0].datatype[15].wset.createifnonexistent false
-documenttype[0].datatype[15].wset.removeifzero false
-documenttype[0].datatype[15].annotationref.annotation.id 0
-documenttype[0].datatype[15].sstruct.name "folder"
-documenttype[0].datatype[15].sstruct.version 0
-documenttype[0].datatype[15].sstruct.compression.type NONE
-documenttype[0].datatype[15].sstruct.compression.level 0
-documenttype[0].datatype[15].sstruct.compression.threshold 95
-documenttype[0].datatype[15].sstruct.compression.minsize 200
-documenttype[0].datatype[15].sstruct.field[0].name "Version"
-documenttype[0].datatype[15].sstruct.field[0].id 64430502
-documenttype[0].datatype[15].sstruct.field[0].datatype 0
-documenttype[0].datatype[15].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[15].sstruct.field[1].name "Name"
-documenttype[0].datatype[15].sstruct.field[1].id 2002760220
-documenttype[0].datatype[15].sstruct.field[1].datatype 2
-documenttype[0].datatype[15].sstruct.field[1].detailedtype ""
-documenttype[0].datatype[15].sstruct.field[2].name "FlagsCounter"
-documenttype[0].datatype[15].sstruct.field[2].id 1741227606
-documenttype[0].datatype[15].sstruct.field[2].datatype -1865479609
-documenttype[0].datatype[15].sstruct.field[2].detailedtype ""
-documenttype[0].datatype[15].sstruct.field[3].name "anotherfolder"
-documenttype[0].datatype[15].sstruct.field[3].id 1582421848
-documenttype[0].datatype[15].sstruct.field[3].datatype 294108848
-documenttype[0].datatype[15].sstruct.field[3].detailedtype ""
-documenttype[0].datatype[16].id -389833101
-documenttype[0].datatype[16].type MAP
-documenttype[0].datatype[16].array.element.id 0
-documenttype[0].datatype[16].map.key.id 0
-documenttype[0].datatype[16].map.value.id 294108848
-documenttype[0].datatype[16].wset.key.id 0
-documenttype[0].datatype[16].wset.createifnonexistent false
-documenttype[0].datatype[16].wset.removeifzero false
-documenttype[0].datatype[16].annotationref.annotation.id 0
-documenttype[0].datatype[16].sstruct.name ""
-documenttype[0].datatype[16].sstruct.version 0
-documenttype[0].datatype[16].sstruct.compression.type NONE
-documenttype[0].datatype[16].sstruct.compression.level 0
-documenttype[0].datatype[16].sstruct.compression.threshold 95
-documenttype[0].datatype[16].sstruct.compression.minsize 200
-documenttype[0].datatype[17].id -1715531035
-documenttype[0].datatype[17].type MAP
-documenttype[0].datatype[17].array.element.id 0
-documenttype[0].datatype[17].map.key.id 0
-documenttype[0].datatype[17].map.value.id 4
-documenttype[0].datatype[17].wset.key.id 0
-documenttype[0].datatype[17].wset.createifnonexistent false
-documenttype[0].datatype[17].wset.removeifzero false
-documenttype[0].datatype[17].annotationref.annotation.id 0
-documenttype[0].datatype[17].sstruct.name ""
-documenttype[0].datatype[17].sstruct.version 0
-documenttype[0].datatype[17].sstruct.compression.type NONE
-documenttype[0].datatype[17].sstruct.compression.level 0
-documenttype[0].datatype[17].sstruct.compression.threshold 95
-documenttype[0].datatype[17].sstruct.compression.minsize 200
-documenttype[0].datatype[18].id 1901258752
-documenttype[0].datatype[18].type MAP
-documenttype[0].datatype[18].array.element.id 0
-documenttype[0].datatype[18].map.key.id 0
-documenttype[0].datatype[18].map.value.id -2092985853
-documenttype[0].datatype[18].wset.key.id 0
-documenttype[0].datatype[18].wset.createifnonexistent false
-documenttype[0].datatype[18].wset.removeifzero false
-documenttype[0].datatype[18].annotationref.annotation.id 0
-documenttype[0].datatype[18].sstruct.name ""
-documenttype[0].datatype[18].sstruct.version 0
-documenttype[0].datatype[18].sstruct.compression.type NONE
-documenttype[0].datatype[18].sstruct.compression.level 0
-documenttype[0].datatype[18].sstruct.compression.threshold 95
-documenttype[0].datatype[18].sstruct.compression.minsize 200
-documenttype[0].datatype[19].id 435886609
-documenttype[0].datatype[19].type MAP
-documenttype[0].datatype[19].array.element.id 0
-documenttype[0].datatype[19].map.key.id 2
-documenttype[0].datatype[19].map.value.id -1245117006
-documenttype[0].datatype[19].wset.key.id 0
-documenttype[0].datatype[19].wset.createifnonexistent false
-documenttype[0].datatype[19].wset.removeifzero false
-documenttype[0].datatype[19].annotationref.annotation.id 0
-documenttype[0].datatype[19].sstruct.name ""
-documenttype[0].datatype[19].sstruct.version 0
-documenttype[0].datatype[19].sstruct.compression.type NONE
-documenttype[0].datatype[19].sstruct.compression.level 0
-documenttype[0].datatype[19].sstruct.compression.threshold 95
-documenttype[0].datatype[19].sstruct.compression.minsize 200
-documenttype[0].datatype[20].id 2125154557
-documenttype[0].datatype[20].type MAP
-documenttype[0].datatype[20].array.element.id 0
-documenttype[0].datatype[20].map.key.id 2
-documenttype[0].datatype[20].map.value.id 1
-documenttype[0].datatype[20].wset.key.id 0
-documenttype[0].datatype[20].wset.createifnonexistent false
-documenttype[0].datatype[20].wset.removeifzero false
-documenttype[0].datatype[20].annotationref.annotation.id 0
-documenttype[0].datatype[20].sstruct.name ""
-documenttype[0].datatype[20].sstruct.version 0
-documenttype[0].datatype[20].sstruct.compression.type NONE
-documenttype[0].datatype[20].sstruct.compression.level 0
-documenttype[0].datatype[20].sstruct.compression.threshold 95
-documenttype[0].datatype[20].sstruct.compression.minsize 200
-documenttype[0].datatype[21].id -1584287606
-documenttype[0].datatype[21].type MAP
-documenttype[0].datatype[21].array.element.id 0
-documenttype[0].datatype[21].map.key.id 2
-documenttype[0].datatype[21].map.value.id 0
-documenttype[0].datatype[21].wset.key.id 0
-documenttype[0].datatype[21].wset.createifnonexistent false
-documenttype[0].datatype[21].wset.removeifzero false
-documenttype[0].datatype[21].annotationref.annotation.id 0
-documenttype[0].datatype[21].sstruct.name ""
-documenttype[0].datatype[21].sstruct.version 0
-documenttype[0].datatype[21].sstruct.compression.type NONE
-documenttype[0].datatype[21].sstruct.compression.level 0
-documenttype[0].datatype[21].sstruct.compression.threshold 95
-documenttype[0].datatype[21].sstruct.compression.minsize 200
-documenttype[0].datatype[22].id 1328286588
-documenttype[0].datatype[22].type WSET
-documenttype[0].datatype[22].array.element.id 0
-documenttype[0].datatype[22].map.key.id 0
-documenttype[0].datatype[22].map.value.id 0
-documenttype[0].datatype[22].wset.key.id 2
-documenttype[0].datatype[22].wset.createifnonexistent false
-documenttype[0].datatype[22].wset.removeifzero false
-documenttype[0].datatype[22].annotationref.annotation.id 0
-documenttype[0].datatype[22].sstruct.name ""
-documenttype[0].datatype[22].sstruct.version 0
-documenttype[0].datatype[22].sstruct.compression.type NONE
-documenttype[0].datatype[22].sstruct.compression.level 0
-documenttype[0].datatype[22].sstruct.compression.threshold 95
-documenttype[0].datatype[22].sstruct.compression.minsize 200
-documenttype[0].datatype[23].id 2065577986
-documenttype[0].datatype[23].type WSET
-documenttype[0].datatype[23].array.element.id 0
-documenttype[0].datatype[23].map.key.id 0
-documenttype[0].datatype[23].map.value.id 0
-documenttype[0].datatype[23].wset.key.id 2
-documenttype[0].datatype[23].wset.createifnonexistent true
-documenttype[0].datatype[23].wset.removeifzero false
-documenttype[0].datatype[23].annotationref.annotation.id 0
-documenttype[0].datatype[23].sstruct.name ""
-documenttype[0].datatype[23].sstruct.version 0
-documenttype[0].datatype[23].sstruct.compression.type NONE
-documenttype[0].datatype[23].sstruct.compression.level 0
-documenttype[0].datatype[23].sstruct.compression.threshold 95
-documenttype[0].datatype[23].sstruct.compression.minsize 200
-documenttype[0].datatype[24].id 2125328771
-documenttype[0].datatype[24].type WSET
-documenttype[0].datatype[24].array.element.id 0
-documenttype[0].datatype[24].map.key.id 0
-documenttype[0].datatype[24].map.value.id 0
-documenttype[0].datatype[24].wset.key.id 2
-documenttype[0].datatype[24].wset.createifnonexistent false
-documenttype[0].datatype[24].wset.removeifzero true
-documenttype[0].datatype[24].annotationref.annotation.id 0
-documenttype[0].datatype[24].sstruct.name ""
-documenttype[0].datatype[24].sstruct.version 0
-documenttype[0].datatype[24].sstruct.compression.type NONE
-documenttype[0].datatype[24].sstruct.compression.level 0
-documenttype[0].datatype[24].sstruct.compression.threshold 95
-documenttype[0].datatype[24].sstruct.compression.minsize 200
-documenttype[0].datatype[25].id 1328581348
-documenttype[0].datatype[25].type STRUCT
-documenttype[0].datatype[25].array.element.id 0
-documenttype[0].datatype[25].map.key.id 0
-documenttype[0].datatype[25].map.value.id 0
-documenttype[0].datatype[25].wset.key.id 0
-documenttype[0].datatype[25].wset.createifnonexistent false
-documenttype[0].datatype[25].wset.removeifzero false
-documenttype[0].datatype[25].annotationref.annotation.id 0
-documenttype[0].datatype[25].sstruct.name "types.header"
-documenttype[0].datatype[25].sstruct.version 0
-documenttype[0].datatype[25].sstruct.compression.type NONE
-documenttype[0].datatype[25].sstruct.compression.level 0
-documenttype[0].datatype[25].sstruct.compression.threshold 95
-documenttype[0].datatype[25].sstruct.compression.minsize 200
-documenttype[0].datatype[25].sstruct.field[0].name "abyte"
-documenttype[0].datatype[25].sstruct.field[0].id 110138156
-documenttype[0].datatype[25].sstruct.field[0].datatype 16
-documenttype[0].datatype[25].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[1].name "along"
-documenttype[0].datatype[25].sstruct.field[1].id 1206464520
-documenttype[0].datatype[25].sstruct.field[1].datatype 4
-documenttype[0].datatype[25].sstruct.field[1].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[2].name "arrayfield"
-documenttype[0].datatype[25].sstruct.field[2].id 965790107
-documenttype[0].datatype[25].sstruct.field[2].datatype -1245117006
-documenttype[0].datatype[25].sstruct.field[2].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[3].name "setfield"
-documenttype[0].datatype[25].sstruct.field[3].id 761581914
-documenttype[0].datatype[25].sstruct.field[3].datatype 1328286588
-documenttype[0].datatype[25].sstruct.field[3].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[4].name "pos"
-documenttype[0].datatype[25].sstruct.field[4].id 1041567475
-documenttype[0].datatype[25].sstruct.field[4].datatype 1381038251
-documenttype[0].datatype[25].sstruct.field[4].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[5].name "setfield2"
-documenttype[0].datatype[25].sstruct.field[5].id 1066659198
-documenttype[0].datatype[25].sstruct.field[5].datatype 18
-documenttype[0].datatype[25].sstruct.field[5].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[6].name "setfield3"
-documenttype[0].datatype[25].sstruct.field[6].id 1180155772
-documenttype[0].datatype[25].sstruct.field[6].datatype 2125328771
-documenttype[0].datatype[25].sstruct.field[6].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[7].name "setfield4"
-documenttype[0].datatype[25].sstruct.field[7].id 1254131631
-documenttype[0].datatype[25].sstruct.field[7].datatype 2065577986
-documenttype[0].datatype[25].sstruct.field[7].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[8].name "tagfield"
-documenttype[0].datatype[25].sstruct.field[8].id 1653562069
-documenttype[0].datatype[25].sstruct.field[8].datatype 18
-documenttype[0].datatype[25].sstruct.field[8].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[9].name "structfield"
-documenttype[0].datatype[25].sstruct.field[9].id 486207386
-documenttype[0].datatype[25].sstruct.field[9].datatype 109267174
-documenttype[0].datatype[25].sstruct.field[9].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[10].name "structarrayfield"
-documenttype[0].datatype[25].sstruct.field[10].id 335048518
-documenttype[0].datatype[25].sstruct.field[10].datatype -1244829667
-documenttype[0].datatype[25].sstruct.field[10].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[11].name "stringmapfield"
-documenttype[0].datatype[25].sstruct.field[11].id 117465687
-documenttype[0].datatype[25].sstruct.field[11].datatype 339965458
-documenttype[0].datatype[25].sstruct.field[11].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[12].name "intmapfield"
-documenttype[0].datatype[25].sstruct.field[12].id 121004462
-documenttype[0].datatype[25].sstruct.field[12].datatype -1584287606
-documenttype[0].datatype[25].sstruct.field[12].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[13].name "floatmapfield"
-documenttype[0].datatype[25].sstruct.field[13].id 1239120925
-documenttype[0].datatype[25].sstruct.field[13].datatype 2125154557
-documenttype[0].datatype[25].sstruct.field[13].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[14].name "longmapfield"
-documenttype[0].datatype[25].sstruct.field[14].id 477718745
-documenttype[0].datatype[25].sstruct.field[14].datatype -1715531035
-documenttype[0].datatype[25].sstruct.field[14].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[15].name "doublemapfield"
-documenttype[0].datatype[25].sstruct.field[15].id 877047192
-documenttype[0].datatype[25].sstruct.field[15].datatype 2138385264
-documenttype[0].datatype[25].sstruct.field[15].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[16].name "arraymapfield"
-documenttype[0].datatype[25].sstruct.field[16].id 1670805928
-documenttype[0].datatype[25].sstruct.field[16].datatype 435886609
-documenttype[0].datatype[25].sstruct.field[16].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[17].name "arrarr"
-documenttype[0].datatype[25].sstruct.field[17].id 1962567166
-documenttype[0].datatype[25].sstruct.field[17].datatype -794985308
-documenttype[0].datatype[25].sstruct.field[17].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[18].name "maparr"
-documenttype[0].datatype[25].sstruct.field[18].id 904375219
-documenttype[0].datatype[25].sstruct.field[18].datatype 69621385
-documenttype[0].datatype[25].sstruct.field[18].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[19].name "complexarray"
-documenttype[0].datatype[25].sstruct.field[19].id 795629533
-documenttype[0].datatype[25].sstruct.field[19].datatype 1416345047
-documenttype[0].datatype[25].sstruct.field[19].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[20].name "mystructfield"
-documenttype[0].datatype[25].sstruct.field[20].id 1348513378
-documenttype[0].datatype[25].sstruct.field[20].datatype -2092985853
-documenttype[0].datatype[25].sstruct.field[20].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[21].name "mystructmap"
-documenttype[0].datatype[25].sstruct.field[21].id 1511423250
-documenttype[0].datatype[25].sstruct.field[21].datatype 1901258752
-documenttype[0].datatype[25].sstruct.field[21].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[22].name "mystructarr"
-documenttype[0].datatype[25].sstruct.field[22].id 595856991
-documenttype[0].datatype[25].sstruct.field[22].datatype 759956026
-documenttype[0].datatype[25].sstruct.field[22].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[23].name "Folders"
-documenttype[0].datatype[25].sstruct.field[23].id 34575524
-documenttype[0].datatype[25].sstruct.field[23].datatype -389833101
-documenttype[0].datatype[25].sstruct.field[23].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[24].name "juletre"
-documenttype[0].datatype[25].sstruct.field[24].id 1039981530
-documenttype[0].datatype[25].sstruct.field[24].datatype 4
-documenttype[0].datatype[25].sstruct.field[24].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[25].name "album0"
-documenttype[0].datatype[25].sstruct.field[25].id 764312262
-documenttype[0].datatype[25].sstruct.field[25].datatype 18
-documenttype[0].datatype[25].sstruct.field[25].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[26].name "album1"
-documenttype[0].datatype[25].sstruct.field[26].id 1967160809
-documenttype[0].datatype[25].sstruct.field[26].datatype 18
-documenttype[0].datatype[25].sstruct.field[26].detailedtype ""
-documenttype[0].datatype[25].sstruct.field[27].name "other"
-documenttype[0].datatype[25].sstruct.field[27].id 2443357
-documenttype[0].datatype[25].sstruct.field[27].datatype 4
-documenttype[0].datatype[25].sstruct.field[27].detailedtype ""
-documenttype[0].fieldsets{[document]}.fields[0] "Folders"
-documenttype[0].fieldsets{[document]}.fields[1] "abyte"
-documenttype[0].fieldsets{[document]}.fields[2] "album0"
-documenttype[0].fieldsets{[document]}.fields[3] "album1"
-documenttype[0].fieldsets{[document]}.fields[4] "along"
-documenttype[0].fieldsets{[document]}.fields[5] "arrarr"
-documenttype[0].fieldsets{[document]}.fields[6] "arrayfield"
-documenttype[0].fieldsets{[document]}.fields[7] "arraymapfield"
-documenttype[0].fieldsets{[document]}.fields[8] "complexarray"
-documenttype[0].fieldsets{[document]}.fields[9] "doublemapfield"
-documenttype[0].fieldsets{[document]}.fields[10] "floatmapfield"
-documenttype[0].fieldsets{[document]}.fields[11] "intmapfield"
-documenttype[0].fieldsets{[document]}.fields[12] "juletre"
-documenttype[0].fieldsets{[document]}.fields[13] "longmapfield"
-documenttype[0].fieldsets{[document]}.fields[14] "maparr"
-documenttype[0].fieldsets{[document]}.fields[15] "mystructarr"
-documenttype[0].fieldsets{[document]}.fields[16] "mystructfield"
-documenttype[0].fieldsets{[document]}.fields[17] "mystructmap"
-documenttype[0].fieldsets{[document]}.fields[18] "pos"
-documenttype[0].fieldsets{[document]}.fields[19] "setfield"
-documenttype[0].fieldsets{[document]}.fields[20] "setfield2"
-documenttype[0].fieldsets{[document]}.fields[21] "setfield3"
-documenttype[0].fieldsets{[document]}.fields[22] "setfield4"
-documenttype[0].fieldsets{[document]}.fields[23] "stringmapfield"
-documenttype[0].fieldsets{[document]}.fields[24] "structarrayfield"
-documenttype[0].fieldsets{[document]}.fields[25] "structfield"
-documenttype[0].fieldsets{[document]}.fields[26] "tagfield"
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "types"
+doctype[1].idx 10015
+doctype[1].internalid -853072901
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].fieldsets{[document]}.fields[0] "Folders"
+doctype[1].fieldsets{[document]}.fields[1] "abyte"
+doctype[1].fieldsets{[document]}.fields[2] "album0"
+doctype[1].fieldsets{[document]}.fields[3] "album1"
+doctype[1].fieldsets{[document]}.fields[4] "along"
+doctype[1].fieldsets{[document]}.fields[5] "arrarr"
+doctype[1].fieldsets{[document]}.fields[6] "arrayfield"
+doctype[1].fieldsets{[document]}.fields[7] "arraymapfield"
+doctype[1].fieldsets{[document]}.fields[8] "complexarray"
+doctype[1].fieldsets{[document]}.fields[9] "doublemapfield"
+doctype[1].fieldsets{[document]}.fields[10] "floatmapfield"
+doctype[1].fieldsets{[document]}.fields[11] "intmapfield"
+doctype[1].fieldsets{[document]}.fields[12] "juletre"
+doctype[1].fieldsets{[document]}.fields[13] "longmapfield"
+doctype[1].fieldsets{[document]}.fields[14] "maparr"
+doctype[1].fieldsets{[document]}.fields[15] "mystructarr"
+doctype[1].fieldsets{[document]}.fields[16] "mystructfield"
+doctype[1].fieldsets{[document]}.fields[17] "mystructmap"
+doctype[1].fieldsets{[document]}.fields[18] "pos"
+doctype[1].fieldsets{[document]}.fields[19] "setfield"
+doctype[1].fieldsets{[document]}.fields[20] "setfield2"
+doctype[1].fieldsets{[document]}.fields[21] "setfield3"
+doctype[1].fieldsets{[document]}.fields[22] "setfield4"
+doctype[1].fieldsets{[document]}.fields[23] "stringmapfield"
+doctype[1].fieldsets{[document]}.fields[24] "structarrayfield"
+doctype[1].fieldsets{[document]}.fields[25] "structfield"
+doctype[1].fieldsets{[document]}.fields[26] "tagfield"
+doctype[1].arraytype[0].idx 10017
+doctype[1].arraytype[0].elementtype 10007
+doctype[1].arraytype[0].internalid -1245117006
+doctype[1].arraytype[1].idx 10024
+doctype[1].arraytype[1].elementtype 10023
+doctype[1].arraytype[1].internalid -1244829667
+doctype[1].arraytype[2].idx 10031
+doctype[1].arraytype[2].elementtype 10007
+doctype[1].arraytype[2].internalid -1245117006
+doctype[1].arraytype[3].idx 10032
+doctype[1].arraytype[3].elementtype 10033
+doctype[1].arraytype[3].internalid -794985308
+doctype[1].arraytype[4].idx 10033
+doctype[1].arraytype[4].elementtype 10034
+doctype[1].arraytype[4].internalid 1707615575
+doctype[1].arraytype[5].idx 10034
+doctype[1].arraytype[5].elementtype 10012
+doctype[1].arraytype[5].internalid -1486737430
+doctype[1].arraytype[6].idx 10035
+doctype[1].arraytype[6].elementtype 10036
+doctype[1].arraytype[6].internalid 69621385
+doctype[1].arraytype[7].idx 10037
+doctype[1].arraytype[7].elementtype 10038
+doctype[1].arraytype[7].internalid 1416345047
+doctype[1].arraytype[8].idx 10039
+doctype[1].arraytype[8].elementtype 10040
+doctype[1].arraytype[8].internalid 1707615575
+doctype[1].arraytype[9].idx 10040
+doctype[1].arraytype[9].elementtype 10012
+doctype[1].arraytype[9].internalid -1486737430
+doctype[1].arraytype[10].idx 10042
+doctype[1].arraytype[10].elementtype 10003
+doctype[1].arraytype[10].internalid 49942803
+doctype[1].arraytype[11].idx 10045
+doctype[1].arraytype[11].elementtype 10041
+doctype[1].arraytype[11].internalid 759956026
+doctype[1].maptype[0].idx 10025
+doctype[1].maptype[0].keytype 10012
+doctype[1].maptype[0].valuetype 10012
+doctype[1].maptype[0].internalid 339965458
+doctype[1].maptype[1].idx 10026
+doctype[1].maptype[1].keytype 10012
+doctype[1].maptype[1].valuetype 10007
+doctype[1].maptype[1].internalid -1584287606
+doctype[1].maptype[2].idx 10027
+doctype[1].maptype[2].keytype 10012
+doctype[1].maptype[2].valuetype 10005
+doctype[1].maptype[2].internalid 2125154557
+doctype[1].maptype[3].idx 10028
+doctype[1].maptype[3].keytype 10007
+doctype[1].maptype[3].valuetype 10008
+doctype[1].maptype[3].internalid -1715531035
+doctype[1].maptype[4].idx 10029
+doctype[1].maptype[4].keytype 10007
+doctype[1].maptype[4].valuetype 10004
+doctype[1].maptype[4].internalid 2138385264
+doctype[1].maptype[5].idx 10030
+doctype[1].maptype[5].keytype 10012
+doctype[1].maptype[5].valuetype 10031
+doctype[1].maptype[5].internalid 435886609
+doctype[1].maptype[6].idx 10036
+doctype[1].maptype[6].keytype 10012
+doctype[1].maptype[6].valuetype 10012
+doctype[1].maptype[6].internalid 339965458
+doctype[1].maptype[7].idx 10038
+doctype[1].maptype[7].keytype 10007
+doctype[1].maptype[7].valuetype 10039
+doctype[1].maptype[7].internalid -372512406
+doctype[1].maptype[8].idx 10043
+doctype[1].maptype[8].keytype 10012
+doctype[1].maptype[8].valuetype 10012
+doctype[1].maptype[8].internalid 339965458
+doctype[1].maptype[9].idx 10044
+doctype[1].maptype[9].keytype 10007
+doctype[1].maptype[9].valuetype 10041
+doctype[1].maptype[9].internalid 1901258752
+doctype[1].maptype[10].idx 10046
+doctype[1].maptype[10].keytype 10007
+doctype[1].maptype[10].valuetype 10047
+doctype[1].maptype[10].internalid -389833101
+doctype[1].maptype[11].idx 10048
+doctype[1].maptype[11].keytype 10012
+doctype[1].maptype[11].valuetype 10008
+doctype[1].maptype[11].internalid -1865479609
+doctype[1].wsettype[0].idx 10018
+doctype[1].wsettype[0].elementtype 10012
+doctype[1].wsettype[0].createifnonexistent false
+doctype[1].wsettype[0].removeifzero false
+doctype[1].wsettype[0].internalid 1328286588
+doctype[1].wsettype[1].idx 10019
+doctype[1].wsettype[1].elementtype 10012
+doctype[1].wsettype[1].createifnonexistent true
+doctype[1].wsettype[1].removeifzero true
+doctype[1].wsettype[1].internalid 18
+doctype[1].wsettype[2].idx 10020
+doctype[1].wsettype[2].elementtype 10012
+doctype[1].wsettype[2].createifnonexistent false
+doctype[1].wsettype[2].removeifzero true
+doctype[1].wsettype[2].internalid 2125328771
+doctype[1].wsettype[3].idx 10021
+doctype[1].wsettype[3].elementtype 10012
+doctype[1].wsettype[3].createifnonexistent true
+doctype[1].wsettype[3].removeifzero false
+doctype[1].wsettype[3].internalid 2065577986
+doctype[1].wsettype[4].idx 10022
+doctype[1].wsettype[4].elementtype 10012
+doctype[1].wsettype[4].createifnonexistent true
+doctype[1].wsettype[4].removeifzero true
+doctype[1].wsettype[4].internalid 18
+doctype[1].wsettype[5].idx 10049
+doctype[1].wsettype[5].elementtype 10012
+doctype[1].wsettype[5].createifnonexistent true
+doctype[1].wsettype[5].removeifzero true
+doctype[1].wsettype[5].internalid 18
+doctype[1].wsettype[6].idx 10050
+doctype[1].wsettype[6].elementtype 10012
+doctype[1].wsettype[6].createifnonexistent true
+doctype[1].wsettype[6].removeifzero true
+doctype[1].wsettype[6].internalid 18
+doctype[1].structtype[0].idx 10023
+doctype[1].structtype[0].name "sct"
+doctype[1].structtype[0].field[0].name "s1"
+doctype[1].structtype[0].field[0].internalid 2146820765
+doctype[1].structtype[0].field[0].type 10012
+doctype[1].structtype[0].field[1].name "s2"
+doctype[1].structtype[0].field[1].internalid 45366795
+doctype[1].structtype[0].field[1].type 10012
+doctype[1].structtype[0].internalid 109267174
+doctype[1].structtype[1].idx 10041
+doctype[1].structtype[1].name "mystruct"
+doctype[1].structtype[1].field[0].name "bytearr"
+doctype[1].structtype[1].field[0].internalid 1079701754
+doctype[1].structtype[1].field[0].type 10042
+doctype[1].structtype[1].field[1].name "mymap"
+doctype[1].structtype[1].field[1].internalid 1954178122
+doctype[1].structtype[1].field[1].type 10043
+doctype[1].structtype[1].field[2].name "title"
+doctype[1].structtype[1].field[2].internalid 567626448
+doctype[1].structtype[1].field[2].type 10012
+doctype[1].structtype[1].field[3].name "structfield"
+doctype[1].structtype[1].field[3].internalid 1726890940
+doctype[1].structtype[1].field[3].type 10012
+doctype[1].structtype[1].internalid -2092985853
+doctype[1].structtype[2].idx 10047
+doctype[1].structtype[2].name "folder"
+doctype[1].structtype[2].field[0].name "Version"
+doctype[1].structtype[2].field[0].internalid 64430502
+doctype[1].structtype[2].field[0].type 10007
+doctype[1].structtype[2].field[1].name "Name"
+doctype[1].structtype[2].field[1].internalid 2002760220
+doctype[1].structtype[2].field[1].type 10012
+doctype[1].structtype[2].field[2].name "FlagsCounter"
+doctype[1].structtype[2].field[2].internalid 1741227606
+doctype[1].structtype[2].field[2].type 10048
+doctype[1].structtype[2].field[3].name "anotherfolder"
+doctype[1].structtype[2].field[3].internalid 1582421848
+doctype[1].structtype[2].field[3].type 10047
+doctype[1].structtype[2].internalid 294108848
+doctype[1].structtype[3].idx 10016
+doctype[1].structtype[3].name "types.header"
+doctype[1].structtype[3].field[0].name "abyte"
+doctype[1].structtype[3].field[0].internalid 110138156
+doctype[1].structtype[3].field[0].type 10003
+doctype[1].structtype[3].field[1].name "along"
+doctype[1].structtype[3].field[1].internalid 1206464520
+doctype[1].structtype[3].field[1].type 10008
+doctype[1].structtype[3].field[2].name "arrayfield"
+doctype[1].structtype[3].field[2].internalid 965790107
+doctype[1].structtype[3].field[2].type 10017
+doctype[1].structtype[3].field[3].name "setfield"
+doctype[1].structtype[3].field[3].internalid 761581914
+doctype[1].structtype[3].field[3].type 10018
+doctype[1].structtype[3].field[4].name "pos"
+doctype[1].structtype[3].field[4].internalid 1041567475
+doctype[1].structtype[3].field[4].type 10009
+doctype[1].structtype[3].field[5].name "setfield2"
+doctype[1].structtype[3].field[5].internalid 1066659198
+doctype[1].structtype[3].field[5].type 10019
+doctype[1].structtype[3].field[6].name "setfield3"
+doctype[1].structtype[3].field[6].internalid 1180155772
+doctype[1].structtype[3].field[6].type 10020
+doctype[1].structtype[3].field[7].name "setfield4"
+doctype[1].structtype[3].field[7].internalid 1254131631
+doctype[1].structtype[3].field[7].type 10021
+doctype[1].structtype[3].field[8].name "tagfield"
+doctype[1].structtype[3].field[8].internalid 1653562069
+doctype[1].structtype[3].field[8].type 10022
+doctype[1].structtype[3].field[9].name "structfield"
+doctype[1].structtype[3].field[9].internalid 486207386
+doctype[1].structtype[3].field[9].type 10023
+doctype[1].structtype[3].field[10].name "structarrayfield"
+doctype[1].structtype[3].field[10].internalid 335048518
+doctype[1].structtype[3].field[10].type 10024
+doctype[1].structtype[3].field[11].name "stringmapfield"
+doctype[1].structtype[3].field[11].internalid 117465687
+doctype[1].structtype[3].field[11].type 10025
+doctype[1].structtype[3].field[12].name "intmapfield"
+doctype[1].structtype[3].field[12].internalid 121004462
+doctype[1].structtype[3].field[12].type 10026
+doctype[1].structtype[3].field[13].name "floatmapfield"
+doctype[1].structtype[3].field[13].internalid 1239120925
+doctype[1].structtype[3].field[13].type 10027
+doctype[1].structtype[3].field[14].name "longmapfield"
+doctype[1].structtype[3].field[14].internalid 477718745
+doctype[1].structtype[3].field[14].type 10028
+doctype[1].structtype[3].field[15].name "doublemapfield"
+doctype[1].structtype[3].field[15].internalid 877047192
+doctype[1].structtype[3].field[15].type 10029
+doctype[1].structtype[3].field[16].name "arraymapfield"
+doctype[1].structtype[3].field[16].internalid 1670805928
+doctype[1].structtype[3].field[16].type 10030
+doctype[1].structtype[3].field[17].name "arrarr"
+doctype[1].structtype[3].field[17].internalid 1962567166
+doctype[1].structtype[3].field[17].type 10032
+doctype[1].structtype[3].field[18].name "maparr"
+doctype[1].structtype[3].field[18].internalid 904375219
+doctype[1].structtype[3].field[18].type 10035
+doctype[1].structtype[3].field[19].name "complexarray"
+doctype[1].structtype[3].field[19].internalid 795629533
+doctype[1].structtype[3].field[19].type 10037
+doctype[1].structtype[3].field[20].name "mystructfield"
+doctype[1].structtype[3].field[20].internalid 1348513378
+doctype[1].structtype[3].field[20].type 10041
+doctype[1].structtype[3].field[21].name "mystructmap"
+doctype[1].structtype[3].field[21].internalid 1511423250
+doctype[1].structtype[3].field[21].type 10044
+doctype[1].structtype[3].field[22].name "mystructarr"
+doctype[1].structtype[3].field[22].internalid 595856991
+doctype[1].structtype[3].field[22].type 10045
+doctype[1].structtype[3].field[23].name "Folders"
+doctype[1].structtype[3].field[23].internalid 34575524
+doctype[1].structtype[3].field[23].type 10046
+doctype[1].structtype[3].field[24].name "juletre"
+doctype[1].structtype[3].field[24].internalid 1039981530
+doctype[1].structtype[3].field[24].type 10008
+doctype[1].structtype[3].field[25].name "album0"
+doctype[1].structtype[3].field[25].internalid 764312262
+doctype[1].structtype[3].field[25].type 10049
+doctype[1].structtype[3].field[26].name "album1"
+doctype[1].structtype[3].field[26].internalid 1967160809
+doctype[1].structtype[3].field[26].type 10050
+doctype[1].structtype[3].field[27].name "other"
+doctype[1].structtype[3].field[27].internalid 2443357
+doctype[1].structtype[3].field[27].type 10008
+doctype[1].structtype[3].internalid 1328581348
diff --git a/config-model/src/test/configmodel/types/documenttypes_with_doc_field.cfg b/config-model/src/test/configmodel/types/documenttypes_with_doc_field.cfg
index 61c92eee8d1..15430101553 100644
--- a/config-model/src/test/configmodel/types/documenttypes_with_doc_field.cfg
+++ b/config-model/src/test/configmodel/types/documenttypes_with_doc_field.cfg
@@ -1,49 +1,65 @@
enablecompression false
usev8geopositions false
-documenttype[0].id -1368624373
-documenttype[0].name "other_doc"
-documenttype[0].version 0
-documenttype[0].headerstruct 1631005140
-documenttype[0].bodystruct 0
-documenttype[0].inherits[0].id 8
-documenttype[0].datatype[0].id 1631005140
-documenttype[0].datatype[0].type STRUCT
-documenttype[0].datatype[0].array.element.id 0
-documenttype[0].datatype[0].map.key.id 0
-documenttype[0].datatype[0].map.value.id 0
-documenttype[0].datatype[0].wset.key.id 0
-documenttype[0].datatype[0].wset.createifnonexistent false
-documenttype[0].datatype[0].wset.removeifzero false
-documenttype[0].datatype[0].annotationref.annotation.id 0
-documenttype[0].datatype[0].sstruct.name "other_doc.header"
-documenttype[0].datatype[0].sstruct.version 0
-documenttype[0].datatype[0].sstruct.compression.type NONE
-documenttype[0].datatype[0].sstruct.compression.level 0
-documenttype[0].datatype[0].sstruct.compression.threshold 95
-documenttype[0].datatype[0].sstruct.compression.minsize 200
-documenttype[1].id -853072901
-documenttype[1].name "types"
-documenttype[1].version 0
-documenttype[1].headerstruct 1328581348
-documenttype[1].bodystruct 0
-documenttype[1].inherits[0].id 8
-documenttype[1].datatype[0].id 1328581348
-documenttype[1].datatype[0].type STRUCT
-documenttype[1].datatype[0].array.element.id 0
-documenttype[1].datatype[0].map.key.id 0
-documenttype[1].datatype[0].map.value.id 0
-documenttype[1].datatype[0].wset.key.id 0
-documenttype[1].datatype[0].wset.createifnonexistent false
-documenttype[1].datatype[0].wset.removeifzero false
-documenttype[1].datatype[0].annotationref.annotation.id 0
-documenttype[1].datatype[0].sstruct.name "types.header"
-documenttype[1].datatype[0].sstruct.version 0
-documenttype[1].datatype[0].sstruct.compression.type NONE
-documenttype[1].datatype[0].sstruct.compression.level 0
-documenttype[1].datatype[0].sstruct.compression.threshold 95
-documenttype[1].datatype[0].sstruct.compression.minsize 200
-documenttype[1].datatype[0].sstruct.field[0].name "doc_field"
-documenttype[1].datatype[0].sstruct.field[0].id 819293364
-documenttype[1].datatype[0].sstruct.field[0].datatype -1368624373
-documenttype[1].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[1].fieldsets{[document]}.fields[0] "doc_field"
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "other_doc"
+doctype[1].idx 10015
+doctype[1].internalid -1368624373
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].structtype[0].idx 10016
+doctype[1].structtype[0].name "other_doc.header"
+doctype[1].structtype[0].internalid 1631005140
+doctype[2].name "types"
+doctype[2].idx 10017
+doctype[2].internalid -853072901
+doctype[2].inherits[0].idx 10000
+doctype[2].contentstruct 10018
+doctype[2].fieldsets{[document]}.fields[0] "doc_field"
+doctype[2].structtype[0].idx 10018
+doctype[2].structtype[0].name "types.header"
+doctype[2].structtype[0].field[0].name "doc_field"
+doctype[2].structtype[0].field[0].internalid 819293364
+doctype[2].structtype[0].field[0].type 10015
+doctype[2].structtype[0].internalid 1328581348
diff --git a/config-model/src/test/configmodel/types/references/documenttypes_multiple_imported_fields.cfg b/config-model/src/test/configmodel/types/references/documenttypes_multiple_imported_fields.cfg
index d992839d5d9..1b5817e6f39 100644
--- a/config-model/src/test/configmodel/types/references/documenttypes_multiple_imported_fields.cfg
+++ b/config-model/src/test/configmodel/types/references/documenttypes_multiple_imported_fields.cfg
@@ -1,97 +1,98 @@
enablecompression false
usev8geopositions false
-documenttype[0].id 2987301
-documenttype[0].name "ad"
-documenttype[0].version 0
-documenttype[0].headerstruct 959075962
-documenttype[0].bodystruct 0
-documenttype[0].inherits[0].id 8
-documenttype[0].datatype[0].id 959075962
-documenttype[0].datatype[0].type STRUCT
-documenttype[0].datatype[0].array.element.id 0
-documenttype[0].datatype[0].map.key.id 0
-documenttype[0].datatype[0].map.value.id 0
-documenttype[0].datatype[0].wset.key.id 0
-documenttype[0].datatype[0].wset.createifnonexistent false
-documenttype[0].datatype[0].wset.removeifzero false
-documenttype[0].datatype[0].annotationref.annotation.id 0
-documenttype[0].datatype[0].sstruct.name "ad.header"
-documenttype[0].datatype[0].sstruct.version 0
-documenttype[0].datatype[0].sstruct.compression.type NONE
-documenttype[0].datatype[0].sstruct.compression.level 0
-documenttype[0].datatype[0].sstruct.compression.threshold 95
-documenttype[0].datatype[0].sstruct.compression.minsize 200
-documenttype[0].datatype[0].sstruct.field[0].name "campaign_ref"
-documenttype[0].datatype[0].sstruct.field[0].id 23963250
-documenttype[0].datatype[0].sstruct.field[0].datatype 595216861
-documenttype[0].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[0].sstruct.field[1].name "person_ref"
-documenttype[0].datatype[0].sstruct.field[1].id 100779805
-documenttype[0].datatype[0].sstruct.field[1].datatype 542332920
-documenttype[0].datatype[0].sstruct.field[1].detailedtype ""
-documenttype[0].fieldsets{[document]}.fields[0] "campaign_ref"
-documenttype[0].fieldsets{[document]}.fields[1] "person_ref"
-documenttype[0].referencetype[0].id 595216861
-documenttype[0].referencetype[0].target_type_id -1318255918
-documenttype[0].referencetype[1].id 542332920
-documenttype[0].referencetype[1].target_type_id 443162583
-documenttype[0].importedfield[0].name "my_cool_field"
-documenttype[0].importedfield[1].name "my_swag_field"
-documenttype[0].importedfield[2].name "my_name"
-documenttype[1].id -1318255918
-documenttype[1].name "campaign"
-documenttype[1].version 0
-documenttype[1].headerstruct -2041471955
-documenttype[1].bodystruct 0
-documenttype[1].inherits[0].id 8
-documenttype[1].datatype[0].id -2041471955
-documenttype[1].datatype[0].type STRUCT
-documenttype[1].datatype[0].array.element.id 0
-documenttype[1].datatype[0].map.key.id 0
-documenttype[1].datatype[0].map.value.id 0
-documenttype[1].datatype[0].wset.key.id 0
-documenttype[1].datatype[0].wset.createifnonexistent false
-documenttype[1].datatype[0].wset.removeifzero false
-documenttype[1].datatype[0].annotationref.annotation.id 0
-documenttype[1].datatype[0].sstruct.name "campaign.header"
-documenttype[1].datatype[0].sstruct.version 0
-documenttype[1].datatype[0].sstruct.compression.type NONE
-documenttype[1].datatype[0].sstruct.compression.level 0
-documenttype[1].datatype[0].sstruct.compression.threshold 95
-documenttype[1].datatype[0].sstruct.compression.minsize 200
-documenttype[1].datatype[0].sstruct.field[0].name "cool_field"
-documenttype[1].datatype[0].sstruct.field[0].id 1588702436
-documenttype[1].datatype[0].sstruct.field[0].datatype 2
-documenttype[1].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[1].datatype[0].sstruct.field[1].name "swag_field"
-documenttype[1].datatype[0].sstruct.field[1].id 1691224741
-documenttype[1].datatype[0].sstruct.field[1].datatype 4
-documenttype[1].datatype[0].sstruct.field[1].detailedtype ""
-documenttype[1].fieldsets{[document]}.fields[0] "cool_field"
-documenttype[1].fieldsets{[document]}.fields[1] "swag_field"
-documenttype[2].id 443162583
-documenttype[2].name "person"
-documenttype[2].version 0
-documenttype[2].headerstruct 3129224
-documenttype[2].bodystruct 0
-documenttype[2].inherits[0].id 8
-documenttype[2].datatype[0].id 3129224
-documenttype[2].datatype[0].type STRUCT
-documenttype[2].datatype[0].array.element.id 0
-documenttype[2].datatype[0].map.key.id 0
-documenttype[2].datatype[0].map.value.id 0
-documenttype[2].datatype[0].wset.key.id 0
-documenttype[2].datatype[0].wset.createifnonexistent false
-documenttype[2].datatype[0].wset.removeifzero false
-documenttype[2].datatype[0].annotationref.annotation.id 0
-documenttype[2].datatype[0].sstruct.name "person.header"
-documenttype[2].datatype[0].sstruct.version 0
-documenttype[2].datatype[0].sstruct.compression.type NONE
-documenttype[2].datatype[0].sstruct.compression.level 0
-documenttype[2].datatype[0].sstruct.compression.threshold 95
-documenttype[2].datatype[0].sstruct.compression.minsize 200
-documenttype[2].datatype[0].sstruct.field[0].name "name"
-documenttype[2].datatype[0].sstruct.field[0].id 1160796772
-documenttype[2].datatype[0].sstruct.field[0].datatype 2
-documenttype[2].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[2].fieldsets{[document]}.fields[0] "name"
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "ad"
+doctype[1].idx 10015
+doctype[1].internalid 2987301
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].fieldsets{[document]}.fields[0] "campaign_ref"
+doctype[1].fieldsets{[document]}.fields[1] "person_ref"
+doctype[1].importedfield[0].name "my_cool_field"
+doctype[1].importedfield[1].name "my_swag_field"
+doctype[1].importedfield[2].name "my_name"
+doctype[1].documentref[0].idx 10017
+doctype[1].documentref[0].targettype 10018
+doctype[1].documentref[0].internalid 595216861
+doctype[1].documentref[1].idx 10019
+doctype[1].documentref[1].targettype 10020
+doctype[1].documentref[1].internalid 542332920
+doctype[1].structtype[0].idx 10016
+doctype[1].structtype[0].name "ad.header"
+doctype[1].structtype[0].field[0].name "campaign_ref"
+doctype[1].structtype[0].field[0].internalid 23963250
+doctype[1].structtype[0].field[0].type 10017
+doctype[1].structtype[0].field[1].name "person_ref"
+doctype[1].structtype[0].field[1].internalid 100779805
+doctype[1].structtype[0].field[1].type 10019
+doctype[1].structtype[0].internalid 959075962
+doctype[2].name "campaign"
+doctype[2].idx 10018
+doctype[2].internalid -1318255918
+doctype[2].inherits[0].idx 10000
+doctype[2].contentstruct 10021
+doctype[2].fieldsets{[document]}.fields[0] "cool_field"
+doctype[2].fieldsets{[document]}.fields[1] "swag_field"
+doctype[2].structtype[0].idx 10021
+doctype[2].structtype[0].name "campaign.header"
+doctype[2].structtype[0].field[0].name "cool_field"
+doctype[2].structtype[0].field[0].internalid 1588702436
+doctype[2].structtype[0].field[0].type 10012
+doctype[2].structtype[0].field[1].name "swag_field"
+doctype[2].structtype[0].field[1].internalid 1691224741
+doctype[2].structtype[0].field[1].type 10008
+doctype[2].structtype[0].internalid -2041471955
+doctype[3].name "person"
+doctype[3].idx 10020
+doctype[3].internalid 443162583
+doctype[3].inherits[0].idx 10000
+doctype[3].contentstruct 10022
+doctype[3].fieldsets{[document]}.fields[0] "name"
+doctype[3].structtype[0].idx 10022
+doctype[3].structtype[0].name "person.header"
+doctype[3].structtype[0].field[0].name "name"
+doctype[3].structtype[0].field[0].internalid 1160796772
+doctype[3].structtype[0].field[0].type 10012
+doctype[3].structtype[0].internalid 3129224
diff --git a/config-model/src/test/configmodel/types/references/documenttypes_refs_to_other_types.cfg b/config-model/src/test/configmodel/types/references/documenttypes_refs_to_other_types.cfg
index 68ed924615f..1c5d4d41819 100644
--- a/config-model/src/test/configmodel/types/references/documenttypes_refs_to_other_types.cfg
+++ b/config-model/src/test/configmodel/types/references/documenttypes_refs_to_other_types.cfg
@@ -1,79 +1,83 @@
enablecompression false
usev8geopositions false
-documenttype[0].id 2987301
-documenttype[0].name "ad"
-documenttype[0].version 0
-documenttype[0].headerstruct 959075962
-documenttype[0].bodystruct 0
-documenttype[0].inherits[0].id 8
-documenttype[0].datatype[0].id 959075962
-documenttype[0].datatype[0].type STRUCT
-documenttype[0].datatype[0].array.element.id 0
-documenttype[0].datatype[0].map.key.id 0
-documenttype[0].datatype[0].map.value.id 0
-documenttype[0].datatype[0].wset.key.id 0
-documenttype[0].datatype[0].wset.createifnonexistent false
-documenttype[0].datatype[0].wset.removeifzero false
-documenttype[0].datatype[0].annotationref.annotation.id 0
-documenttype[0].datatype[0].sstruct.name "ad.header"
-documenttype[0].datatype[0].sstruct.version 0
-documenttype[0].datatype[0].sstruct.compression.type NONE
-documenttype[0].datatype[0].sstruct.compression.level 0
-documenttype[0].datatype[0].sstruct.compression.threshold 95
-documenttype[0].datatype[0].sstruct.compression.minsize 200
-documenttype[0].datatype[0].sstruct.field[0].name "campaign_ref"
-documenttype[0].datatype[0].sstruct.field[0].id 23963250
-documenttype[0].datatype[0].sstruct.field[0].datatype 595216861
-documenttype[0].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[0].sstruct.field[1].name "person_ref"
-documenttype[0].datatype[0].sstruct.field[1].id 100779805
-documenttype[0].datatype[0].sstruct.field[1].datatype 542332920
-documenttype[0].datatype[0].sstruct.field[1].detailedtype ""
-documenttype[0].fieldsets{[document]}.fields[0] "campaign_ref"
-documenttype[0].fieldsets{[document]}.fields[1] "person_ref"
-documenttype[0].referencetype[0].id 595216861
-documenttype[0].referencetype[0].target_type_id -1318255918
-documenttype[0].referencetype[1].id 542332920
-documenttype[0].referencetype[1].target_type_id 443162583
-documenttype[1].id -1318255918
-documenttype[1].name "campaign"
-documenttype[1].version 0
-documenttype[1].headerstruct -2041471955
-documenttype[1].bodystruct 0
-documenttype[1].inherits[0].id 8
-documenttype[1].datatype[0].id -2041471955
-documenttype[1].datatype[0].type STRUCT
-documenttype[1].datatype[0].array.element.id 0
-documenttype[1].datatype[0].map.key.id 0
-documenttype[1].datatype[0].map.value.id 0
-documenttype[1].datatype[0].wset.key.id 0
-documenttype[1].datatype[0].wset.createifnonexistent false
-documenttype[1].datatype[0].wset.removeifzero false
-documenttype[1].datatype[0].annotationref.annotation.id 0
-documenttype[1].datatype[0].sstruct.name "campaign.header"
-documenttype[1].datatype[0].sstruct.version 0
-documenttype[1].datatype[0].sstruct.compression.type NONE
-documenttype[1].datatype[0].sstruct.compression.level 0
-documenttype[1].datatype[0].sstruct.compression.threshold 95
-documenttype[1].datatype[0].sstruct.compression.minsize 200
-documenttype[2].id 443162583
-documenttype[2].name "person"
-documenttype[2].version 0
-documenttype[2].headerstruct 3129224
-documenttype[2].bodystruct 0
-documenttype[2].inherits[0].id 8
-documenttype[2].datatype[0].id 3129224
-documenttype[2].datatype[0].type STRUCT
-documenttype[2].datatype[0].array.element.id 0
-documenttype[2].datatype[0].map.key.id 0
-documenttype[2].datatype[0].map.value.id 0
-documenttype[2].datatype[0].wset.key.id 0
-documenttype[2].datatype[0].wset.createifnonexistent false
-documenttype[2].datatype[0].wset.removeifzero false
-documenttype[2].datatype[0].annotationref.annotation.id 0
-documenttype[2].datatype[0].sstruct.name "person.header"
-documenttype[2].datatype[0].sstruct.version 0
-documenttype[2].datatype[0].sstruct.compression.type NONE
-documenttype[2].datatype[0].sstruct.compression.level 0
-documenttype[2].datatype[0].sstruct.compression.threshold 95
-documenttype[2].datatype[0].sstruct.compression.minsize 200
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "ad"
+doctype[1].idx 10015
+doctype[1].internalid 2987301
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].fieldsets{[document]}.fields[0] "campaign_ref"
+doctype[1].fieldsets{[document]}.fields[1] "person_ref"
+doctype[1].documentref[0].idx 10017
+doctype[1].documentref[0].targettype 10018
+doctype[1].documentref[0].internalid 595216861
+doctype[1].documentref[1].idx 10019
+doctype[1].documentref[1].targettype 10020
+doctype[1].documentref[1].internalid 542332920
+doctype[1].structtype[0].idx 10016
+doctype[1].structtype[0].name "ad.header"
+doctype[1].structtype[0].field[0].name "campaign_ref"
+doctype[1].structtype[0].field[0].internalid 23963250
+doctype[1].structtype[0].field[0].type 10017
+doctype[1].structtype[0].field[1].name "person_ref"
+doctype[1].structtype[0].field[1].internalid 100779805
+doctype[1].structtype[0].field[1].type 10019
+doctype[1].structtype[0].internalid 959075962
+doctype[2].name "campaign"
+doctype[2].idx 10018
+doctype[2].internalid -1318255918
+doctype[2].inherits[0].idx 10000
+doctype[2].contentstruct 10021
+doctype[2].structtype[0].idx 10021
+doctype[2].structtype[0].name "campaign.header"
+doctype[2].structtype[0].internalid -2041471955
+doctype[3].name "person"
+doctype[3].idx 10020
+doctype[3].internalid 443162583
+doctype[3].inherits[0].idx 10000
+doctype[3].contentstruct 10022
+doctype[3].structtype[0].idx 10022
+doctype[3].structtype[0].name "person.header"
+doctype[3].structtype[0].internalid 3129224
diff --git a/config-model/src/test/configmodel/types/references/documenttypes_refs_to_same_type.cfg b/config-model/src/test/configmodel/types/references/documenttypes_refs_to_same_type.cfg
index 6415e62cd7e..2f178c55bfd 100644
--- a/config-model/src/test/configmodel/types/references/documenttypes_refs_to_same_type.cfg
+++ b/config-model/src/test/configmodel/types/references/documenttypes_refs_to_same_type.cfg
@@ -1,56 +1,72 @@
enablecompression false
usev8geopositions false
-documenttype[0].id 2987301
-documenttype[0].name "ad"
-documenttype[0].version 0
-documenttype[0].headerstruct 959075962
-documenttype[0].bodystruct 0
-documenttype[0].inherits[0].id 8
-documenttype[0].datatype[0].id 959075962
-documenttype[0].datatype[0].type STRUCT
-documenttype[0].datatype[0].array.element.id 0
-documenttype[0].datatype[0].map.key.id 0
-documenttype[0].datatype[0].map.value.id 0
-documenttype[0].datatype[0].wset.key.id 0
-documenttype[0].datatype[0].wset.createifnonexistent false
-documenttype[0].datatype[0].wset.removeifzero false
-documenttype[0].datatype[0].annotationref.annotation.id 0
-documenttype[0].datatype[0].sstruct.name "ad.header"
-documenttype[0].datatype[0].sstruct.version 0
-documenttype[0].datatype[0].sstruct.compression.type NONE
-documenttype[0].datatype[0].sstruct.compression.level 0
-documenttype[0].datatype[0].sstruct.compression.threshold 95
-documenttype[0].datatype[0].sstruct.compression.minsize 200
-documenttype[0].datatype[0].sstruct.field[0].name "campaign_ref"
-documenttype[0].datatype[0].sstruct.field[0].id 23963250
-documenttype[0].datatype[0].sstruct.field[0].datatype 595216861
-documenttype[0].datatype[0].sstruct.field[0].detailedtype ""
-documenttype[0].datatype[0].sstruct.field[1].name "other_campaign_ref"
-documenttype[0].datatype[0].sstruct.field[1].id 874751172
-documenttype[0].datatype[0].sstruct.field[1].datatype 595216861
-documenttype[0].datatype[0].sstruct.field[1].detailedtype ""
-documenttype[0].fieldsets{[document]}.fields[0] "campaign_ref"
-documenttype[0].fieldsets{[document]}.fields[1] "other_campaign_ref"
-documenttype[0].referencetype[0].id 595216861
-documenttype[0].referencetype[0].target_type_id -1318255918
-documenttype[1].id -1318255918
-documenttype[1].name "campaign"
-documenttype[1].version 0
-documenttype[1].headerstruct -2041471955
-documenttype[1].bodystruct 0
-documenttype[1].inherits[0].id 8
-documenttype[1].datatype[0].id -2041471955
-documenttype[1].datatype[0].type STRUCT
-documenttype[1].datatype[0].array.element.id 0
-documenttype[1].datatype[0].map.key.id 0
-documenttype[1].datatype[0].map.value.id 0
-documenttype[1].datatype[0].wset.key.id 0
-documenttype[1].datatype[0].wset.createifnonexistent false
-documenttype[1].datatype[0].wset.removeifzero false
-documenttype[1].datatype[0].annotationref.annotation.id 0
-documenttype[1].datatype[0].sstruct.name "campaign.header"
-documenttype[1].datatype[0].sstruct.version 0
-documenttype[1].datatype[0].sstruct.compression.type NONE
-documenttype[1].datatype[0].sstruct.compression.level 0
-documenttype[1].datatype[0].sstruct.compression.threshold 95
-documenttype[1].datatype[0].sstruct.compression.minsize 200
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "ad"
+doctype[1].idx 10015
+doctype[1].internalid 2987301
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].fieldsets{[document]}.fields[0] "campaign_ref"
+doctype[1].fieldsets{[document]}.fields[1] "other_campaign_ref"
+doctype[1].documentref[0].idx 10017
+doctype[1].documentref[0].targettype 10018
+doctype[1].documentref[0].internalid 595216861
+doctype[1].structtype[0].idx 10016
+doctype[1].structtype[0].name "ad.header"
+doctype[1].structtype[0].field[0].name "campaign_ref"
+doctype[1].structtype[0].field[0].internalid 23963250
+doctype[1].structtype[0].field[0].type 10017
+doctype[1].structtype[0].field[1].name "other_campaign_ref"
+doctype[1].structtype[0].field[1].internalid 874751172
+doctype[1].structtype[0].field[1].type 10017
+doctype[1].structtype[0].internalid 959075962
+doctype[2].name "campaign"
+doctype[2].idx 10018
+doctype[2].internalid -1318255918
+doctype[2].inherits[0].idx 10000
+doctype[2].contentstruct 10019
+doctype[2].structtype[0].idx 10019
+doctype[2].structtype[0].name "campaign.header"
+doctype[2].structtype[0].internalid -2041471955
diff --git a/config-model/src/test/derived/duplicate_struct/documenttypes.cfg b/config-model/src/test/derived/duplicate_struct/documenttypes.cfg
index 1b897214d73..867e1e70e7a 100644
--- a/config-model/src/test/derived/duplicate_struct/documenttypes.cfg
+++ b/config-model/src/test/derived/duplicate_struct/documenttypes.cfg
@@ -1,100 +1,88 @@
enablecompression false
usev8geopositions false
-documenttype[].id 97614088
-documenttype[].name "foo"
-documenttype[].version 0
-documenttype[].headerstruct -308552393
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id -2092985853
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "mystruct"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "name"
-documenttype[].datatype[].sstruct.field[].id 1160796772
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "company"
-documenttype[].datatype[].sstruct.field[].id 2010814026
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id 759956026
-documenttype[].datatype[].type ARRAY
-documenttype[].datatype[].array.element.id -2092985853
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name ""
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].id -308552393
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "foo.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "timestamp"
-documenttype[].datatype[].sstruct.field[].id 808678733
-documenttype[].datatype[].sstruct.field[].datatype 4
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "mystuff"
-documenttype[].datatype[].sstruct.field[].id 885106505
-documenttype[].datatype[].sstruct.field[].datatype 759956026
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "mystuff"
-documenttype[].fieldsets{[document]}.fields[] "timestamp"
-documenttype[].id 378030095
-documenttype[].name "foobar"
-documenttype[].version 0
-documenttype[].headerstruct -1365874608
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].inherits[].id 97614088
-documenttype[].datatype[].id -1365874608
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "foobar.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "title"
-documenttype[].datatype[].sstruct.field[].id 567626448
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "mystuff"
-documenttype[].fieldsets{[document]}.fields[] "timestamp"
-documenttype[].fieldsets{[document]}.fields[] "title"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "foo"
+doctype[].idx 10015
+doctype[].internalid 97614088
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "mystuff"
+doctype[].fieldsets{[document]}.fields[] "timestamp"
+doctype[].arraytype[].idx 10017
+doctype[].arraytype[].elementtype 10018
+doctype[].arraytype[].internalid 759956026
+doctype[].structtype[].idx 10018
+doctype[].structtype[].name "mystruct"
+doctype[].structtype[].field[].name "name"
+doctype[].structtype[].field[].internalid 1160796772
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "company"
+doctype[].structtype[].field[].internalid 2010814026
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid -2092985853
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "foo.header"
+doctype[].structtype[].field[].name "timestamp"
+doctype[].structtype[].field[].internalid 808678733
+doctype[].structtype[].field[].type 10008
+doctype[].structtype[].field[].name "mystuff"
+doctype[].structtype[].field[].internalid 885106505
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].internalid -308552393
+doctype[].name "foobar"
+doctype[].idx 10019
+doctype[].internalid 378030095
+doctype[].inherits[].idx 10000
+doctype[].inherits[].idx 10015
+doctype[].contentstruct 10020
+doctype[].fieldsets{[document]}.fields[] "mystuff"
+doctype[].fieldsets{[document]}.fields[] "timestamp"
+doctype[].fieldsets{[document]}.fields[] "title"
+doctype[].structtype[].idx 10020
+doctype[].structtype[].name "foobar.header"
+doctype[].structtype[].field[].name "title"
+doctype[].structtype[].field[].internalid 567626448
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid -1365874608
diff --git a/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg b/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg
index c3ea1318d33..7a67640adfe 100644
--- a/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg
+++ b/config-model/src/test/derived/imported_fields_inherited_reference/documenttypes.cfg
@@ -1,110 +1,99 @@
enablecompression false
usev8geopositions false
-documenttype[].id -94853056
-documenttype[].name "child_a"
-documenttype[].version 0
-documenttype[].headerstruct 867409663
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 867409663
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "child_a.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "ref_from_a"
-documenttype[].datatype[].sstruct.field[].id 300427062
-documenttype[].datatype[].sstruct.field[].datatype 427398467
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "ref_from_a"
-documenttype[].referencetype[].id 427398467
-documenttype[].referencetype[].target_type_id 1175161836
-documenttype[].id -94852095
-documenttype[].name "child_b"
-documenttype[].version 0
-documenttype[].headerstruct 670896158
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].inherits[].id -94853056
-documenttype[].datatype[].id 670896158
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "child_b.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "ref_from_b"
-documenttype[].datatype[].sstruct.field[].id 185778735
-documenttype[].datatype[].sstruct.field[].datatype 427398467
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "ref_from_a"
-documenttype[].fieldsets{[document]}.fields[] "ref_from_b"
-documenttype[].id -94851134
-documenttype[].name "child_c"
-documenttype[].version 0
-documenttype[].headerstruct 474382653
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].inherits[].id -94852095
-documenttype[].datatype[].id 474382653
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "child_c.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].fieldsets{[document]}.fields[] "ref_from_a"
-documenttype[].fieldsets{[document]}.fields[] "ref_from_b"
-documenttype[].importedfield[].name "from_a_int_field"
-documenttype[].importedfield[].name "from_b_int_field"
-documenttype[].id 1175161836
-documenttype[].name "parent"
-documenttype[].version 0
-documenttype[].headerstruct 836075987
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 836075987
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "parent.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "int_field"
-documenttype[].datatype[].sstruct.field[].id 2128822283
-documenttype[].datatype[].sstruct.field[].datatype 0
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "int_field"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "child_a"
+doctype[].idx 10015
+doctype[].internalid -94853056
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "ref_from_a"
+doctype[].documentref[].idx 10017
+doctype[].documentref[].targettype 10018
+doctype[].documentref[].internalid 427398467
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "child_a.header"
+doctype[].structtype[].field[].name "ref_from_a"
+doctype[].structtype[].field[].internalid 300427062
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].internalid 867409663
+doctype[].name "child_b"
+doctype[].idx 10019
+doctype[].internalid -94852095
+doctype[].inherits[].idx 10000
+doctype[].inherits[].idx 10015
+doctype[].contentstruct 10020
+doctype[].fieldsets{[document]}.fields[] "ref_from_a"
+doctype[].fieldsets{[document]}.fields[] "ref_from_b"
+doctype[].structtype[].idx 10020
+doctype[].structtype[].name "child_b.header"
+doctype[].structtype[].field[].name "ref_from_b"
+doctype[].structtype[].field[].internalid 185778735
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].internalid 670896158
+doctype[].name "child_c"
+doctype[].idx 10021
+doctype[].internalid -94851134
+doctype[].inherits[].idx 10000
+doctype[].inherits[].idx 10019
+doctype[].contentstruct 10022
+doctype[].fieldsets{[document]}.fields[] "ref_from_a"
+doctype[].fieldsets{[document]}.fields[] "ref_from_b"
+doctype[].importedfield[].name "from_a_int_field"
+doctype[].importedfield[].name "from_b_int_field"
+doctype[].structtype[].idx 10022
+doctype[].structtype[].name "child_c.header"
+doctype[].structtype[].internalid 474382653
+doctype[].name "parent"
+doctype[].idx 10018
+doctype[].internalid 1175161836
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10023
+doctype[].fieldsets{[document]}.fields[] "int_field"
+doctype[].structtype[].idx 10023
+doctype[].structtype[].name "parent.header"
+doctype[].structtype[].field[].name "int_field"
+doctype[].structtype[].field[].internalid 2128822283
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 836075987
diff --git a/config-model/src/test/derived/inheritfromparent/documenttypes.cfg b/config-model/src/test/derived/inheritfromparent/documenttypes.cfg
index da65510ee5a..e8fd97671ff 100644
--- a/config-model/src/test/derived/inheritfromparent/documenttypes.cfg
+++ b/config-model/src/test/derived/inheritfromparent/documenttypes.cfg
@@ -1,79 +1,80 @@
enablecompression false
usev8geopositions false
-documenttype[].id 1175161836
-documenttype[].name "parent"
-documenttype[].version 0
-documenttype[].headerstruct 836075987
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 836075987
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "parent.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "weight_src"
-documenttype[].datatype[].sstruct.field[].id 1225660233
-documenttype[].datatype[].sstruct.field[].datatype 1
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "weight"
-documenttype[].datatype[].sstruct.field[].id 1001392207
-documenttype[].datatype[].sstruct.field[].datatype 1
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id 1091188812
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "parent_struct"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "parent_field"
-documenttype[].datatype[].sstruct.field[].id 933533022
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "weight_src"
-documenttype[].id 746267614
-documenttype[].name "child"
-documenttype[].version 0
-documenttype[].headerstruct 81425825
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].inherits[].id 1175161836
-documenttype[].datatype[].id 81425825
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "child.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "child_field"
-documenttype[].datatype[].sstruct.field[].id 1814271363
-documenttype[].datatype[].sstruct.field[].datatype 1091188812
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "child_field"
-documenttype[].fieldsets{[document]}.fields[] "weight_src"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "parent"
+doctype[].idx 10015
+doctype[].internalid 1175161836
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "weight_src"
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "parent.header"
+doctype[].structtype[].field[].name "weight_src"
+doctype[].structtype[].field[].internalid 1225660233
+doctype[].structtype[].field[].type 10005
+doctype[].structtype[].field[].name "weight"
+doctype[].structtype[].field[].internalid 1001392207
+doctype[].structtype[].field[].type 10005
+doctype[].structtype[].internalid 836075987
+doctype[].structtype[].idx 10017
+doctype[].structtype[].name "parent_struct"
+doctype[].structtype[].field[].name "parent_field"
+doctype[].structtype[].field[].internalid 933533022
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid 1091188812
+doctype[].name "child"
+doctype[].idx 10018
+doctype[].internalid 746267614
+doctype[].inherits[].idx 10000
+doctype[].inherits[].idx 10015
+doctype[].contentstruct 10019
+doctype[].fieldsets{[document]}.fields[] "child_field"
+doctype[].fieldsets{[document]}.fields[] "weight_src"
+doctype[].structtype[].idx 10019
+doctype[].structtype[].name "child.header"
+doctype[].structtype[].field[].name "child_field"
+doctype[].structtype[].field[].internalid 1814271363
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].internalid 81425825
diff --git a/config-model/src/test/derived/multi_struct/documenttypes.cfg b/config-model/src/test/derived/multi_struct/documenttypes.cfg
index c358b2854ee..9ce19079177 100644
--- a/config-model/src/test/derived/multi_struct/documenttypes.cfg
+++ b/config-model/src/test/derived/multi_struct/documenttypes.cfg
@@ -1,194 +1,128 @@
enablecompression false
usev8geopositions false
-documenttype[].id 2987301
-documenttype[].name "ad"
-documenttype[].version 0
-documenttype[].headerstruct 959075962
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 959075962
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "ad.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "e"
-documenttype[].datatype[].sstruct.field[].id 970377814
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "e"
-documenttype[].id -1051831567
-documenttype[].name "product"
-documenttype[].version 0
-documenttype[].headerstruct -107300050
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id -2092985853
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "mystruct"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "zero"
-documenttype[].datatype[].sstruct.field[].id 2128579715
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "one"
-documenttype[].datatype[].sstruct.field[].id 997119011
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "two"
-documenttype[].datatype[].sstruct.field[].id 2054688289
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "three"
-documenttype[].datatype[].sstruct.field[].id 814368361
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id -420192670
-documenttype[].datatype[].type MAP
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 2
-documenttype[].datatype[].map.value.id -2092985853
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name ""
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].id -107300050
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "product.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "more_stuff"
-documenttype[].datatype[].sstruct.field[].id 278342855
-documenttype[].datatype[].sstruct.field[].datatype -420192670
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "more_stuff"
-documenttype[].id -903152840
-documenttype[].name "shop"
-documenttype[].version 0
-documenttype[].headerstruct 371492103
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id -2092985853
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "mystruct"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "one"
-documenttype[].datatype[].sstruct.field[].id 997119011
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "two"
-documenttype[].datatype[].sstruct.field[].id 2054688289
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "three"
-documenttype[].datatype[].sstruct.field[].id 814368361
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id -420192670
-documenttype[].datatype[].type MAP
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 2
-documenttype[].datatype[].map.value.id -2092985853
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name ""
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].id 371492103
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "shop.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "some_stuff"
-documenttype[].datatype[].sstruct.field[].id 1543312381
-documenttype[].datatype[].sstruct.field[].datatype -420192670
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "some_stuff"
-documenttype[].id -836031795
-documenttype[].name "user"
-documenttype[].version 0
-documenttype[].headerstruct 1601213394
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 1601213394
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "user.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "a"
-documenttype[].datatype[].sstruct.field[].id 493339625
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "a"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "ad"
+doctype[].idx 10015
+doctype[].internalid 2987301
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "e"
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "ad.header"
+doctype[].structtype[].field[].name "e"
+doctype[].structtype[].field[].internalid 970377814
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid 959075962
+doctype[].name "product"
+doctype[].idx 10017
+doctype[].internalid -1051831567
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10018
+doctype[].fieldsets{[document]}.fields[] "more_stuff"
+doctype[].maptype[].idx 10019
+doctype[].maptype[].keytype 10012
+doctype[].maptype[].valuetype 10020
+doctype[].maptype[].internalid -420192670
+doctype[].structtype[].idx 10020
+doctype[].structtype[].name "mystruct"
+doctype[].structtype[].field[].name "zero"
+doctype[].structtype[].field[].internalid 2128579715
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "one"
+doctype[].structtype[].field[].internalid 997119011
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "two"
+doctype[].structtype[].field[].internalid 2054688289
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "three"
+doctype[].structtype[].field[].internalid 814368361
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid -2092985853
+doctype[].structtype[].idx 10018
+doctype[].structtype[].name "product.header"
+doctype[].structtype[].field[].name "more_stuff"
+doctype[].structtype[].field[].internalid 278342855
+doctype[].structtype[].field[].type 10019
+doctype[].structtype[].internalid -107300050
+doctype[].name "shop"
+doctype[].idx 10021
+doctype[].internalid -903152840
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10022
+doctype[].fieldsets{[document]}.fields[] "some_stuff"
+doctype[].maptype[].idx 10023
+doctype[].maptype[].keytype 10012
+doctype[].maptype[].valuetype 10024
+doctype[].maptype[].internalid -420192670
+doctype[].structtype[].idx 10024
+doctype[].structtype[].name "mystruct"
+doctype[].structtype[].field[].name "one"
+doctype[].structtype[].field[].internalid 997119011
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "two"
+doctype[].structtype[].field[].internalid 2054688289
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].field[].name "three"
+doctype[].structtype[].field[].internalid 814368361
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid -2092985853
+doctype[].structtype[].idx 10022
+doctype[].structtype[].name "shop.header"
+doctype[].structtype[].field[].name "some_stuff"
+doctype[].structtype[].field[].internalid 1543312381
+doctype[].structtype[].field[].type 10023
+doctype[].structtype[].internalid 371492103
+doctype[].name "user"
+doctype[].idx 10025
+doctype[].internalid -836031795
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10026
+doctype[].fieldsets{[document]}.fields[] "a"
+doctype[].structtype[].idx 10026
+doctype[].structtype[].name "user.header"
+doctype[].structtype[].field[].name "a"
+doctype[].structtype[].field[].internalid 493339625
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid 1601213394
diff --git a/config-model/src/test/derived/structandfieldset/attributes.cfg b/config-model/src/test/derived/structandfieldset/attributes.cfg
new file mode 100644
index 00000000000..b441857c9c7
--- /dev/null
+++ b/config-model/src/test/derived/structandfieldset/attributes.cfg
@@ -0,0 +1,96 @@
+attribute[].name "tag"
+attribute[].datatype STRING
+attribute[].collectiontype SINGLE
+attribute[].dictionary.type BTREE
+attribute[].dictionary.match UNCASED
+attribute[].match UNCASED
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].paged false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported false
+attribute[].maxuncommittedmemory 77777
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
+attribute[].name "people.first_name"
+attribute[].datatype STRING
+attribute[].collectiontype ARRAY
+attribute[].dictionary.type BTREE
+attribute[].dictionary.match UNCASED
+attribute[].match UNCASED
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].paged false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors false
+attribute[].enableonlybitvector false
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported false
+attribute[].maxuncommittedmemory 77777
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
+attribute[].name "people.last_name"
+attribute[].datatype STRING
+attribute[].collectiontype ARRAY
+attribute[].dictionary.type BTREE
+attribute[].dictionary.match UNCASED
+attribute[].match UNCASED
+attribute[].removeifzero false
+attribute[].createifnonexistent false
+attribute[].fastsearch false
+attribute[].huge false
+attribute[].paged false
+attribute[].ismutable false
+attribute[].sortascending true
+attribute[].sortfunction UCA
+attribute[].sortstrength PRIMARY
+attribute[].sortlocale ""
+attribute[].enablebitvectors true
+attribute[].enableonlybitvector true
+attribute[].fastaccess false
+attribute[].arity 8
+attribute[].lowerbound -9223372036854775808
+attribute[].upperbound 9223372036854775807
+attribute[].densepostinglistthreshold 0.4
+attribute[].tensortype ""
+attribute[].imported false
+attribute[].maxuncommittedmemory 77777
+attribute[].distancemetric EUCLIDEAN
+attribute[].index.hnsw.enabled false
+attribute[].index.hnsw.maxlinkspernode 16
+attribute[].index.hnsw.neighborstoexploreatinsert 200
+attribute[].index.hnsw.distancemetric EUCLIDEAN
+attribute[].index.hnsw.multithreadedindexing true
diff --git a/config-model/src/test/derived/structandfieldset/test.sd b/config-model/src/test/derived/structandfieldset/test.sd
index 77316eab9c9..da12787e564 100644
--- a/config-model/src/test/derived/structandfieldset/test.sd
+++ b/config-model/src/test/derived/structandfieldset/test.sd
@@ -14,7 +14,10 @@ schema test {
field people type array<person> {
indexing: summary
struct-field first_name { indexing: attribute }
- struct-field last_name { indexing: attribute }
+ struct-field last_name {
+ indexing: attribute
+ rank: filter
+ }
}
}
diff --git a/config-model/src/test/derived/structinheritance/documenttypes.cfg b/config-model/src/test/derived/structinheritance/documenttypes.cfg
index cf4bc89866f..8b343665289 100644
--- a/config-model/src/test/derived/structinheritance/documenttypes.cfg
+++ b/config-model/src/test/derived/structinheritance/documenttypes.cfg
@@ -1,102 +1,81 @@
enablecompression false
usev8geopositions false
-documenttype[].id 485659380
-documenttype[].name "simple"
-documenttype[].version 0
-documenttype[].headerstruct -2142109237
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id -1396204461
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "base"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "name"
-documenttype[].datatype[].sstruct.field[].id 1160796772
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id 746267614
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "child"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "age"
-documenttype[].datatype[].sstruct.field[].id 1862473705
-documenttype[].datatype[].sstruct.field[].datatype 0
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "name"
-documenttype[].datatype[].sstruct.field[].id 1160796772
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id 1811766610
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "grandchild"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "toy"
-documenttype[].datatype[].sstruct.field[].id 536645790
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "age"
-documenttype[].datatype[].sstruct.field[].id 1862473705
-documenttype[].datatype[].sstruct.field[].datatype 0
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "name"
-documenttype[].datatype[].sstruct.field[].id 1160796772
-documenttype[].datatype[].sstruct.field[].datatype 2
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].id -2142109237
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "simple.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "f1"
-documenttype[].datatype[].sstruct.field[].id 750623154
-documenttype[].datatype[].sstruct.field[].datatype 746267614
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].datatype[].sstruct.field[].name "f2"
-documenttype[].datatype[].sstruct.field[].id 1523850983
-documenttype[].datatype[].sstruct.field[].datatype 1811766610
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "f1"
-documenttype[].fieldsets{[document]}.fields[] "f2"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "simple"
+doctype[].idx 10015
+doctype[].internalid 485659380
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "f1"
+doctype[].fieldsets{[document]}.fields[] "f2"
+doctype[].structtype[].idx 10018
+doctype[].structtype[].name "base"
+doctype[].structtype[].field[].name "name"
+doctype[].structtype[].field[].internalid 1160796772
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid -1396204461
+doctype[].structtype[].idx 10017
+doctype[].structtype[].name "child"
+doctype[].structtype[].inherits[].type 10018
+doctype[].structtype[].field[].name "age"
+doctype[].structtype[].field[].internalid 1862473705
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 746267614
+doctype[].structtype[].idx 10019
+doctype[].structtype[].name "grandchild"
+doctype[].structtype[].inherits[].type 10017
+doctype[].structtype[].field[].name "toy"
+doctype[].structtype[].field[].internalid 536645790
+doctype[].structtype[].field[].type 10012
+doctype[].structtype[].internalid 1811766610
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "simple.header"
+doctype[].structtype[].field[].name "f1"
+doctype[].structtype[].field[].internalid 750623154
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].field[].name "f2"
+doctype[].structtype[].field[].internalid 1523850983
+doctype[].structtype[].field[].type 10019
+doctype[].structtype[].internalid -2142109237
diff --git a/config-model/src/test/derived/tensor/documenttypes.cfg b/config-model/src/test/derived/tensor/documenttypes.cfg
index 879b455a711..9ee6a82245f 100644
--- a/config-model/src/test/derived/tensor/documenttypes.cfg
+++ b/config-model/src/test/derived/tensor/documenttypes.cfg
@@ -1,53 +1,87 @@
enablecompression false
usev8geopositions false
-documenttype[].id -1290043429
-documenttype[].name "tensor"
-documenttype[].version 0
-documenttype[].headerstruct 2125927172
-documenttype[].bodystruct 0
-documenttype[].inherits[].id 8
-documenttype[].datatype[].id 2125927172
-documenttype[].datatype[].type STRUCT
-documenttype[].datatype[].array.element.id 0
-documenttype[].datatype[].map.key.id 0
-documenttype[].datatype[].map.value.id 0
-documenttype[].datatype[].wset.key.id 0
-documenttype[].datatype[].wset.createifnonexistent false
-documenttype[].datatype[].wset.removeifzero false
-documenttype[].datatype[].annotationref.annotation.id 0
-documenttype[].datatype[].sstruct.name "tensor.header"
-documenttype[].datatype[].sstruct.version 0
-documenttype[].datatype[].sstruct.compression.type NONE
-documenttype[].datatype[].sstruct.compression.level 0
-documenttype[].datatype[].sstruct.compression.threshold 95
-documenttype[].datatype[].sstruct.compression.minsize 200
-documenttype[].datatype[].sstruct.field[].name "f1"
-documenttype[].datatype[].sstruct.field[].id 26661415
-documenttype[].datatype[].sstruct.field[].datatype 21
-documenttype[].datatype[].sstruct.field[].detailedtype "tensor(x[3])"
-documenttype[].datatype[].sstruct.field[].name "f2"
-documenttype[].datatype[].sstruct.field[].id 2080644671
-documenttype[].datatype[].sstruct.field[].datatype 21
-documenttype[].datatype[].sstruct.field[].detailedtype "tensor<float>(x[2],y[1])"
-documenttype[].datatype[].sstruct.field[].name "f3"
-documenttype[].datatype[].sstruct.field[].id 1295091863
-documenttype[].datatype[].sstruct.field[].datatype 21
-documenttype[].datatype[].sstruct.field[].detailedtype "tensor(x{})"
-documenttype[].datatype[].sstruct.field[].name "f4"
-documenttype[].datatype[].sstruct.field[].id 1224191509
-documenttype[].datatype[].sstruct.field[].datatype 21
-documenttype[].datatype[].sstruct.field[].detailedtype "tensor(x[10],y[10])"
-documenttype[].datatype[].sstruct.field[].name "f5"
-documenttype[].datatype[].sstruct.field[].id 329055840
-documenttype[].datatype[].sstruct.field[].datatype 21
-documenttype[].datatype[].sstruct.field[].detailedtype "tensor<float>(x[10])"
-documenttype[].datatype[].sstruct.field[].name "f6"
-documenttype[].datatype[].sstruct.field[].id 596352344
-documenttype[].datatype[].sstruct.field[].datatype 1
-documenttype[].datatype[].sstruct.field[].detailedtype ""
-documenttype[].fieldsets{[document]}.fields[] "f1"
-documenttype[].fieldsets{[document]}.fields[] "f2"
-documenttype[].fieldsets{[document]}.fields[] "f3"
-documenttype[].fieldsets{[document]}.fields[] "f4"
-documenttype[].fieldsets{[document]}.fields[] "f5"
-documenttype[].fieldsets{[document]}.fields[] "f6"
+doctype[].name "document"
+doctype[].idx 10000
+doctype[].internalid 8
+doctype[].contentstruct 10001
+doctype[].primitivetype[].idx 10002
+doctype[].primitivetype[].name "bool"
+doctype[].primitivetype[].idx 10003
+doctype[].primitivetype[].name "byte"
+doctype[].primitivetype[].idx 10004
+doctype[].primitivetype[].name "double"
+doctype[].primitivetype[].idx 10005
+doctype[].primitivetype[].name "float"
+doctype[].primitivetype[].idx 10006
+doctype[].primitivetype[].name "float16"
+doctype[].primitivetype[].idx 10007
+doctype[].primitivetype[].name "int"
+doctype[].primitivetype[].idx 10008
+doctype[].primitivetype[].name "long"
+doctype[].primitivetype[].idx 10010
+doctype[].primitivetype[].name "predicate"
+doctype[].primitivetype[].idx 10011
+doctype[].primitivetype[].name "raw"
+doctype[].primitivetype[].idx 10012
+doctype[].primitivetype[].name "string"
+doctype[].primitivetype[].idx 10014
+doctype[].primitivetype[].name "uri"
+doctype[].wsettype[].idx 10013
+doctype[].wsettype[].elementtype 10012
+doctype[].wsettype[].createifnonexistent true
+doctype[].wsettype[].removeifzero true
+doctype[].wsettype[].internalid 18
+doctype[].structtype[].idx 10001
+doctype[].structtype[].name "document.header"
+doctype[].structtype[].internalid -284186494
+doctype[].structtype[].idx 10009
+doctype[].structtype[].name "position"
+doctype[].structtype[].field[].name "x"
+doctype[].structtype[].field[].internalid 914677694
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].field[].name "y"
+doctype[].structtype[].field[].internalid 900009410
+doctype[].structtype[].field[].type 10007
+doctype[].structtype[].internalid 1381038251
+doctype[].name "tensor"
+doctype[].idx 10015
+doctype[].internalid -1290043429
+doctype[].inherits[].idx 10000
+doctype[].contentstruct 10016
+doctype[].fieldsets{[document]}.fields[] "f1"
+doctype[].fieldsets{[document]}.fields[] "f2"
+doctype[].fieldsets{[document]}.fields[] "f3"
+doctype[].fieldsets{[document]}.fields[] "f4"
+doctype[].fieldsets{[document]}.fields[] "f5"
+doctype[].fieldsets{[document]}.fields[] "f6"
+doctype[].tensortype[].idx 10017
+doctype[].tensortype[].detailedtype "tensor(x[3])"
+doctype[].tensortype[].idx 10018
+doctype[].tensortype[].detailedtype "tensor<float>(x[2],y[1])"
+doctype[].tensortype[].idx 10019
+doctype[].tensortype[].detailedtype "tensor(x{})"
+doctype[].tensortype[].idx 10020
+doctype[].tensortype[].detailedtype "tensor(x[10],y[10])"
+doctype[].tensortype[].idx 10021
+doctype[].tensortype[].detailedtype "tensor<float>(x[10])"
+doctype[].structtype[].idx 10016
+doctype[].structtype[].name "tensor.header"
+doctype[].structtype[].field[].name "f1"
+doctype[].structtype[].field[].internalid 26661415
+doctype[].structtype[].field[].type 10017
+doctype[].structtype[].field[].name "f2"
+doctype[].structtype[].field[].internalid 2080644671
+doctype[].structtype[].field[].type 10018
+doctype[].structtype[].field[].name "f3"
+doctype[].structtype[].field[].internalid 1295091863
+doctype[].structtype[].field[].type 10019
+doctype[].structtype[].field[].name "f4"
+doctype[].structtype[].field[].internalid 1224191509
+doctype[].structtype[].field[].type 10020
+doctype[].structtype[].field[].name "f5"
+doctype[].structtype[].field[].internalid 329055840
+doctype[].structtype[].field[].type 10021
+doctype[].structtype[].field[].name "f6"
+doctype[].structtype[].field[].internalid 596352344
+doctype[].structtype[].field[].type 10005
+doctype[].structtype[].internalid 2125927172
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
index 76d4ae3f1a6..aa70bf4d26a 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
@@ -3,6 +3,10 @@ package com.yahoo.config.provision;
import com.yahoo.cloud.config.ApplicationIdConfig;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
/**
* A complete, immutable identification of an application instance.
*
@@ -10,27 +14,40 @@ import com.yahoo.cloud.config.ApplicationIdConfig;
* @author vegard
* @author bratseth
*/
-public final class ApplicationId implements Comparable<ApplicationId> {
+public class ApplicationId implements Comparable<ApplicationId> {
+
+ // TODO: remove '.' and '*' from this pattern.
+ static final Pattern namePattern = Pattern.compile("(?!\\.\\.)[a-zA-Z0-9_.*-]{1,256}");
+
+ private static final ApplicationId global = new ApplicationId(TenantName.from("*"),
+ ApplicationName.from("*"),
+ InstanceName.from("*")) {
+ @Override public boolean equals(Object other) { return this == other; }
+ };
+
+ private static final Comparator<ApplicationId> comparator = Comparator.comparing(ApplicationId::tenant)
+ .thenComparing(ApplicationId::application)
+ .thenComparing(ApplicationId::instance)
+ .thenComparing(global::equals, Boolean::compare);
private final TenantName tenant;
private final ApplicationName application;
private final InstanceName instance;
-
- private final String stringValue;
private final String serializedForm;
- public ApplicationId(ApplicationIdConfig config) {
- this(TenantName.from(config.tenant()), ApplicationName.from(config.application()), InstanceName.from(config.instance()));
- }
-
private ApplicationId(TenantName tenant, ApplicationName applicationName, InstanceName instanceName) {
this.tenant = tenant;
this.application = applicationName;
this.instance = instanceName;
- this.stringValue = toStringValue();
this.serializedForm = toSerializedForm();
}
+ public static ApplicationId from(ApplicationIdConfig config) {
+ return from(TenantName.from(config.tenant()),
+ ApplicationName.from(config.application()),
+ InstanceName.from(config.instance()));
+ }
+
public static ApplicationId from(TenantName tenant, ApplicationName application, InstanceName instance) {
return new ApplicationId(tenant, application, instance);
}
@@ -44,7 +61,7 @@ public final class ApplicationId implements Comparable<ApplicationId> {
if (parts.length < 3)
throw new IllegalArgumentException("Application ids must be on the form tenant:application:instance, but was " + idString);
- return new Builder().tenant(parts[0]).applicationName(parts[1]).instanceName(parts[2]).build();
+ return from(parts[0], parts[1], parts[2]);
}
public static ApplicationId fromFullString(String idString) {
@@ -52,11 +69,11 @@ public final class ApplicationId implements Comparable<ApplicationId> {
if (parts.length < 3)
throw new IllegalArgumentException("Application ids must be on the form tenant.application.instance, but was " + idString);
- return new Builder().tenant(parts[0]).applicationName(parts[1]).instanceName(parts[2]).build();
+ return from(parts[0], parts[1], parts[2]);
}
@Override
- public int hashCode() { return stringValue.hashCode(); }
+ public int hashCode() { return Objects.hash(tenant, application, instance); }
@Override
public boolean equals(Object other) {
@@ -72,10 +89,6 @@ public final class ApplicationId implements Comparable<ApplicationId> {
/** Returns a serialized form of the content of this: tenant:application:instance */
public String serializedForm() { return serializedForm; }
- private String toStringValue() {
- return "tenant '" + tenant + "', application '" + application + "', instance '" + instance + "'";
- }
-
/** Returns "dotted" string (tenant.application.instance) with instance name omitted if it is "default" */
public String toShortString() {
return tenant().value() + "." + application().value() +
@@ -88,7 +101,7 @@ public final class ApplicationId implements Comparable<ApplicationId> {
}
private String toSerializedForm() {
- return tenant + ":" + application + ":" + instance;
+ return tenant.value() + ":" + application.value() + ":" + instance.value();
}
@Override
@@ -100,18 +113,7 @@ public final class ApplicationId implements Comparable<ApplicationId> {
@Override
public int compareTo(ApplicationId other) {
- int diff;
-
- diff = tenant.compareTo(other.tenant);
- if (diff != 0) { return diff; }
-
- diff = application.compareTo(other.application);
- if (diff != 0) { return diff; }
-
- diff = instance.compareTo(other.instance);
- if (diff != 0) { return diff; }
-
- return 0;
+ return comparator.compare(this, other);
}
/** Returns an application id where all fields are "default" */
@@ -119,12 +121,10 @@ public final class ApplicationId implements Comparable<ApplicationId> {
return new ApplicationId(TenantName.defaultName(), ApplicationName.defaultName(), InstanceName.defaultName());
}
- /** Returns an application id where all fields are "*" */
+ // TODO: kill this
+ /** Returns a very special application id, which is not equal to any other id. */
public static ApplicationId global() {
- return new Builder().tenant("*")
- .applicationName("*")
- .instanceName("*")
- .build();
+ return global;
}
public static class Builder {
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java
index f16c126dec2..f2585913015 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.provision;
-import java.util.Objects;
+import ai.vespa.validation.PatternedStringWrapper;
/**
* Represents an applications name, which may be any kind of string or default. This type is defined
@@ -10,28 +10,12 @@ import java.util.Objects;
* @author Ulf Lilleengen
* @since 5.25
*/
-public class ApplicationName implements Comparable<ApplicationName> {
+public class ApplicationName extends PatternedStringWrapper<ApplicationName> {
- private final String applicationName;
+ private static final ApplicationName defaultName = new ApplicationName("default");
- private ApplicationName(String applicationName) {
- this.applicationName = applicationName;
- }
-
- @Override
- public int hashCode() {
- return applicationName.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ApplicationName)) return false;
- return Objects.equals(((ApplicationName) obj).applicationName, applicationName);
- }
-
- @Override
- public String toString() {
- return applicationName;
+ private ApplicationName(String name) {
+ super(name, ApplicationId.namePattern, "application name");
}
public static ApplicationName from(String name) {
@@ -39,20 +23,11 @@ public class ApplicationName implements Comparable<ApplicationName> {
}
public static ApplicationName defaultName() {
- return new ApplicationName("default");
+ return defaultName;
}
public boolean isDefault() {
- return equals(ApplicationName.defaultName());
- }
-
- public String value() {
- return applicationName;
- }
-
- @Override
- public int compareTo(ApplicationName name) {
- return this.applicationName.compareTo(name.applicationName);
+ return equals(defaultName);
}
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java
index 8101b70b943..fc40d351465 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.provision;
-import java.util.Objects;
+import ai.vespa.validation.PatternedStringWrapper;
/**
* Represents an applications instance name, which may be any kind of string or default. This type is defined
@@ -9,30 +9,12 @@ import java.util.Objects;
*
* @author Ulf Lilleengen
*/
-public class InstanceName implements Comparable<InstanceName> {
+public class InstanceName extends PatternedStringWrapper<InstanceName> {
- private static final InstanceName defaultInstance = new InstanceName("default");
+ private static final InstanceName defaultName = new InstanceName("default");
- private final String instanceName;
-
- private InstanceName(String instanceName) {
- this.instanceName = instanceName;
- }
-
- @Override
- public int hashCode() {
- return instanceName.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof InstanceName)) return false;
- return Objects.equals(((InstanceName) obj).instanceName, instanceName);
- }
-
- @Override
- public String toString() {
- return instanceName;
+ private InstanceName(String name) {
+ super(name, ApplicationId.namePattern, "instance name");
}
public static InstanceName from(String name) {
@@ -40,22 +22,15 @@ public class InstanceName implements Comparable<InstanceName> {
}
public static InstanceName defaultName() {
- return defaultInstance;
+ return defaultName;
}
public boolean isDefault() {
- return equals(InstanceName.defaultName());
+ return equals(defaultName);
}
public boolean isTester() {
return value().endsWith("-t");
}
- public String value() { return instanceName; }
-
- @Override
- public int compareTo(InstanceName instance) {
- return instanceName.compareTo(instance.instanceName);
- }
-
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java
index 92fe5345b4e..9909ab360a0 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java
@@ -1,56 +1,31 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.provision;
-import java.util.Objects;
+import ai.vespa.validation.PatternedStringWrapper;
/**
* Represents a tenant in the provision API.
*
* @author Ulf Lilleengen
*/
-public class TenantName implements Comparable<TenantName> {
+public class TenantName extends PatternedStringWrapper<TenantName> {
- private final String name;
+ private static final TenantName defaultName = new TenantName("default");
private TenantName(String name) {
- this.name = name;
+ super(name, ApplicationId.namePattern, "tenant name");
}
- public String value() { return name; }
-
- /**
- * Create a {@link TenantName} with a given name.
- *
- * @param name Name of tenant.
- * @return instance of {@link TenantName}.
- */
public static TenantName from(String name) {
return new TenantName(name);
}
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof TenantName)) return false;
- return Objects.equals(((TenantName)obj).value(), value());
- }
-
- @Override
- public String toString() {
- return name;
- }
-
public static TenantName defaultName() {
- return from("default");
+ return defaultName;
}
- @Override
- public int compareTo(TenantName that) {
- return this.name.compareTo(that.name);
+ public boolean isDefault() {
+ return equals(defaultName);
}
}
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/ApplicationIdTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/ApplicationIdTest.java
index c82230f7edf..01904b5eece 100644
--- a/config-provisioning/src/test/java/com/yahoo/config/provision/ApplicationIdTest.java
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/ApplicationIdTest.java
@@ -106,7 +106,7 @@ public class ApplicationIdTest {
builder.tenant("a");
builder.application("b");
builder.instance("c");
- ApplicationId applicationId = new ApplicationId(new ApplicationIdConfig(builder));
+ ApplicationId applicationId = ApplicationId.from(new ApplicationIdConfig(builder));
assertEquals("a", applicationId.tenant().value());
assertEquals("b", applicationId.application().value());
assertEquals("c", applicationId.instance().value());
diff --git a/configdefinitions/src/vespa/configserver.def b/configdefinitions/src/vespa/configserver.def
index 05143bfef9f..64ca1e522f5 100644
--- a/configdefinitions/src/vespa/configserver.def
+++ b/configdefinitions/src/vespa/configserver.def
@@ -20,6 +20,10 @@ configServerDBDir string default="var/db/vespa/config_server/serverdb/"
configDefinitionsDir string default="share/vespa/configdefinitions/"
fileReferencesDir string default="var/db/vespa/filedistribution/"
+# Application package
+# The maximum decompressed size of an application package, in bytes. Defaults to 8 GB
+maxApplicationPackageSize long default=8589934592
+
# Misc
sessionLifetime long default=3600 # in seconds
masterGeneration long default=0
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index 7a754dd84cd..76d7ff2fc7f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -31,6 +31,7 @@ import com.yahoo.docproc.jdisc.metric.NullMetric;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.Metric;
import com.yahoo.path.Path;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
@@ -558,27 +559,24 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
}
- public HttpResponse serviceStatusPage(ApplicationId applicationId, String hostName, String serviceName, String pathSuffix) {
+ public HttpResponse serviceStatusPage(ApplicationId applicationId, String hostName, String serviceName, HttpURL.Path pathSuffix) {
// WARNING: pathSuffix may be given by the external user. Make sure no security issues arise...
// We should be OK here, because at most, pathSuffix may change the parent path, but cannot otherwise
// change the hostname and port. Exposing other paths on the cluster controller should be fine.
// TODO: It would be nice to have a simple check to verify pathSuffix doesn't contain /../ components.
- String pathPrefix;
+ HttpURL.Path pathPrefix = HttpURL.Path.empty();
switch (serviceName) {
- case "container-clustercontroller": {
- pathPrefix = "clustercontroller-status/v1/";
+ case "container-clustercontroller":
+ pathPrefix = pathPrefix.append("clustercontroller-status").append("v1");
break;
- }
case "distributor":
- case "storagenode": {
- pathPrefix = "";
+ case "storagenode":
break;
- }
default:
throw new NotFoundException("No status page for service: " + serviceName);
}
- return httpProxy.get(getApplication(applicationId), hostName, serviceName, pathPrefix + pathSuffix);
+ return httpProxy.get(getApplication(applicationId), hostName, serviceName, pathPrefix.append(pathSuffix));
}
public Map<String, ClusterReindexing> getClusterReindexingStatus(ApplicationId applicationId) {
@@ -659,9 +657,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return getOptionalApplication(applicationId).map(app -> app.getModel().fileReferences()).orElse(Set.of());
}
- public ApplicationFile getApplicationFileFromSession(TenantName tenantName, long sessionId, String path, Session.Mode mode) {
+ public ApplicationFile getApplicationFileFromSession(TenantName tenantName, long sessionId, HttpURL.Path path, Session.Mode mode) {
Tenant tenant = tenantRepository.getTenant(tenantName);
- return getLocalSession(tenant, sessionId).getApplicationFile(Path.fromString(path), mode);
+ return getLocalSession(tenant, sessionId).getApplicationFile(Path.from(path.segments()), mode);
}
public Tenant getTenant(ApplicationId applicationId) {
@@ -1053,7 +1051,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private File decompressApplication(InputStream in, String contentType, File tempDir) {
try (CompressedApplicationInputStream application =
- CompressedApplicationInputStream.createFromCompressedStream(in, contentType)) {
+ CompressedApplicationInputStream.createFromCompressedStream(in, contentType, configserverConfig.maxApplicationPackageSize())) {
return decompressApplication(application, tempDir);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to decompress data in body", e);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStream.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStream.java
index 0672f13fd6a..443ab47e786 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStream.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStream.java
@@ -1,23 +1,21 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.application;
-import com.google.common.io.ByteStreams;
+import com.yahoo.compress.ArchiveStreamReader;
+import com.yahoo.compress.ArchiveStreamReader.Options;
+import com.yahoo.vespa.config.server.http.BadRequestException;
+import com.yahoo.vespa.config.server.http.InternalServerException;
+import com.yahoo.vespa.config.server.http.v2.ApplicationApiHandler;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.logging.Level;
-import com.yahoo.vespa.config.server.http.BadRequestException;
-import com.yahoo.vespa.config.server.http.InternalServerException;
-import com.yahoo.vespa.config.server.http.v2.ApplicationApiHandler;
-import org.apache.commons.compress.archivers.ArchiveEntry;
-import org.apache.commons.compress.archivers.ArchiveInputStream;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
-
import java.util.logging.Logger;
-import java.util.zip.GZIPInputStream;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -29,53 +27,43 @@ import static com.yahoo.yolean.Exceptions.uncheck;
public class CompressedApplicationInputStream implements AutoCloseable {
private static final Logger log = Logger.getLogger(CompressedApplicationInputStream.class.getPackage().getName());
- private final ArchiveInputStream ais;
+
+ private final ArchiveStreamReader reader;
+
+ private CompressedApplicationInputStream(ArchiveStreamReader reader) {
+ this.reader = reader;
+ }
/**
* Create an instance of a compressed application from an input stream.
*
* @param is the input stream containing the compressed files.
* @param contentType the content type for determining what kind of compressed stream should be used.
+ * @param maxSizeInBytes the maximum allowed size of the decompressed content
* @return An instance of an unpacked application.
*/
- public static CompressedApplicationInputStream createFromCompressedStream(InputStream is, String contentType) {
+ public static CompressedApplicationInputStream createFromCompressedStream(InputStream is, String contentType, long maxSizeInBytes) {
try {
- ArchiveInputStream ais = getArchiveInputStream(is, contentType);
- return createFromCompressedStream(ais);
- } catch (IOException e) {
+ Options options = Options.standard().maxSize(maxSizeInBytes).allowDotSegment(true);
+ switch (contentType) {
+ case ApplicationApiHandler.APPLICATION_X_GZIP:
+ return new CompressedApplicationInputStream(ArchiveStreamReader.ofTarGzip(is, options));
+ case ApplicationApiHandler.APPLICATION_ZIP:
+ return new CompressedApplicationInputStream(ArchiveStreamReader.ofZip(is, options));
+ default:
+ throw new BadRequestException("Unable to decompress");
+ }
+ } catch (UncheckedIOException e) {
throw new InternalServerException("Unable to create compressed application stream", e);
}
}
- static CompressedApplicationInputStream createFromCompressedStream(ArchiveInputStream ais) {
- return new CompressedApplicationInputStream(ais);
- }
-
- private static ArchiveInputStream getArchiveInputStream(InputStream is, String contentTypeHeader) throws IOException {
- ArchiveInputStream ais;
- switch (contentTypeHeader) {
- case ApplicationApiHandler.APPLICATION_X_GZIP:
- ais = new TarArchiveInputStream(new GZIPInputStream(is));
- break;
- case ApplicationApiHandler.APPLICATION_ZIP:
- ais = new ZipArchiveInputStream(is);
- break;
- default:
- throw new BadRequestException("Unable to decompress");
- }
- return ais;
- }
-
- private CompressedApplicationInputStream(ArchiveInputStream ais) {
- this.ais = ais;
- }
-
/**
* Close this stream.
* @throws IOException if the stream could not be closed
*/
public void close() throws IOException {
- ais.close();
+ reader.close();
}
File decompress() throws IOException {
@@ -83,45 +71,44 @@ public class CompressedApplicationInputStream implements AutoCloseable {
}
public File decompress(File dir) throws IOException {
- decompressInto(dir);
+ decompressInto(dir.toPath());
dir = findActualApplicationDir(dir);
return dir;
}
- private void decompressInto(File application) throws IOException {
- log.log(Level.FINE, () -> "Application is in " + application.getAbsolutePath());
+ private void decompressInto(Path dir) throws IOException {
+ if (!Files.isDirectory(dir)) throw new IllegalArgumentException("Not a directory: " + dir.toAbsolutePath());
+ log.log(Level.FINE, () -> "Application is in " + dir.toAbsolutePath());
int entries = 0;
- ArchiveEntry entry;
- while ((entry = ais.getNextEntry()) != null) {
- log.log(Level.FINE, "Unpacking %s", entry.getName());
- File outFile = new File(application, entry.getName());
- // FIXME/TODO: write more tests that break this logic. I have a feeling it is not very robust.
- if (entry.isDirectory()) {
- if (!(outFile.exists() && outFile.isDirectory())) {
- log.log(Level.FINE, () -> "Creating dir: " + outFile.getAbsolutePath());
- boolean res = outFile.mkdirs();
- if (!res) {
- log.log(Level.WARNING, "Could not create dir " + entry.getName());
- }
- }
- } else {
- log.log(Level.FINE, () -> "Creating output file: " + outFile.getAbsolutePath());
-
- // Create parent dir if necessary
- String parent = outFile.getParent();
- new File(parent).mkdirs();
-
- FileOutputStream fos = new FileOutputStream(outFile);
- ByteStreams.copy(ais, fos);
- fos.close();
+ Path tmpFile = null;
+ OutputStream tmpStream = null;
+ try {
+ tmpFile = createTempFile(dir);
+ tmpStream = Files.newOutputStream(tmpFile);
+ ArchiveStreamReader.ArchiveFile file;
+ while ((file = reader.readNextTo(tmpStream)) != null) {
+ tmpStream.close();
+ log.log(Level.FINE, "Creating output file: " + file.path());
+ Path dstFile = dir.resolve(file.path().toString()).normalize();
+ Files.createDirectories(dstFile.getParent());
+ Files.move(tmpFile, dstFile);
+ tmpFile = createTempFile(dir);
+ tmpStream = Files.newOutputStream(tmpFile);
+ entries++;
}
- entries++;
+ } finally {
+ if (tmpStream != null) tmpStream.close();
+ if (tmpFile != null) Files.deleteIfExists(tmpFile);
}
if (entries == 0) {
- log.log(Level.WARNING, "Not able to read any entries from " + application.getName());
+ log.log(Level.WARNING, "Not able to decompress any entries to " + dir);
}
}
+ private static Path createTempFile(Path applicationDir) throws IOException {
+ return Files.createTempFile(applicationDir, "application", null);
+ }
+
private File findActualApplicationDir(File application) {
// If application is in e.g. application/, use that as root for UnpackedApplication
// TODO: Vespa 8: Remove application/ directory support
@@ -131,4 +118,5 @@ public class CompressedApplicationInputStream implements AutoCloseable {
}
return application;
}
+
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
index a8915d187f3..14acd1b5630 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
@@ -10,8 +10,14 @@ import com.yahoo.container.jdisc.HttpResponse;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
+
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Scheme;
import com.yahoo.vespa.config.server.http.HttpErrorResponse;
import com.yahoo.vespa.config.server.http.HttpFetcher;
+import com.yahoo.vespa.config.server.http.HttpFetcher.Params;
import com.yahoo.vespa.config.server.http.NotFoundException;
import com.yahoo.vespa.config.server.http.SimpleHttpFetcher;
@@ -33,7 +39,7 @@ public class HttpProxy {
this.fetcher = fetcher;
}
- public HttpResponse get(Application application, String hostName, String serviceType, String relativePath) {
+ public HttpResponse get(Application application, String hostName, String serviceType, Path relativePath) {
HostInfo host = application.getModel().getHosts().stream()
.filter(hostInfo -> hostInfo.getHostname().equals(hostName))
.findFirst()
@@ -54,18 +60,15 @@ public class HttpProxy {
return internalGet(host.getHostname(), port.getPort(), relativePath);
}
- private HttpResponse internalGet(String hostname, int port, String relativePath) {
- String urlString = "http://" + hostname + ":" + port + "/" + relativePath;
- URL url;
+ private HttpResponse internalGet(String hostname, int port, Path relativePath) {
+ HttpURL url = HttpURL.create(Scheme.http, DomainName.of(hostname), port, relativePath);
try {
- url = new URL(urlString);
+ return fetcher.get(new Params(2000), // 2_000 ms read timeout
+ url.asURI().toURL());
} catch (MalformedURLException e) {
- logger.log(Level.WARNING, "Badly formed url: " + urlString, e);
+ logger.log(Level.WARNING, "Badly formed url: " + url, e);
return HttpErrorResponse.internalServerError("Failed to construct URL for backend");
}
-
- HttpFetcher.Params params = new HttpFetcher.Params(2000); // 2_000 ms read timeout
- return fetcher.get(params, url);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index 0ee9bc84ff7..fe9ab82637f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -211,7 +211,7 @@ public class ModelContextImpl implements ModelContext {
private final boolean useQrserverServiceName;
private final boolean avoidRenamingSummaryFeatures;
private final boolean experimentalSdParsing;
- private final Architecture adminClusterNodeResourcesArchitecture;
+ private final Architecture adminClusterArchitecture;
public FeatureFlags(FlagSource source, ApplicationId appId, Version version) {
this.defaultTermwiseLimit = flagValue(source, appId, version, Flags.DEFAULT_TERM_WISE_LIMIT);
@@ -260,7 +260,7 @@ public class ModelContextImpl implements ModelContext {
this.useQrserverServiceName = flagValue(source, appId, version, Flags.USE_QRSERVER_SERVICE_NAME);
this.avoidRenamingSummaryFeatures = flagValue(source, appId, version, Flags.AVOID_RENAMING_SUMMARY_FEATURES);
this.experimentalSdParsing = flagValue(source, appId, version, Flags.EXPERIMENTAL_SD_PARSING);
- this.adminClusterNodeResourcesArchitecture = Architecture.valueOf(flagValue(source, appId, version, PermanentFlags.ADMIN_CLUSTER_NODE_ARCHITECTURE));
+ this.adminClusterArchitecture = Architecture.valueOf(flagValue(source, appId, version, PermanentFlags.ADMIN_CLUSTER_NODE_ARCHITECTURE));
}
@Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; }
@@ -311,8 +311,7 @@ public class ModelContextImpl implements ModelContext {
@Override public boolean useQrserverServiceName() { return useQrserverServiceName; }
@Override public boolean avoidRenamingSummaryFeatures() { return avoidRenamingSummaryFeatures; }
@Override public boolean experimentalSdParsing() { return experimentalSdParsing; }
- @Override public String adminClusterNodeArchitecture() { return adminClusterArchitecture().name(); }
- @Override public Architecture adminClusterArchitecture() { return adminClusterNodeResourcesArchitecture; }
+ @Override public Architecture adminClusterArchitecture() { return adminClusterArchitecture; }
private static <V> V flagValue(FlagSource source, ApplicationId appId, Version vespaVersion, UnboundFlag<? extends V, ?, ?> flag) {
return flag.bindTo(source)
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
index 13acc121fa0..0ceb459233b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.http;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
@@ -32,7 +33,7 @@ public class ContentHandler {
public HttpResponse put(ContentRequest request) {
ApplicationFile file = request.getFile();
- if (request.getPath().endsWith("/")) {
+ if (request.getPath().hasTrailingSlash()) {
createDirectory(request, file);
} else {
createFile(request, file);
@@ -62,9 +63,9 @@ public class ContentHandler {
return new SessionContentStatusResponse(file, urlBase);
}
- private static List<ApplicationFile> listSortedFiles(ApplicationFile file, String path, boolean recursive) {
- if (!path.isEmpty() && !path.endsWith("/")) {
- return Arrays.asList(file);
+ private static List<ApplicationFile> listSortedFiles(ApplicationFile file, Path path, boolean recursive) {
+ if ( ! path.segments().isEmpty() && ! path.hasTrailingSlash()) {
+ return List.of(file);
}
List<ApplicationFile> files = file.listFiles(recursive);
Collections.sort(files);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
index 995c4b2dc56..f06e1dabf8c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.http;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import java.io.InputStream;
@@ -20,11 +21,11 @@ public abstract class ContentRequest {
enum ReturnType {CONTENT, STATUS}
private final long sessionId;
- private final String path;
+ private final Path path;
private final ApplicationFile file;
private final HttpRequest request;
- protected ContentRequest(HttpRequest request, long sessionId, String path, ApplicationFile applicationFile) {
+ protected ContentRequest(HttpRequest request, long sessionId, Path path, ApplicationFile applicationFile) {
this.request = request;
this.sessionId = sessionId;
this.path = path;
@@ -77,7 +78,7 @@ public abstract class ContentRequest {
}
- String getPath() {
+ Path getPath() {
return path;
}
@@ -86,8 +87,8 @@ public abstract class ContentRequest {
}
ApplicationFile getExistingFile() {
- if (!file.exists()) {
- throw new NotFoundException("Session " + sessionId + " does not contain a file '" + path + "'");
+ if ( ! file.exists()) {
+ throw new NotFoundException("Session " + sessionId + " does not contain a file at " + path);
}
return file;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java
index 1a8b36ee19f..c8953d5996c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java
@@ -49,9 +49,11 @@ public class ApplicationApiHandler extends SessionHandler {
public final static String MULTIPART_PARAMS = "prepareParams";
public final static String MULTIPART_APPLICATION_PACKAGE = "applicationPackage";
public final static String contentTypeHeader = "Content-Type";
+
private final TenantRepository tenantRepository;
private final Duration zookeeperBarrierTimeout;
private final Zone zone;
+ private final long maxApplicationPackageSize;
@Inject
public ApplicationApiHandler(Context ctx,
@@ -61,6 +63,7 @@ public class ApplicationApiHandler extends SessionHandler {
super(ctx, applicationRepository);
this.tenantRepository = applicationRepository.tenantRepository();
this.zookeeperBarrierTimeout = Duration.ofSeconds(configserverConfig.zookeeper().barrierTimeout());
+ this.maxApplicationPackageSize = configserverConfig.maxApplicationPackageSize();
this.zone = zone;
}
@@ -85,14 +88,14 @@ public class ApplicationApiHandler extends SessionHandler {
log.log(Level.FINE, "Deploy parameters: [{0}]", new String(params, StandardCharsets.UTF_8));
prepareParams = PrepareParams.fromJson(params, tenantName, zookeeperBarrierTimeout);
Part appPackagePart = parts.get(MULTIPART_APPLICATION_PACKAGE);
- compressedStream = createFromCompressedStream(appPackagePart.getInputStream(), appPackagePart.getContentType());
+ compressedStream = createFromCompressedStream(appPackagePart.getInputStream(), appPackagePart.getContentType(), maxApplicationPackageSize);
} catch (IOException e) {
log.log(Level.WARNING, "Unable to parse multipart in deploy", e);
throw new BadRequestException("Request contains invalid data");
}
} else {
prepareParams = PrepareParams.fromHttpRequest(request, tenantName, zookeeperBarrierTimeout);
- compressedStream = createFromCompressedStream(request.getData(), request.getHeader(contentTypeHeader));
+ compressedStream = createFromCompressedStream(request.getData(), request.getHeader(contentTypeHeader), maxApplicationPackageSize);
}
PrepareResult result = applicationRepository.deploy(compressedStream, prepareParams);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index 0707261bb45..885456ff69c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -16,6 +16,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.Response;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.slime.Cursor;
@@ -132,23 +133,23 @@ public class ApplicationHandler extends HttpHandler {
return HttpServiceResponse.createResponse(response, hostAndPort, request.getUri());
}
- private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, String pathSuffix) {
+ private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, HttpURL.Path pathSuffix) {
return applicationRepository.serviceStatusPage(applicationId, hostname, service, pathSuffix);
}
- private HttpResponse content(ApplicationId applicationId, String contentPath, HttpRequest request) {
+ private HttpResponse content(ApplicationId applicationId, HttpURL.Path contentPath, HttpRequest request) {
long sessionId = applicationRepository.getSessionIdForApplication(applicationId);
ApplicationFile applicationFile =
applicationRepository.getApplicationFileFromSession(applicationId.tenant(),
- sessionId,
- contentPath,
- ContentRequest.getApplicationFileMode(request.getMethod()));
+ sessionId,
+ contentPath,
+ ContentRequest.getApplicationFileMode(request.getMethod()));
ApplicationContentRequest contentRequest = new ApplicationContentRequest(request,
- sessionId,
- applicationId,
- zone,
- contentPath,
- applicationFile);
+ sessionId,
+ applicationId,
+ zone,
+ contentPath,
+ applicationFile);
return new ContentHandler().get(contentRequest);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
index 17a8e1449c4..f6af9e616a9 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.http.ContentRequest;
import com.yahoo.vespa.config.server.http.ContentHandler;
@@ -52,7 +53,7 @@ public class SessionContentHandler extends SessionHandler {
TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
validateRequest(tenantName);
long sessionId = getSessionIdV2(request);
- String contentPath = SessionContentRequestV2.getContentPath(request);
+ Path contentPath = SessionContentRequestV2.getContentPath(request);
ApplicationFile applicationFile =
applicationRepository.getApplicationFileFromSession(tenantName,
sessionId,
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
index 34c842eca44..15d6c5c18ff 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
@@ -5,6 +5,7 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.http.ContentRequest;
/**
@@ -22,7 +23,7 @@ public class ApplicationContentRequest extends ContentRequest {
long sessionId,
ApplicationId applicationId,
Zone zone,
- String contentPath,
+ Path contentPath,
ApplicationFile applicationFile) {
super(request, sessionId, contentPath, applicationFile);
this.applicationId = applicationId;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
index da71fd89054..449058eb911 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
@@ -5,6 +5,8 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.jdisc.application.BindingMatch;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.Path;
import com.yahoo.vespa.config.server.http.ContentRequest;
import com.yahoo.vespa.config.server.http.Utils;
@@ -16,14 +18,14 @@ import com.yahoo.vespa.config.server.http.Utils;
* @since 5.3
*/
public class SessionContentRequestV2 extends ContentRequest {
- private static final String uriPattern = "http://*/application/v2/tenant/*/session/*/content/*";
+
private final TenantName tenantName;
private final long sessionId;
public SessionContentRequestV2(HttpRequest request,
long sessionId,
TenantName tenantName,
- String path,
+ HttpURL.Path path,
ApplicationFile applicationFile) {
super(request, sessionId, path, applicationFile);
this.tenantName = tenantName;
@@ -35,8 +37,11 @@ public class SessionContentRequestV2 extends ContentRequest {
return "/application/v2/tenant/" + tenantName.value() + "/session/" + sessionId;
}
- public static String getContentPath(HttpRequest request) {
- BindingMatch<?> bm = Utils.getBindingMatch(request, uriPattern);
- return bm.group(4);
+ public static HttpURL.Path getContentPath(HttpRequest request) {
+ Path path = new Path(request.getUri());
+ if ( ! path.matches("/application/v2/tenant/{tenant}/session/{session}/content/{*}"))
+ throw new IllegalStateException("error in request routing");
+ return path.getRest();
}
+
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java
index d3976464bde..7e6fccb6d2f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java
@@ -193,10 +193,9 @@ public class DelayedConfigResponses {
}
private synchronized void metricDelayedResponses(ApplicationId app, int elems) {
- if ( ! metrics.containsKey(app)) {
- metrics.put(app, rpcServer.metricUpdaterFactory().getOrCreateMetricUpdater(Metrics.createDimensions(app)));
- }
- metrics.get(app).setDelayedResponses(elems);
+ metrics.computeIfAbsent(app, key -> rpcServer.metricUpdaterFactory()
+ .getOrCreateMetricUpdater(Metrics.createDimensions(key)))
+ .setDelayedResponses(elems);
}
private synchronized void createQueueIfNotExists(GetConfigContext context) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
index 99ffff6403b..ebf1fb32141 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
@@ -243,13 +243,9 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener {
}
private ApplicationState getState(ApplicationId id) {
- ApplicationState state = applicationStateMap.get(id);
- if (state == null) {
- applicationStateMap.putIfAbsent(id, new ApplicationState(0));
- state = applicationStateMap.get(id);
- }
- return state;
+ return applicationStateMap.computeIfAbsent(id, __ -> new ApplicationState(0));
}
+
boolean hasNewerGeneration(ApplicationId id, long generation) {
return getState(id).getActiveGeneration() > generation;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
index 30975be61e2..8b9714c3bfb 100644
--- a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
@@ -5,6 +5,11 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
+import com.yahoo.restapi.HttpURL.Scheme;
import com.yahoo.restapi.RestApi;
import com.yahoo.restapi.RestApiRequestHandler;
import com.yahoo.restapi.UriBuilder;
@@ -106,7 +111,7 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
String regionName = context.pathParameters().getStringOrThrow("regionName");
String instanceName = context.pathParameters().getStringOrThrow("instanceName");
String identifier = context.pathParameters().getStringOrThrow("serviceIdentifier");
- String apiParams = context.pathParameters().getString("*").orElse("");
+ Path apiParams = context.pathParameters().getRest().orElse(Path.empty());
return singleService(context.uriBuilder(), context.request().getUri(), tenantName, applicationName, environmentName, regionName, instanceName, identifier, apiParams);
}
@@ -125,7 +130,7 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
}
protected HashMap<?, ?> singleService(
- UriBuilder uriBuilder, URI requestUri, String tenantName, String applicationName, String environmentName, String regionName, String instanceName, String identifier, String apiParams) {
+ UriBuilder uriBuilder, URI requestUri, String tenantName, String applicationName, String environmentName, String regionName, String instanceName, String identifier, Path apiParams) {
ServiceModel model = new ServiceModel(getModelConfig(tenantName, applicationName, environmentName, regionName, instanceName));
Service s = model.getService(identifier);
int requestedPort = s.matchIdentifierWithPort(identifier);
@@ -135,11 +140,9 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
return apiResult;
}
- protected HealthClient getHealthClient(String apiParams, Service s, int requestedPort, String uriQuery, Client client) {
- final StringBuilder uriBuffer = new StringBuilder("http://").append(s.host).append(':').append(requestedPort).append('/')
- .append(apiParams);
- addQuery(uriQuery, uriBuffer);
- WebTarget target = client.target(uriBuffer.toString());
+ protected HealthClient getHealthClient(Path apiParams, Service s, int requestedPort, String uriQuery, Client client) {
+ URI uri = HttpURL.create(Scheme.http, DomainName.of(s.host), requestedPort, apiParams, Query.parse(uriQuery)).asURI();
+ WebTarget target = client.target(uri);
return WebResourceFactory.newResource(HealthClient.class, target);
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java
index 23444ac53d6..c7662ac9ee4 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java
@@ -2,24 +2,27 @@
package com.yahoo.vespa.config.server.application;
import com.google.common.io.ByteStreams;
+import com.yahoo.vespa.config.server.http.InternalServerException;
+import com.yahoo.yolean.Exceptions;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
-import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
-import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
@@ -27,6 +30,9 @@ import static org.junit.Assert.assertTrue;
*/
public class CompressedApplicationInputStreamTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
private static void writeFileToTar(ArchiveOutputStream taos, File file) throws IOException {
taos.putArchiveEntry(taos.createArchiveEntry(file, file.getName()));
ByteStreams.copy(new FileInputStream(file), taos);
@@ -42,14 +48,14 @@ public class CompressedApplicationInputStreamTest {
return outFile;
}
- public static File createTarFile() throws IOException {
- File outFile = File.createTempFile("testapp", ".tar.gz");
+ public static File createTarFile(Path dir) throws IOException {
+ File outFile = Files.createTempFile(dir, "testapp", ".tar.gz").toFile();
ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outFile)));
return createArchiveFile(archiveOutputStream, outFile);
}
- private static File createZipFile() throws IOException {
- File outFile = File.createTempFile("testapp", ".tar.gz");
+ private File createZipFile(Path dir) throws IOException {
+ File outFile = Files.createTempFile(dir, "testapp", ".tar.gz").toFile();
ArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(new FileOutputStream(outFile));
return createArchiveFile(archiveOutputStream, outFile);
}
@@ -63,15 +69,16 @@ public class CompressedApplicationInputStreamTest {
@Test
public void require_that_valid_tar_application_can_be_unpacked() throws IOException {
- File outFile = createTarFile();
- CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(outFile))));
- File outApp = unpacked.decompress();
- assertTestApp(outApp);
+ File outFile = createTarFile(temporaryFolder.getRoot().toPath());
+ try (CompressedApplicationInputStream unpacked = streamFromTarGz(outFile)) {
+ File outApp = unpacked.decompress();
+ assertTestApp(outApp);
+ }
}
@Test
public void require_that_valid_tar_application_in_subdir_can_be_unpacked() throws IOException {
- File outFile = File.createTempFile("testapp", ".tar.gz");
+ File outFile = Files.createTempFile(temporaryFolder.getRoot().toPath(), "testapp", ".tar.gz").toFile();
ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outFile)));
File app = new File("src/test/resources/deploy/validapp");
@@ -91,48 +98,39 @@ public class CompressedApplicationInputStreamTest {
archiveOutputStream.close();
- CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(outFile))));
- File outApp = unpacked.decompress();
- assertEquals("application", outApp.getName()); // gets the name of the subdir
- assertTestApp(outApp);
+ try (CompressedApplicationInputStream unpacked = streamFromTarGz(outFile)) {
+ File outApp = unpacked.decompress();
+ assertEquals("application", outApp.getName()); // gets the name of the subdir
+ assertTestApp(outApp);
+ }
}
@Test
public void require_that_valid_zip_application_can_be_unpacked() throws IOException {
- File outFile = createZipFile();
- CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
- new ZipArchiveInputStream(new FileInputStream(outFile)));
- File outApp = unpacked.decompress();
- assertTestApp(outApp);
+ File outFile = createZipFile(temporaryFolder.getRoot().toPath());
+ try (CompressedApplicationInputStream unpacked = streamFromZip(outFile)) {
+ File outApp = unpacked.decompress();
+ assertTestApp(outApp);
+ }
}
@Test
public void require_that_gnu_tared_file_can_be_unpacked() throws IOException, InterruptedException {
- File tmpTar = File.createTempFile("myapp", ".tar");
- Process p = new ProcessBuilder("tar", "-C", "src/test/resources/deploy/validapp", "--exclude=.svn", "-cvf", tmpTar.getAbsolutePath(), ".").start();
- p.waitFor();
- p = new ProcessBuilder("gzip", tmpTar.getAbsolutePath()).start();
- p.waitFor();
- File gzFile = new File(tmpTar.getAbsolutePath() + ".gz");
+ File gzFile = createTarGz("src/test/resources/deploy/validapp");
assertTrue(gzFile.exists());
- CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
- new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(gzFile))));
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(new FileInputStream(gzFile), "application/x-gzip", Long.MAX_VALUE);
File outApp = unpacked.decompress();
assertTestApp(outApp);
}
@Test
public void require_that_nested_app_can_be_unpacked() throws IOException, InterruptedException {
- File tmpTar = File.createTempFile("myapp", ".tar");
- Process p = new ProcessBuilder("tar", "-C", "src/test/resources/deploy/advancedapp", "--exclude=.svn", "-cvf", tmpTar.getAbsolutePath(), ".").start();
- p.waitFor();
- p = new ProcessBuilder("gzip", tmpTar.getAbsolutePath()).start();
- p.waitFor();
- File gzFile = new File(tmpTar.getAbsolutePath() + ".gz");
+ File gzFile = createTarGz("src/test/resources/deploy/advancedapp");
assertTrue(gzFile.exists());
- CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
- new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(gzFile))));
- File outApp = unpacked.decompress();
+ File outApp;
+ try (CompressedApplicationInputStream unpacked = streamFromTarGz(gzFile)) {
+ outApp = unpacked.decompress();
+ }
List<File> files = Arrays.asList(outApp.listFiles());
assertEquals(5, files.size());
assertTrue(files.contains(new File(outApp, "services.xml")));
@@ -164,11 +162,29 @@ public class CompressedApplicationInputStreamTest {
assertEquals(new File(bar, "lol").getAbsolutePath(), bar.listFiles()[0].getAbsolutePath());
}
-
- @Test(expected = IOException.class)
- public void require_that_invalid_application_returns_error_when_unpacked() throws IOException {
+ @Test(expected = InternalServerException.class)
+ public void require_that_invalid_application_returns_error_when_unpacked() throws Exception {
File app = new File("src/test/resources/deploy/validapp/services.xml");
- CompressedApplicationInputStream.createFromCompressedStream(
- new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(app))));
+ streamFromTarGz(app).close();
}
+
+ private File createTarGz(String appDir) throws IOException, InterruptedException {
+ File tmpTar = Files.createTempFile(temporaryFolder.getRoot().toPath(), "myapp", ".tar").toFile();
+ Process p = new ProcessBuilder("tar", "-C", appDir, "-cvf", tmpTar.getAbsolutePath(), ".").start();
+ p.waitFor();
+ p = new ProcessBuilder("gzip", tmpTar.getAbsolutePath()).start();
+ p.waitFor();
+ File gzFile = new File(tmpTar.getAbsolutePath() + ".gz");
+ assertTrue(gzFile.exists());
+ return gzFile;
+ }
+
+ private static CompressedApplicationInputStream streamFromZip(File zipFile) {
+ return Exceptions.uncheck(() -> CompressedApplicationInputStream.createFromCompressedStream(new FileInputStream(zipFile), "application/zip", Long.MAX_VALUE));
+ }
+
+ private static CompressedApplicationInputStream streamFromTarGz(File tarFile) {
+ return Exceptions.uncheck(() -> CompressedApplicationInputStream.createFromCompressedStream(new FileInputStream(tarFile), "application/x-gzip", Long.MAX_VALUE));
+ }
+
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
index ac0a6491100..83cae04cbfd 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.http.HttpFetcher;
import com.yahoo.vespa.config.server.http.RequestTimeoutException;
import com.yahoo.vespa.config.server.http.StaticResponse;
@@ -48,7 +49,7 @@ public class HttpProxyTest {
when(fetcher.get(actualParams.capture(), actualUrl.capture())).thenReturn(response);
HttpResponse actualResponse = proxy.get(applicationMock, hostname, CLUSTERCONTROLLER_CONTAINER.serviceName,
- "clustercontroller-status/v1/clusterName");
+ Path.parse("clustercontroller-status/v1/clusterName"));
assertEquals(1, actualParams.getAllValues().size());
assertEquals(2000, actualParams.getValue().readTimeoutMs);
@@ -67,7 +68,7 @@ public class HttpProxyTest {
when(fetcher.get(any(), any())).thenThrow(new RequestTimeoutException("timed out"));
proxy.get(applicationMock, hostname, CLUSTERCONTROLLER_CONTAINER.serviceName,
- "clustercontroller-status/v1/clusterName");
+ Path.parse("clustercontroller-status/v1/clusterName"));
}
private static MockModel createClusterController() {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
index 69caf86729f..0732379e0d9 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
@@ -34,7 +34,7 @@ public class HandlerTest {
if (contentType != null) {
assertEquals(renderedString, contentType, response.getContentType());
}
- assertTrue(renderedString.contains(message));
+ assertTrue("\n" + renderedString + "\n should contain \n" + message, renderedString.contains(message));
}
public static void assertHttpStatusCodeErrorCodeAndMessage(HttpResponse response, int statusCode, HttpErrorResponse.ErrorCode errorCode, String message) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 005dd715dd4..9f7e539a2e3 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -15,6 +15,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.MockLogRetriever;
@@ -356,16 +357,20 @@ public class ApplicationHandlerTest {
.withHttpProxy(mockHttpProxy)
.build();
ApplicationHandler mockHandler = createApplicationHandler(applicationRepository);
- doAnswer(invoc -> new StaticResponse(200, "text/html", "<html>" +
- "host=" + invoc.getArgument(1, String.class) + "," +
- "service=" + invoc.getArgument(2, String.class) + "," +
- "path=" + invoc.getArgument(3, String.class) + "</html>")).when(mockHttpProxy).get(any(), any(), any(), any());
+ doAnswer(invoc -> new StaticResponse(200,
+ "text/html",
+ "<html>" +
+ "host=" + invoc.getArgument(1, String.class) + "," +
+ "service=" + invoc.getArgument(2, String.class) + "," +
+ "path=" + invoc.getArgument(3, HttpURL.Path.class) +
+ "</html>"))
+ .when(mockHttpProxy).get(any(), any(), any(), any());
HttpResponse response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/container-clustercontroller/" + host + "/status/some/path/clusterName1", GET));
- assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=container-clustercontroller,path=clustercontroller-status/v1/some/path/clusterName1</html>");
+ assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=container-clustercontroller,path=path '/clustercontroller-status/v1/some/path/clusterName1'</html>");
response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/distributor/" + host + "/status/something", GET));
- assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=distributor,path=something</html>");
+ assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=distributor,path=path '/something'</html>");
response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/fake-service/" + host + "/status/something", GET));
assertHttpStatusCodeAndMessage(response, 404, "{\"error-code\":\"NOT_FOUND\",\"message\":\"No status page for service: fake-service\"}");
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
index 790be6d45ba..7c2e0be0c3a 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
@@ -121,13 +121,13 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase {
@Test
public void require_that_nonexistent_file_returns_not_found_when_deleted() throws IOException {
- assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file 'test2.txt'\"}");
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file at path '/test2.txt'\"}");
}
@Test
public void require_that_files_can_be_deleted() throws IOException {
assertDeleteFile(Response.Status.OK, "/test.txt");
- assertDeleteFile(Response.Status.NOT_FOUND, "/test.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file 'test.txt'\"}");
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file at path '/test.txt'\"}");
assertDeleteFile(Response.Status.BAD_REQUEST, "/newtest", "{\"error-code\":\"BAD_REQUEST\",\"message\":\"File 'newtest' is not an empty directory\"}");
assertDeleteFile(Response.Status.OK, "/newtest/testfile.txt");
assertDeleteFile(Response.Status.OK, "/newtest");
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
index 702dd2792da..58a3593ae14 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.MockProvisioner;
import com.yahoo.vespa.config.server.application.CompressedApplicationInputStreamTest;
@@ -109,20 +110,20 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
@Test
public void require_that_post_request_must_have_correct_content_type() throws IOException {
HashMap<String, String> headers = new HashMap<>(); // no Content-Type header
- File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile(temporaryFolder.getRoot().toPath());
HttpResponse response = createHandler().handle(post(outFile, headers, null));
assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.ErrorCode.BAD_REQUEST, "Request contains no Content-Type header");
}
private void assertIllegalFromParameter(String fromValue) throws IOException {
- File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile(temporaryFolder.getRoot().toPath());
HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue));
assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.ErrorCode.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'");
}
@Test
public void require_that_prepare_url_is_returned_on_success() throws IOException {
- File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile(temporaryFolder.getRoot().toPath());
Map<String, String> parameters = Collections.singletonMap("name", "foo");
HttpResponse response = createHandler().handle(post(outFile, postHeaders, parameters));
assertNotNull(response);
@@ -143,7 +144,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
@Test
public void require_internal_error_when_exception() throws IOException {
- File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile(temporaryFolder.getRoot().toPath());
new FileWriter(outFile).write("rubbish");
HttpResponse response = createHandler().handle(post(outFile));
assertHttpStatusCodeErrorCodeAndMessage(response, INTERNAL_SERVER_ERROR,
@@ -153,9 +154,9 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
@Test
public void require_that_handler_unpacks_application() throws IOException {
- File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile(temporaryFolder.getRoot().toPath());
createHandler().handle(post(outFile));
- ApplicationFile applicationFile = applicationRepository.getApplicationFileFromSession(tenant, 2, "services.xml", Session.Mode.READ);
+ ApplicationFile applicationFile = applicationRepository.getApplicationFileFromSession(tenant, 2, Path.parse("services.xml"), Session.Mode.READ);
assertTrue(applicationFile.exists());
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
index fe025c9e861..35e63c46bf8 100644
--- a/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.serviceview;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.jdisc.test.MockMetric;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.restapi.UriBuilder;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import com.yahoo.vespa.serviceview.bindings.HealthClient;
@@ -45,7 +46,7 @@ public class StateRequestHandlerTest {
}
@Override
- protected HealthClient getHealthClient(String apiParams, Service s, int requestedPort, String uriQuery, Client client) {
+ protected HealthClient getHealthClient(Path apiParams, Service s, int requestedPort, String uriQuery, Client client) {
HealthClient healthClient = Mockito.mock(HealthClient.class);
HashMap<Object, Object> dummyHealthData = new HashMap<>();
HashMap<String, String> dummyLink = new HashMap<>();
@@ -75,7 +76,7 @@ public class StateRequestHandlerTest {
public final void test() {
Service s = correspondingModel.resolve("vespa.yahoo.com", 8080, null);
String api = "/state/v1";
- HashMap<?, ?> boom = testHandler.singleService(new UriBuilder("http://someserver:8080"), URI.create(EXTERNAL_BASE_URI), "default", "default", "default", "default", "default", s.getIdentifier(8080), api);
+ HashMap<?, ?> boom = testHandler.singleService(new UriBuilder("http://someserver:8080"), URI.create(EXTERNAL_BASE_URI), "default", "default", "default", "default", "default", s.getIdentifier(8080), Path.parse(api));
assertEquals(EXTERNAL_BASE_URI + "tenant/default/application/default/environment/default/region/default/instance/default/service/" + s.getIdentifier(8080) + api,
((Map<?, ?>) ((List<?>) boom.get("resources")).get(0)).get("url"));
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/documentapi/DocumentAccessProvider.java b/container-core/src/main/java/com/yahoo/container/core/documentapi/DocumentAccessProvider.java
index 44a70ea2f3b..be8ba669ec0 100644
--- a/container-core/src/main/java/com/yahoo/container/core/documentapi/DocumentAccessProvider.java
+++ b/container-core/src/main/java/com/yahoo/container/core/documentapi/DocumentAccessProvider.java
@@ -19,10 +19,10 @@ public class DocumentAccessProvider implements Provider<VespaDocumentAccess> {
private final VespaDocumentAccess access;
@Inject
- public DocumentAccessProvider(DocumentmanagerConfig documentmanagerConfig, LoadTypeConfig loadTypeConfig,
+ public DocumentAccessProvider(DocumentmanagerConfig documentmanagerConfig,
MessagebusConfig messagebusConfig, DocumentProtocolPoliciesConfig policiesConfig,
DistributionConfig distributionConfig) {
- this.access = new VespaDocumentAccess(documentmanagerConfig, loadTypeConfig, System.getProperty("config.id"),
+ this.access = new VespaDocumentAccess(documentmanagerConfig, System.getProperty("config.id"),
messagebusConfig, policiesConfig, distributionConfig);
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/documentapi/VespaDocumentAccess.java b/container-core/src/main/java/com/yahoo/container/core/documentapi/VespaDocumentAccess.java
index 6976299cc7d..1775dbe53c1 100644
--- a/container-core/src/main/java/com/yahoo/container/core/documentapi/VespaDocumentAccess.java
+++ b/container-core/src/main/java/com/yahoo/container/core/documentapi/VespaDocumentAccess.java
@@ -43,13 +43,12 @@ public class VespaDocumentAccess extends DocumentAccess {
private boolean shutDown = false;
VespaDocumentAccess(DocumentmanagerConfig documentmanagerConfig,
- LoadTypeConfig loadTypeConfig,
String slobroksConfigId,
MessagebusConfig messagebusConfig,
DocumentProtocolPoliciesConfig policiesConfig,
DistributionConfig distributionConfig) {
super(new DocumentAccessParams().setDocumentmanagerConfig(documentmanagerConfig));
- this.parameters = new MessageBusParams(new LoadTypeSet(loadTypeConfig))
+ this.parameters = new MessageBusParams()
.setDocumentProtocolPoliciesConfig(policiesConfig, distributionConfig);
this.parameters.setDocumentmanagerConfig(documentmanagerConfig);
this.parameters.getRPCNetworkParams().setSlobrokConfigId(slobroksConfigId);
diff --git a/container-core/src/main/java/com/yahoo/container/logging/LogFileHandler.java b/container-core/src/main/java/com/yahoo/container/logging/LogFileHandler.java
index c97e128b170..4d6f470637b 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/LogFileHandler.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/LogFileHandler.java
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.logging;
-import com.yahoo.compress.ZstdOuputStream;
+import com.yahoo.compress.ZstdOutputStream;
import com.yahoo.io.NativeIO;
import com.yahoo.log.LogFileDb;
import com.yahoo.protect.Process;
@@ -401,7 +401,7 @@ class LogFileHandler <LOGTYPE> {
Path compressedFile = Paths.get(oldFile.toString() + ".zst");
int bufferSize = 2*1024*1024;
try (FileOutputStream fileOut = AtomicFileOutputStream.create(compressedFile);
- ZstdOuputStream out = new ZstdOuputStream(fileOut, bufferSize);
+ ZstdOutputStream out = new ZstdOutputStream(fileOut, bufferSize);
FileInputStream in = new FileInputStream(oldFile.toFile())) {
pageFriendlyTransfer(nativeIO, out, fileOut.getFD(), in, bufferSize);
out.flush();
diff --git a/container-core/src/main/java/com/yahoo/restapi/HttpURL.java b/container-core/src/main/java/com/yahoo/restapi/HttpURL.java
new file mode 100644
index 00000000000..e890b0fe71a
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/restapi/HttpURL.java
@@ -0,0 +1,451 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.restapi;
+
+import ai.vespa.validation.StringWrapper;
+import com.yahoo.net.DomainName;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.OptionalInt;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+
+import static ai.vespa.validation.Validation.require;
+import static ai.vespa.validation.Validation.requireInRange;
+import static java.net.URLDecoder.decode;
+import static java.net.URLEncoder.encode;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * This is the best class for creating, manipulating and inspecting HTTP URLs, because:
+ * <ul>
+ * <li>It is more restrictive than {@link URI}, but with a richer construction API, reducing risk of blunder.
+ * <ul>
+ * <li>Scheme must be HTTP or HTTPS.</li>
+ * <li>Authority must be a {@link DomainName}, with an optional port.</li>
+ * <li>{@link Path} must be normalized at all times.</li>
+ * <li>Only {@link Query} is allowed, in addition to the above.</li>
+ * </ul>
+ * </li>
+ * <li>
+ * It contains all those helpful builder methods that {@link URI} has none of.
+ * <ul>
+ * <li>{@link Path} can be parsed, have segments or other paths appended, and cut.</li>
+ * <li>{@link Query} can be parsed, and keys and key-value pairs can be inserted or removed.</li>
+ * </ul>
+ * All these (except the parse methods) operate on <em>decoded</em> values.
+ * </li>
+ * <li>It makes it super-easy to use a {@link StringWrapper} for validation of path and query segments.</li>
+ * </ul>
+ *
+ * @author jonmv
+ */
+public class HttpURL {
+
+ private final Scheme scheme;
+ private final DomainName domain;
+ private final int port;
+ private final Path path;
+ private final Query query;
+
+ private HttpURL(Scheme scheme, DomainName domain, int port, Path path, Query query) {
+ this.scheme = requireNonNull(scheme);
+ this.domain = requireNonNull(domain);
+ this.port = requireInRange(port, "port number", -1, (1 << 16) - 1);
+ this.path = requireNonNull(path);
+ this.query = requireNonNull(query);
+ }
+
+ public static HttpURL create(Scheme scheme, DomainName domain, int port, Path path, Query query) {
+ return new HttpURL(scheme, domain, port, path, query);
+ }
+
+ public static HttpURL create(Scheme scheme, DomainName domain, int port, Path path) {
+ return create(scheme, domain, port, path, Query.empty());
+ }
+
+ public static HttpURL create(Scheme scheme, DomainName domain, int port) {
+ return create(scheme, domain, port, Path.empty(), Query.empty());
+ }
+
+ public static HttpURL create(Scheme scheme, DomainName domain) {
+ return create(scheme, domain, -1);
+ }
+
+ public static HttpURL from(URI uri) {
+ return from(uri, HttpURL::requirePathSegment, HttpURL::requireNothing);
+ }
+
+ public static HttpURL from(URI uri, Consumer<String> pathValidator, Consumer<String> queryValidator) {
+ if ( ! uri.normalize().equals(uri))
+ throw new IllegalArgumentException("uri should be normalized, but got: " + uri);
+
+ return create(Scheme.of(uri.getScheme()),
+ DomainName.of(requireNonNull(uri.getHost(), "URI must specify a host")),
+ uri.getPort(),
+ Path.parse(uri.getRawPath(), pathValidator),
+ Query.parse(uri.getRawQuery(), queryValidator));
+ }
+
+ public HttpURL withScheme(Scheme scheme) {
+ return create(scheme, domain, port, path, query);
+ }
+
+ public HttpURL withDomain(DomainName domain) {
+ return create(scheme, domain, port, path, query);
+ }
+
+ public HttpURL withPort(int port) {
+ return create(scheme, domain, port, path, query);
+ }
+
+ public HttpURL withoutPort() {
+ return create(scheme, domain, -1, path, query);
+ }
+
+ public HttpURL withPath(Path path) {
+ return create(scheme, domain, port, path, query);
+ }
+
+ public HttpURL withQuery(Query query) {
+ return create(scheme, domain, port, path, query);
+ }
+
+ public Scheme scheme() {
+ return scheme;
+ }
+
+ public DomainName domain() {
+ return domain;
+ }
+
+ public OptionalInt port() {
+ return port == -1 ? OptionalInt.empty() : OptionalInt.of(port);
+ }
+
+ public Path path() {
+ return path;
+ }
+
+ public Query query() {
+ return query;
+ }
+
+ /** Returns an absolute, hierarchical URI representing this HTTP URL. */
+ public URI asURI() {
+ try {
+ return new URI(scheme.name() + "://" + domain.value() + (port == -1 ? "" : ":" + port) + path.raw() + query.raw());
+ }
+ catch (URISyntaxException e) {
+ throw new IllegalStateException("invalid URI, this should not happen", e);
+ }
+ }
+
+ /** Require that the given string (possibly decoded multiple times) contains none of {@code '/', '?', '#'}, and isn't either of {@code "", ".", ".."}. */
+ public static String requirePathSegment(String value) {
+ while ( ! value.equals(value = decode(value, UTF_8)));
+ require( ! value.contains("/"), value, "path segment decoded cannot contain '/'");
+ require( ! value.contains("?"), value, "path segment decoded cannot contain '?'");
+ require( ! value.contains("#"), value, "path segment decoded cannot contain '#'");
+ return Path.requireNonNormalizable(value);
+ }
+
+ private static void requireNothing(String value) { }
+
+ public static class Path {
+
+ private final List<String> segments;
+ private final boolean trailingSlash;
+ private final UnaryOperator<String> validator;
+
+ private Path(List<String> segments, boolean trailingSlash, UnaryOperator<String> validator) {
+ this.segments = requireNonNull(segments);
+ this.trailingSlash = trailingSlash;
+ this.validator = requireNonNull(validator);
+ }
+
+ /** Creates a new, empty path, with a trailing slash, using {@link HttpURL#requirePathSegment} for segment validation. */
+ public static Path empty() {
+ return empty(HttpURL::requirePathSegment);
+ }
+
+ /** Creates a new, empty path, with a trailing slash, using the indicated validator for segments. */
+ public static Path empty(Consumer<String> validator) {
+ return new Path(List.of(), true, segmentValidator(validator));
+ }
+
+ /** Creates a new path with the given <em>decoded</em> segments. */
+ public static Path from(List<String> segments) {
+ return from(segments, __ -> { });
+ }
+
+ /** Creates a new path with the given <em>decoded</em> segments, and the validator applied to each segment. */
+ public static Path from(List<String> segments, Consumer<String> validator) {
+ return empty(validator).append(segments, true);
+ }
+
+ /** Parses the given raw, normalized path string; this ignores whether the path is absolute or relative. */
+ public static Path parse(String raw) {
+ return parse(raw, HttpURL::requirePathSegment);
+ }
+
+ /** Parses the given raw, normalized path string; this ignores whether the path is absolute or relative.) */
+ public static Path parse(String raw, Consumer<String> validator) {
+ Path base = new Path(List.of(), raw.endsWith("/"), segmentValidator(validator));
+ if (raw.startsWith("/")) raw = raw.substring(1);
+ if (raw.isEmpty()) return base;
+ List<String> segments = new ArrayList<>();
+ for (String segment : raw.split("/")) segments.add(decode(segment, UTF_8));
+ if (segments.isEmpty()) requireNonNormalizable(""); // Raw path was only slashes.
+ return base.append(segments);
+ }
+
+ private static UnaryOperator<String> segmentValidator(Consumer<String> validator) {
+ requireNonNull(validator, "segment validator cannot be null");
+ return value -> {
+ requireNonNormalizable(value);
+ validator.accept(value);
+ return value;
+ };
+ }
+
+ private static String requireNonNormalizable(String segment) {
+ return require( ! (segment.isEmpty() || segment.equals(".") || segment.equals("..")),
+ segment, "path segments cannot be \"\", \".\", or \"..\"");
+ }
+
+ /** Returns a copy of this where only the first segments are retained, and with a trailing slash. */
+ public Path head(int count) {
+ return count == segments.size() ? this : new Path(segments.subList(0, count), true, validator);
+ }
+
+ /** Returns a copy of this where only the last segments are retained. */
+ public Path tail(int count) {
+ return count == segments.size() ? this : new Path(segments.subList(segments.size() - count, segments.size()), trailingSlash, validator);
+ }
+
+ /** Returns a copy of this where the first segments are skipped. */
+ public Path skip(int count) {
+ return count == 0 ? this : new Path(segments.subList(count, segments.size()), trailingSlash, validator);
+ }
+
+ /** Returns a copy of this where the last segments are cut off, and with a trailing slash. */
+ public Path cut(int count) {
+ return count == 0 ? this : new Path(segments.subList(0, segments.size() - count), true, validator);
+ }
+
+ /** Returns a copy of this with the <em>decoded</em> segment appended at the end; it may not be either of {@code ""}, {@code "."} or {@code ".."}. */
+ public Path append(String segment) {
+ return append(List.of(segment), trailingSlash);
+ }
+
+ /** Returns a copy of this all segments of the other path appended, with a trailing slash as per the appendage. */
+ public Path append(Path other) {
+ return append(other.segments, other.trailingSlash);
+ }
+
+ /** Returns a copy of this all given segments appended, with a trailing slash as per this path. */
+ public Path append(List<String> segments) {
+ return append(segments, trailingSlash);
+ }
+
+ private Path append(List<String> segments, boolean trailingSlash) {
+ List<String> copy = new ArrayList<>(this.segments);
+ for (String segment : segments) copy.add(validator.apply(segment));
+ return new Path(copy, trailingSlash, validator);
+ }
+
+ /** Whether this path has a trailing slash. */
+ public boolean hasTrailingSlash() {
+ return trailingSlash;
+ }
+
+ /** Returns a copy of this which encodes a trailing slash. */
+ public Path withTrailingSlash() {
+ return new Path(segments, true, validator);
+ }
+
+ /** Returns a copy of this which does not encode a trailing slash. */
+ public Path withoutTrailingSlash() {
+ return new Path(segments, false, validator);
+ }
+
+ /** The <em>URL decoded</em> segments that make up this path; never {@code null}, {@code ""}, {@code "."} or {@code ".."}. */
+ public List<String> segments() {
+ return Collections.unmodifiableList(segments);
+ }
+
+ /** A raw path string which parses to this, by splitting on {@code "/"}, and then URL decoding. */
+ String raw() {
+ StringJoiner joiner = new StringJoiner("/", "/", trailingSlash ? "/" : "").setEmptyValue(trailingSlash ? "/" : "");
+ for (String segment : segments) joiner.add(encode(segment, UTF_8));
+ return joiner.toString();
+ }
+
+ /** Intentionally not usable for constructing new URIs. Use {@link HttpURL} for that instead. */
+ @Override
+ public String toString() {
+ return "path '" + raw() + "'";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Path path = (Path) o;
+ return trailingSlash == path.trailingSlash && segments.equals(path.segments);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(segments, trailingSlash);
+ }
+
+ }
+
+
+ public static class Query {
+
+ private final Map<String, String> values;
+ private final UnaryOperator<String> validator;
+
+ private Query(Map<String, String> values, UnaryOperator<String> validator) {
+ this.values = requireNonNull(values);
+ this.validator = requireNonNull(validator);
+ }
+
+ /** Creates a new, empty query part. */
+ public static Query empty() {
+ return empty(__ -> { });
+ }
+
+ /** Creates a new, empty query part, using the indicated string wrapper for keys and non-null values. */
+ public static Query empty(Consumer<String> validator) {
+ return new Query(Map.of(), entryValidator(validator));
+ }
+
+ /** Creates a new query part with the given <em>decoded</em> values. */
+ public static Query from(Map<String, String> values) {
+ return from(values, __ -> { });
+ }
+
+ /** Creates a new query part with the given <em>decoded</em> values, and the validator applied to each pair. */
+ public static Query from(Map<String, String> values, Consumer<String> validator) {
+ return empty(validator).merge(values);
+ }
+
+ /** Parses the given raw query string. */
+ public static Query parse(String raw) {
+ return parse(raw, __-> { });
+ }
+ /** Parses the given raw query string, using the indicated string wrapper to hold keys and non-null values. */
+ public static Query parse(String raw, Consumer<String> validator) {
+ if (raw == null) return empty(validator);
+ Map<String, String> values = new LinkedHashMap<>();
+ for (String pair : raw.split("&")) {
+ int split = pair.indexOf("=");
+ String key, value;
+ if (split == -1) { key = pair; value = null; }
+ else { key = pair.substring(0, split); value = pair.substring(split + 1); }
+ values.put(decode(key, UTF_8), value == null ? null : decode(value, UTF_8));
+ }
+ return empty(validator).merge(values);
+ }
+
+ private static UnaryOperator<String> entryValidator(Consumer<String> validator) {
+ requireNonNull(validator);
+ return value -> {
+ validator.accept(value);
+ return value;
+ };
+ }
+
+ /** Returns a copy of this with the <em>decoded</em> non-null key pointing to the <em>decoded</em> non-null value. */
+ public Query put(String key, String value) {
+ Map<String, String> copy = new LinkedHashMap<>(values);
+ copy.put(validator.apply(requireNonNull(key)), validator.apply(requireNonNull(value)));
+ return new Query(copy, validator);
+ }
+
+ /** Returns a copy of this with the <em>decoded</em> non-null key pointing to "nothing". */
+ public Query add(String key) {
+ Map<String, String> copy = new LinkedHashMap<>(values);
+ copy.put(validator.apply(requireNonNull(key)), null);
+ return new Query(copy, validator);
+ }
+
+ /** Returns a copy of this without any key-value pair with the <em>decoded</em> key. */
+ public Query remove(String key) {
+ Map<String, String> copy = new LinkedHashMap<>(values);
+ copy.remove(validator.apply(requireNonNull(key)));
+ return new Query(copy, validator);
+ }
+
+ /** Returns a copy of this with all mappings from the other query added to this, possibly overwriting existing mappings. */
+ public Query merge(Query other) {
+ return merge(other.values);
+ }
+
+ /** Returns a copy of this with all given mappings added to this, possibly overwriting existing mappings. */
+ public Query merge(Map<String, String> values) {
+ Map<String, String> copy = new LinkedHashMap<>(this.values);
+ values.forEach((key, value) -> copy.put(validator.apply(requireNonNull(key, "keys cannot be null")),
+ value == null ? null : validator.apply(value)));
+ return new Query(copy, validator);
+ }
+
+ /** The <em>URL decoded</em> key-value pairs that make up this query; keys and values may be {@code ""}, and values are {@code null} when only key was specified. */
+ public Map<String, String> entries() {
+ return unmodifiableMap(values);
+ }
+
+ /** A raw query string, with {@code '?'} prepended, that parses to this, by splitting on {@code "&"}, then on {@code "="}, and then URL decoding; or the empty string if this is empty. */
+ private String raw() {
+ StringJoiner joiner = new StringJoiner("&", "?", "").setEmptyValue("");
+ values.forEach((key, value) -> joiner.add(encode(key, UTF_8) +
+ (value == null ? "" : "=" + encode(value, UTF_8))));
+ return joiner.toString();
+ }
+
+ /** Intentionally not usable for constructing new URIs. Use {@link HttpURL} for that instead. */
+ @Override
+ public String toString() {
+ return "query '" + raw() + "'";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Query query = (Query) o;
+ return values.equals(query.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(values);
+ }
+
+ }
+
+
+ public enum Scheme {
+ http,
+ https;
+ public static Scheme of(String scheme) {
+ if (scheme.equalsIgnoreCase(http.name())) return http;
+ if (scheme.equalsIgnoreCase(https.name())) return https;
+ throw new IllegalArgumentException("scheme must be HTTP or HTTPS");
+ }
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/restapi/Path.java b/container-core/src/main/java/com/yahoo/restapi/Path.java
index b96488c6781..c639432db89 100644
--- a/container-core/src/main/java/com/yahoo/restapi/Path.java
+++ b/container-core/src/main/java/com/yahoo/restapi/Path.java
@@ -2,14 +2,10 @@
package com.yahoo.restapi;
import java.net.URI;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.Stream;
+import java.util.function.Consumer;
/**
* A normalized path which is able to match strings containing bracketed placeholders and return the
@@ -32,50 +28,47 @@ import java.util.stream.Stream;
public class Path {
// This path
- private final String pathString;
- private final String[] elements;
+ private final HttpURL.Path path;
// Info about the last match
private final Map<String, String> values = new HashMap<>();
- private String rest = "";
+ private HttpURL.Path rest;
+ /** Creates a new Path for matching the given URI against patterns, which uses {@link HttpURL#requirePathSegment} as a segment validator. */
public Path(URI uri) {
- this.pathString = requireNormalized(uri).getRawPath();
- this.elements = splitAbsolutePath(pathString, (part) -> URLDecoder.decode(part, StandardCharsets.UTF_8));
+ this.path = HttpURL.Path.parse(uri.getRawPath());
+ }
+
+ /** Creates a new Path for matching the given URI against patterns, with the given path segment validator. */
+ public Path(URI uri, Consumer<String> validator) {
+ this.path = HttpURL.Path.parse(uri.getRawPath(), validator);
}
private boolean matchesInner(String pathSpec) {
values.clear();
- String[] specElements = splitAbsolutePath(pathSpec, Function.identity());
+ List<String> specElements = HttpURL.Path.parse(pathSpec).segments();
boolean matchPrefix = false;
- if (specElements.length > 1 && specElements[specElements.length-1].equals("{*}")) {
+ if (specElements.size() > 1 && specElements.get(specElements.size() - 1).equals("{*}")) {
matchPrefix = true;
- specElements = Arrays.copyOf(specElements, specElements.length-1);
+ specElements = specElements.subList(0, specElements.size() - 1);
}
if (matchPrefix) {
- if (this.elements.length < specElements.length) return false;
+ if (path.segments().size() < specElements.size()) return false;
}
else { // match exact
- if (this.elements.length != specElements.length) return false;
+ if (path.segments().size() != specElements.size()) return false;
}
- for (int i = 0; i < specElements.length; i++) {
- if (specElements[i].startsWith("{") && specElements[i].endsWith("}")) // placeholder
- values.put(specElements[i].substring(1, specElements[i].length()-1), elements[i]);
- else if ( ! specElements[i].equals(this.elements[i]))
+ for (int i = 0; i < specElements.size(); i++) {
+ if (specElements.get(i).startsWith("{") && specElements.get(i).endsWith("}")) // placeholder
+ values.put(specElements.get(i).substring(1, specElements.get(i).length() - 1), path.segments().get(i));
+ else if ( ! specElements.get(i).equals(path.segments().get(i)))
return false;
}
-
- if (matchPrefix) {
- StringBuilder rest = new StringBuilder();
- for (int i = specElements.length; i < this.elements.length; i++)
- rest.append(elements[i]).append("/");
- if ( ! pathString.endsWith("/") && rest.length() > 0)
- rest.setLength(rest.length() - 1);
- this.rest = rest.toString();
- }
-
+
+ rest = matchPrefix ? path.skip(specElements.size()) : null;
+
return true;
}
@@ -104,34 +97,15 @@ public class Path {
}
/**
- * Returns the rest of the last matched path.
- * This is always the empty string (never null) unless the path spec ends with {*}
+ * Returns the rest of the last matched path, or {@code null} if the path spec didn't end with {*}.
*/
- public String getRest() { return rest; }
-
- public String asString() {
- return pathString;
+ public HttpURL.Path getRest() {
+ return rest;
}
@Override
public String toString() {
- return "path '" + String.join("/", elements) + "'";
- }
-
- private static URI requireNormalized(URI uri) {
- Objects.requireNonNull(uri);
- if (!uri.normalize().equals(uri)) throw new IllegalArgumentException("Expected normalized URI, got '" + uri + "'");
- return uri;
+ return path.toString();
}
- private static String[] splitAbsolutePath(String path, Function<String, String> partParser) {
- String[] parts = Stream.of(path.split("/"))
- .map(partParser)
- .toArray(String[]::new);
- for (var part : parts) {
- if (part.equals("..")) throw new IllegalArgumentException("Expected absolute path, got '" + path + "'");
- }
- return parts;
- }
-
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index d6cc0cccf3f..353ac3eb5cc 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -153,7 +153,9 @@ public interface RestApi {
default double getDoubleOrThrow(String name) { return Double.parseDouble(getStringOrThrow(name)); }
}
- interface PathParameters extends Parameters {}
+ interface PathParameters extends Parameters {
+ Optional<HttpURL.Path> getRest();
+ }
interface QueryParameters extends Parameters {
List<String> getStringList(String name);
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index 09de7ffa133..cc243a3e92b 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -429,16 +429,16 @@ class RestApiImpl implements RestApi {
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override
public Optional<String> getString(String name) {
- if (name.equals("*")) {
- String rest = pathMatcher.getRest();
- return rest.isEmpty() ? Optional.empty() : Optional.of(rest);
- }
return Optional.ofNullable(pathMatcher.get(name));
}
@Override public String getStringOrThrow(String name) {
return getString(name)
.orElseThrow(() -> new RestApiException.BadRequest("Path parameter '" + name + "' is missing"));
}
+ @Override public Optional<HttpURL.Path> getRest() {
+ return Optional.ofNullable(pathMatcher.getRest());
+ }
+
}
private class QueryParametersImpl implements RestApi.RequestContext.QueryParameters {
diff --git a/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java b/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java
new file mode 100644
index 00000000000..858513c2a69
--- /dev/null
+++ b/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java
@@ -0,0 +1,202 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.restapi;
+
+import ai.vespa.validation.Name;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL.Query;
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.function.Consumer;
+
+import static com.yahoo.net.DomainName.localhost;
+import static com.yahoo.restapi.HttpURL.Scheme.http;
+import static com.yahoo.restapi.HttpURL.Scheme.https;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * @author jonmv
+ */
+class HttpURLTest {
+
+ @Test
+ void testConversionBackAndForth() {
+ for (String uri : List.of("http://minimal",
+ "http://empty.query?",
+ "http://zero-port:0?no=path",
+ "http://only-path/",
+ "https://strange/queries?=&foo",
+ "https://weirdness?=foo",
+ "https://encoded/%3F%3D%26%2F?%3F%3D%26%2F=%3F%3D%26%2F",
+ "https://host.at.domain:123/one/two/?three=four&five")) {
+ Consumer<String> pathValidator = __ -> { };
+ assertEquals(uri, HttpURL.from(URI.create(uri), pathValidator, pathValidator).asURI().toString(),
+ "uri '" + uri + "' should be returned unchanged");
+ }
+ }
+
+ @Test
+ void testModification() {
+ HttpURL url = HttpURL.create(http, localhost).withPath(HttpURL.Path.empty(Name::of));
+ assertEquals(http, url.scheme());
+ assertEquals(localhost, url.domain());
+ assertEquals(OptionalInt.empty(), url.port());
+ assertEquals(HttpURL.Path.empty(Name::of), url.path());
+ assertEquals(HttpURL.Query.empty(Name::of), url.query());
+
+ url = url.withScheme(https)
+ .withDomain(DomainName.of("domain"))
+ .withPort(0)
+ .withPath(url.path().append("foo").withoutTrailingSlash())
+ .withQuery(url.query().put("boo", "bar").add("baz"));
+ assertEquals(https, url.scheme());
+ assertEquals(DomainName.of("domain"), url.domain());
+ assertEquals(OptionalInt.of(0), url.port());
+ assertEquals(HttpURL.Path.parse("/foo", Name::of), url.path());
+ assertEquals(HttpURL.Query.parse("boo=bar&baz", Name::of), url.query());
+ }
+
+ @Test
+ void testInvalidURIs() {
+ assertEquals("scheme must be HTTP or HTTPS",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("file:/txt"))).getMessage());
+
+ assertEquals("URI must specify a host",
+ assertThrows(NullPointerException.class,
+ () -> HttpURL.from(URI.create("http:///foo"))).getMessage());
+
+ assertEquals("port number must be at least '-1' and at most '65535', but got: '65536'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo:65536/bar"))).getMessage());
+
+ assertEquals("uri should be normalized, but got: http://foo//",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo//"))).getMessage());
+
+ assertEquals("uri should be normalized, but got: http://foo/./",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo/./"))).getMessage());
+
+ assertEquals("path segments cannot be \"\", \".\", or \"..\", but got: '..'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo/.."))).getMessage());
+
+ assertEquals("path segments cannot be \"\", \".\", or \"..\", but got: '..'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo/.%2E"))).getMessage());
+
+ assertEquals("name must match '[A-Za-z][A-Za-z0-9_-]{0,63}', but got: '/'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo/%2F"), Name::of, Name::of)).getMessage());
+
+ assertEquals("name must match '[A-Za-z][A-Za-z0-9_-]{0,63}', but got: '/'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo?%2F"), Name::of, Name::of)).getMessage());
+
+ assertEquals("name must match '[A-Za-z][A-Za-z0-9_-]{0,63}', but got: ''",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.from(URI.create("http://foo?"), Name::of, Name::of)).getMessage());
+ }
+
+ @Test
+ void testPath() {
+ HttpURL.Path path = HttpURL.Path.parse("foo/bar/baz", Name::of);
+ List<String> expected = List.of("foo", "bar", "baz");
+ assertEquals(expected, path.segments());
+
+ assertEquals(expected.subList(1, 3), path.skip(1).segments());
+ assertEquals(expected.subList(0, 2), path.cut(1).segments());
+ assertEquals(expected.subList(1, 2), path.skip(1).cut(1).segments());
+
+ assertEquals("path '/foo/bar/baz/'", path.withTrailingSlash().toString());
+ assertEquals(path, path.withoutTrailingSlash().withoutTrailingSlash());
+
+ assertEquals(List.of("one", "foo", "bar", "baz", "two"),
+ HttpURL.Path.from(List.of("one")).append(path).append("two").segments());
+
+ assertEquals(List.of(expected.get(2), expected.get(0)),
+ path.append(path).cut(2).skip(2).segments());
+
+ for (int i = 0; i < 3; i++) {
+ assertEquals(path.head(i), path.cut(3 - i));
+ assertEquals(path.tail(i), path.skip(3 - i));
+ }
+
+ assertThrows(NullPointerException.class,
+ () -> path.append((String) null));
+
+ List<String> names = new ArrayList<>();
+ names.add(null);
+ assertThrows(NullPointerException.class,
+ () -> path.append(names));
+
+ assertEquals("name must match '[A-Za-z][A-Za-z0-9_-]{0,63}', but got: '???'",
+ assertThrows(IllegalArgumentException.class,
+ () -> path.append("???")).getMessage());
+
+ assertEquals("fromIndex(2) > toIndex(1)",
+ assertThrows(IllegalArgumentException.class,
+ () -> path.cut(2).skip(2)).getMessage());
+
+ assertEquals("path segment decoded cannot contain '/', but got: '/'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.Path.empty().append("%2525252525252525%2525252525253%25252532%252525%252534%36")).getMessage());
+
+ assertEquals("path segment decoded cannot contain '?', but got: '?'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.Path.empty().append("?")).getMessage());
+
+ assertEquals("path segment decoded cannot contain '#', but got: '#'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.Path.empty().append("#")).getMessage());
+
+ assertEquals("path segments cannot be \"\", \".\", or \"..\", but got: '..'",
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpURL.Path.empty().append("%2E%25252E")).getMessage());
+ }
+
+ @Test
+ void testQuery() {
+ Query query = Query.parse("foo=bar&baz", Name::of);
+ Map<String, String> expected = new LinkedHashMap<>();
+ expected.put("foo", "bar");
+ expected.put("baz", null);
+ assertEquals(expected, query.entries());
+
+ expected.remove("baz");
+ assertEquals(expected, query.remove("baz").entries());
+
+ expected.put("baz", null);
+ expected.remove("foo");
+ assertEquals(expected, query.remove("foo").entries());
+ assertEquals(expected, Query.empty(Name::of).add("baz").entries());
+
+ assertEquals("query '?foo=bar&baz=bax&quu=fez&moo'",
+ query.put("baz", "bax").merge(Query.from(Map.of("quu", "fez"))).add("moo").toString());
+
+ assertThrows(NullPointerException.class,
+ () -> query.remove(null));
+
+ assertThrows(NullPointerException.class,
+ () -> query.add(null));
+
+ assertThrows(NullPointerException.class,
+ () -> query.put(null, "hax"));
+
+ assertThrows(NullPointerException.class,
+ () -> query.put("hax", null));
+
+ Map<String, String> names = new LinkedHashMap<>();
+ names.put(null, "hax");
+ assertThrows(NullPointerException.class,
+ () -> query.merge(names));
+ }
+
+}
diff --git a/container-core/src/test/java/com/yahoo/restapi/PathTest.java b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
index 5cbf80ff2ad..4786eb9775c 100644
--- a/container-core/src/test/java/com/yahoo/restapi/PathTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
@@ -6,6 +6,7 @@ import org.junit.Test;
import java.net.URI;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -34,7 +35,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("", path.getRest());
+ assertEquals("/", path.getRest().raw());
}
{
@@ -42,7 +43,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo", path.getRest());
+ assertEquals("/kanoo", path.getRest().raw());
}
{
@@ -50,7 +51,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo/trips", path.getRest());
+ assertEquals("/kanoo/trips", path.getRest().raw());
}
{
@@ -58,17 +59,19 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo/trips/", path.getRest());
+ assertEquals("/kanoo/trips/", path.getRest().raw());
}
}
@Test
public void testUrlEncodedPath() {
assertTrue(new Path(URI.create("/a/%62/c")).matches("/a/b/c"));
- assertFalse(new Path(URI.create("/a/b%2fc")).matches("/a/b/c"));
- assertFalse(new Path(URI.create("/foo")).matches("/foo/bar/%2e%2e"));
+ assertFalse(new Path(URI.create("/a/b%2fc"), __ -> { }).matches("/a/b/c"));
+ assertThrows("path segments cannot be \"\", \".\", or \"..\", but got: '..'",
+ IllegalArgumentException.class,
+ () -> new Path(URI.create("/foo")).matches("/foo/bar/%2e%2e"));
- Path path = new Path(URI.create("/%61/%2f/%63"));
+ Path path = new Path(URI.create("/%61/%2f/%63"), __ -> { });
assertTrue(path.matches("/a/{slash}/{c}"));
assertEquals("/", path.get("slash"));
assertEquals("c", path.get("c"));
diff --git a/container-dev/pom.xml b/container-dev/pom.xml
index 034081f4620..6268e1e6fb4 100644
--- a/container-dev/pom.xml
+++ b/container-dev/pom.xml
@@ -121,6 +121,10 @@
<groupId>io.airlift</groupId>
<artifactId>aircompressor</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ </exclusion>
</exclusions>
</dependency>
<dependency>
diff --git a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
index 46dcaf17abc..91a3181cb68 100644
--- a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
+++ b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
@@ -69,14 +69,18 @@ public final class SessionCache extends AbstractComponent {
@Inject
public SessionCache(NetworkMultiplexerProvider nets, ContainerMbusConfig containerMbusConfig,
DocumentTypeManager documentTypeManager,
- LoadTypeConfig loadTypeConfig, MessagebusConfig messagebusConfig,
+ MessagebusConfig messagebusConfig,
DocumentProtocolPoliciesConfig policiesConfig,
DistributionConfig distributionConfig) {
this(nets::net, containerMbusConfig, documentTypeManager,
- loadTypeConfig, messagebusConfig, policiesConfig, distributionConfig);
+ null/*TODO: Remove on Vespa 8*/, messagebusConfig, policiesConfig, distributionConfig);
}
+ /**
+ * @deprecated load types are deprecated. Use constructor without LoadTypeSet instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public SessionCache(Supplier<NetworkMultiplexer> net, ContainerMbusConfig containerMbusConfig,
DocumentTypeManager documentTypeManager,
LoadTypeConfig loadTypeConfig, MessagebusConfig messagebusConfig,
@@ -86,7 +90,6 @@ public final class SessionCache extends AbstractComponent {
containerMbusConfig,
messagebusConfig,
new DocumentProtocol(documentTypeManager,
- new LoadTypeSet(loadTypeConfig),
policiesConfig,
distributionConfig));
}
diff --git a/container-messagebus/src/test/java/com/yahoo/container/jdisc/messagebus/MbusClientProviderTest.java b/container-messagebus/src/test/java/com/yahoo/container/jdisc/messagebus/MbusClientProviderTest.java
index c509fb917fa..b9f33506894 100644
--- a/container-messagebus/src/test/java/com/yahoo/container/jdisc/messagebus/MbusClientProviderTest.java
+++ b/container-messagebus/src/test/java/com/yahoo/container/jdisc/messagebus/MbusClientProviderTest.java
@@ -36,6 +36,7 @@ public class MbusClientProviderTest {
testClient(new SessionConfig(builder));
}
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
private void testClient(SessionConfig config) {
SessionCache cache = new SessionCache(() -> NetworkMultiplexer.dedicated(new NullNetwork()),
new ContainerMbusConfig.Builder().build(),
diff --git a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
index b2b9dd47514..bbce02ed97a 100644
--- a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
+++ b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
@@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.util.logging.Level;
@@ -71,24 +72,23 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
if (path.matches("/querybuilder/")) {
return new FileResponse("_includes/index.html", null, null);
}
- if (!path.matches("/querybuilder/{*}") ) {
+ if ( ! path.matches("/querybuilder/{*}") ) {
return ErrorResponse.notFoundError("Nothing at path:" + path);
}
- String filepath = path.getRest();
- if (!isValidPath(filepath) && !filepath.equals("config.json")){
+ String filepath = String.join("/", path.getRest().segments());
+ if ( ! filepath.equals("config.json") && ! isValidPath(filepath)){
return ErrorResponse.notFoundError("Nothing at path:" + filepath);
}
return new FileResponse(filepath, indexModel, rankProfilesConfig);
}
private static boolean isValidPath(String path) {
- InputStream in = GUIHandler.class.getClassLoader().getResourceAsStream("gui/"+path);
- boolean isValid = (in != null);
- if(isValid){
- try { in.close(); } catch (IOException e) {/* Problem with closing inputstream */}
+ InputStream in = GUIHandler.class.getClassLoader().getResourceAsStream("gui/" + path);
+ if (in != null){
+ try { in.close(); } catch (IOException e) { throw new UncheckedIOException(e); }
+ return true;
}
-
- return isValid;
+ return false;
}
private static class FileResponse extends HttpResponse {
@@ -112,12 +112,12 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
String json = "{}";
try { json = getGUIConfig(); } catch (IOException e) { /*Something happened while parsing JSON */ }
is = new ByteArrayInputStream(json.getBytes());
- } else{
- is = GUIHandler.class.getClassLoader().getResourceAsStream("gui/"+this.path);
+ } else {
+ is = GUIHandler.class.getClassLoader().getResourceAsStream("gui/" + this.path);
}
byte[] buf = new byte[1024];
int numRead;
- while ( (numRead = is.read(buf) ) >= 0) {
+ while ((numRead = is.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
}
@@ -152,8 +152,6 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
return "image/x-icon";
} else if (path.endsWith(".json")) {
return "application/json";
- } else if (path.endsWith(".ttf")) {
- return "font/ttf";
}
return "text/html";
}
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
index 489214bebf8..71a93607b4b 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
@@ -90,6 +90,7 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
}
@Override
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public LoadTypeSet getLoadTypeSet() {
return ((MessageBusDocumentAccess) access.delegate()).getParams().getLoadTypes();
}
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
index b2e4821f164..9330e43eaf7 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
@@ -76,6 +76,12 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
public interface VisitorSessionFactory {
VisitorSession createVisitorSession(VisitorParameters params) throws ParseException;
+
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
LoadTypeSet getLoadTypeSet();
}
@@ -119,6 +125,7 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
return query.properties().getString(streamingSelection);
}
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
private void setVisitorParameters(String searchCluster, Route route, String documentType) {
params.setDocumentSelection(createSelectionString(documentType, createQuerySelectionString()));
params.setTimeoutMs(query.getTimeout()); // Per bucket visitor timeout
@@ -134,6 +141,7 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
params.visitInconsistentBuckets(true);
params.setPriority(DocumentProtocol.Priority.VERY_HIGH);
+ // TODO remove on Vespa 8
if (query.properties().getString(streamingLoadtype) != null) {
LoadType loadType = visitorSessionFactory.getLoadTypeSet().getNameMap().get(query.properties().getString(streamingLoadtype));
if (loadType != null) {
diff --git a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
index 1d07cafeda9..b1bc926daed 100644
--- a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
+++ b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
@@ -31,8 +31,9 @@ import static org.junit.Assert.*;
/**
* @author <a href="mailto:ulf@yahoo-inc.com">Ulf Carlin</a>
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class VdsVisitorTestCase {
- private LoadTypeSet loadTypeSet = new LoadTypeSet();
+ private LoadTypeSet loadTypeSet = new LoadTypeSet(); // TODO remove on Vespa 8
public VdsVisitorTestCase() {
loadTypeSet.addLoadType(1, "low", DocumentProtocol.Priority.LOW_1);
@@ -489,7 +490,7 @@ public class VdsVisitorTestCase {
private static class MockVisitorSessionFactory implements VdsVisitor.VisitorSessionFactory {
private VisitorParameters params;
- private LoadTypeSet loadTypeSet;
+ private LoadTypeSet loadTypeSet; // TODO remove on Vespa 8
private boolean timeoutQuery = false;
private boolean failQuery = false;
@@ -504,6 +505,7 @@ public class VdsVisitorTestCase {
}
@Override
+ // TODO: Remove on Vespa 8
public LoadTypeSet getLoadTypeSet() {
return loadTypeSet;
}
diff --git a/container-test/pom.xml b/container-test/pom.xml
index 7c739faad26..d4ea39fa3c9 100644
--- a/container-test/pom.xml
+++ b/container-test/pom.xml
@@ -92,5 +92,10 @@
<artifactId>aircompressor</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
index 1788154b9e2..01488711f59 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.billing;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -187,6 +188,7 @@ public class Bill {
private BigDecimal cpuCost;
private BigDecimal memoryCost;
private BigDecimal diskCost;
+ private NodeResources.Architecture architecture;
public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt) {
this.id = id;
@@ -198,7 +200,7 @@ public class Bill {
}
public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt, ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId,
- BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost) {
+ BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture) {
this(id, description, amount, plan, agent, addedAt);
this.startedAt = startedAt;
this.endedAt = endedAt;
@@ -214,6 +216,7 @@ public class Bill {
this.cpuCost = cpuCost;
this.memoryCost = memoryCost;
this.diskCost = diskCost;
+ this.architecture = architecture;
}
/** The opaque ID of this */
@@ -290,6 +293,10 @@ public class Bill {
return Optional.ofNullable(diskCost);
}
+ public Optional<NodeResources.Architecture> getArchitecture() {
+ return Optional.ofNullable(architecture);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
index 61f8844482c..43312f9332c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
@@ -109,9 +109,6 @@ public interface BillingController {
/** Get all bills from the system */
List<Bill> getBills();
- /** Delete billing contact information from the tenant */
- void deleteBillingInfo(TenantName tenant, Set<User> users, boolean isPrivileged);
-
/** Get the bill collection method for the given tenant */
default CollectionMethod getCollectionMethod(TenantName tenant) {
return CollectionMethod.NONE;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
index f4d3577aeec..295c993f409 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
@@ -162,9 +162,6 @@ public class MockBillingController implements BillingController {
}
@Override
- public void deleteBillingInfo(TenantName tenant, Set<User> users, boolean isPrivileged) {}
-
- @Override
public CollectionMethod getCollectionMethod(TenantName tenant) {
return collectionMethod.getOrDefault(tenant, CollectionMethod.AUTO);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
index 5fb4d853e67..4757fa76224 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
@@ -117,7 +117,8 @@ public class PlanRegistryMock implements PlanRegistry {
usage.getDiskMillis().divide(millisPerHour, RoundingMode.HALF_UP),
cpuCost,
memCost,
- dgbCost
+ dgbCost,
+ usage.getArchitecture()
);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
index b9b7881745a..42368fa358d 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.hosted.controller.api.integration.configserver;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeploymentData;
@@ -52,9 +54,9 @@ public interface ConfigServer {
ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, String environment, String region);
- Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, String restPath);
+ Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, Path restPath);
- String getServiceStatusPage(DeploymentId deployment, String serviceName, String node, String subPath);
+ String getServiceStatusPage(DeploymentId deployment, String serviceName, DomainName node, Path subPath);
/**
* Gets the Vespa logs of the given deployment.
@@ -73,7 +75,7 @@ public interface ConfigServer {
* @param path path within package to get
* @param requestUri request URI on the controller, used to rewrite paths in response from config server
*/
- ProxyResponse getApplicationPackageContent(DeploymentId deployment, String path, URI requestUri);
+ ProxyResponse getApplicationPackageContent(DeploymentId deployment, Path path, URI requestUri);
List<ClusterMetrics> getDeploymentMetrics(DeploymentId deployment);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java
index fe7beb538da..68ebc5e86aa 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java
@@ -20,6 +20,6 @@ public class NodeRepoStats {
public Load load() { return load; }
public Load activeLoad() { return activeLoad; }
- public List<ApplicationStats> applicationStats() { return applicationStats; }
+ public List<ApplicationStats> applicationStats() { return applicationStats; }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MailerException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MailerException.java
new file mode 100644
index 00000000000..0febc296fc8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MailerException.java
@@ -0,0 +1,14 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.organization;
+
+/**
+ * MailerException wrap all possible Mailer implementation exceptions
+ *
+ * @author enygaard
+ */
+public class MailerException extends RuntimeException {
+
+ public MailerException(Throwable ex) {
+ super(ex);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
index 3bc9580307b..c756100e563 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.resource;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.zone.ZoneId;
import java.math.BigDecimal;
@@ -19,11 +20,12 @@ public class CostInfo {
private final BigDecimal cpuCost;
private final BigDecimal memoryCost;
private final BigDecimal diskCost;
+ private final NodeResources.Architecture architecture;
public CostInfo(ApplicationId applicationId, ZoneId zoneId,
BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours,
- BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost) {
+ BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture) {
this.applicationId = applicationId;
this.zoneId = zoneId;
this.cpuHours = cpuHours;
@@ -32,6 +34,7 @@ public class CostInfo {
this.cpuCost = cpuCost;
this.memoryCost = memoryCost;
this.diskCost = diskCost;
+ this.architecture = architecture;
}
public ApplicationId getApplicationId() {
@@ -70,4 +73,8 @@ public class CostInfo {
return cpuCost.add(memoryCost).add(diskCost);
}
+ public NodeResources.Architecture getArchitecture() {
+ return architecture;
+ }
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringClient.java
index 767ba4aa34b..944a5eaf696 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringClient.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringClient.java
@@ -17,8 +17,6 @@ public interface MeteringClient {
void consume(Collection<ResourceSnapshot> resources);
- MeteringData getMeteringData(TenantName tenantName, ApplicationName applicationName);
-
List<ResourceSnapshot> getSnapshotHistoryForTenant(TenantName tenantName, YearMonth yearMonth);
void refresh();
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceAllocation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceAllocation.java
index 742e3a01171..8191540e898 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceAllocation.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceAllocation.java
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.resource;
+import com.yahoo.config.provision.NodeResources;
+
import java.util.Objects;
/**
@@ -10,16 +12,18 @@ import java.util.Objects;
*/
public class ResourceAllocation {
- public static final ResourceAllocation ZERO = new ResourceAllocation(0, 0, 0);
+ public static final ResourceAllocation ZERO = new ResourceAllocation(0, 0, 0, NodeResources.Architecture.getDefault());
private final double cpuCores;
private final double memoryGb;
private final double diskGb;
+ private final NodeResources.Architecture architecture;
- public ResourceAllocation(double cpuCores, double memoryGb, double diskGb) {
+ public ResourceAllocation(double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture) {
this.cpuCores = cpuCores;
this.memoryGb = memoryGb;
this.diskGb = diskGb;
+ this.architecture = architecture;
}
public double usageFraction(ResourceAllocation total) {
@@ -38,14 +42,18 @@ public class ResourceAllocation {
return diskGb;
}
+ public NodeResources.Architecture getArchitecture() {
+ return architecture;
+ }
+
/** Returns a copy of this with the given allocation added */
public ResourceAllocation plus(ResourceAllocation allocation) {
- return new ResourceAllocation(cpuCores + allocation.cpuCores, memoryGb + allocation.memoryGb, diskGb + allocation.diskGb);
+ return new ResourceAllocation(cpuCores + allocation.cpuCores, memoryGb + allocation.memoryGb, diskGb + allocation.diskGb, architecture);
}
/** Returns a copy of this with each resource multiplied by given factor */
public ResourceAllocation multiply(double multiplicand) {
- return new ResourceAllocation(cpuCores * multiplicand, memoryGb * multiplicand, diskGb * multiplicand);
+ return new ResourceAllocation(cpuCores * multiplicand, memoryGb * multiplicand, diskGb * multiplicand, architecture);
}
@Override
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java
index 2f277193231..8ae12c0e7ac 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java
@@ -18,8 +18,6 @@ public interface ResourceDatabaseClient {
void writeResourceSnapshots(Collection<ResourceSnapshot> snapshots);
- List<ResourceSnapshot> getResourceSnapshotsForMonth(TenantName tenantName, ApplicationName applicationName, YearMonth month);
-
List<ResourceUsage> getResourceSnapshotsForPeriod(TenantName tenantName, long startTimestamp, long endTimestamp);
void refreshMaterializedView();
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
index 5a4d250ea9d..c680990e240 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
@@ -45,19 +45,6 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient {
}
@Override
- public List<ResourceSnapshot> getResourceSnapshotsForMonth(TenantName tenantName, ApplicationName applicationName, YearMonth month) {
- return resourceSnapshots.stream()
- .filter(resourceSnapshot -> {
- LocalDate snapshotDate = LocalDate.ofInstant(resourceSnapshot.getTimestamp(), ZoneId.of("UTC"));
- return YearMonth.from(snapshotDate).equals(month) &&
- snapshotDate.getYear() == month.getYear() &&
- resourceSnapshot.getApplicationId().tenant().equals(tenantName) &&
- resourceSnapshot.getApplicationId().application().equals(applicationName);
- })
- .collect(Collectors.toList());
- }
-
- @Override
public Set<YearMonth> getMonthsWithSnapshotsForTenant(TenantName tenantName) {
return Collections.emptySet();
}
@@ -88,6 +75,7 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient {
a.getApplicationId(),
a.getZoneId(),
plan,
+ a.getArchitecture(),
BigDecimal.valueOf(a.getCpuCores()).multiply(d),
BigDecimal.valueOf(a.getMemoryGb()).multiply(d),
BigDecimal.valueOf(a.getDiskGb()).multiply(d)
@@ -100,10 +88,12 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient {
assert a.getApplicationId().equals(b.getApplicationId());
assert a.getZoneId().equals(b.getZoneId());
assert a.getPlan().equals(b.getPlan());
+ assert a.getArchitecture().equals(b.getArchitecture());
return new ResourceUsage(
a.getApplicationId(),
a.getZoneId(),
a.getPlan(),
+ a.getArchitecture(),
a.getCpuMillis().add(b.getCpuMillis()),
a.getMemoryMillis().add(b.getMemoryMillis()),
a.getDiskMillis().add(b.getDiskMillis())
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java
index 9b3de004bae..85ee23f4df0 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java
@@ -24,15 +24,15 @@ public class ResourceSnapshot {
private final Instant timestamp;
private final ZoneId zoneId;
- public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, Instant timestamp, ZoneId zoneId) {
+ public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId) {
this.applicationId = applicationId;
- this.resourceAllocation = new ResourceAllocation(cpuCores, memoryGb, diskGb);
+ this.resourceAllocation = new ResourceAllocation(cpuCores, memoryGb, diskGb, architecture);
this.timestamp = timestamp;
this.zoneId = zoneId;
}
- public static ResourceSnapshot from(ApplicationId applicationId, int nodes, double cpuCores, double memoryGb, double diskGb, Instant timestamp, ZoneId zoneId) {
- return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, timestamp, zoneId);
+ public static ResourceSnapshot from(ApplicationId applicationId, int nodes, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId) {
+ return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, architecture, timestamp, zoneId);
}
public static ResourceSnapshot from(List<Node> nodes, Instant timestamp, ZoneId zoneId) {
@@ -48,6 +48,7 @@ public class ResourceSnapshot {
nodes.stream().map(Node::resources).mapToDouble(NodeResources::vcpu).sum(),
nodes.stream().map(Node::resources).mapToDouble(NodeResources::memoryGb).sum(),
nodes.stream().map(Node::resources).mapToDouble(NodeResources::diskGb).sum(),
+ nodes.stream().map(node -> node.resources().architecture()).findFirst().orElse(NodeResources.Architecture.getDefault()),
timestamp,
zoneId
);
@@ -81,6 +82,10 @@ public class ResourceSnapshot {
return zoneId;
}
+ public NodeResources.Architecture getArchitecture() {
+ return resourceAllocation.getArchitecture();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
index fdea3b26372..850ca040d03 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.resource;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan;
@@ -15,8 +16,9 @@ public class ResourceUsage {
private final BigDecimal cpuMillis;
private final BigDecimal memoryMillis;
private final BigDecimal diskMillis;
+ private final NodeResources.Architecture architecture;
- public ResourceUsage(ApplicationId applicationId, ZoneId zoneId, Plan plan,
+ public ResourceUsage(ApplicationId applicationId, ZoneId zoneId, Plan plan, NodeResources.Architecture architecture,
BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis) {
this.applicationId = applicationId;
this.zoneId = zoneId;
@@ -24,6 +26,7 @@ public class ResourceUsage {
this.memoryMillis = memoryMillis;
this.diskMillis = diskMillis;
this.plan = plan;
+ this.architecture = architecture;
}
public ApplicationId getApplicationId() {
@@ -50,4 +53,7 @@ public class ResourceUsage {
return plan;
}
+ public NodeResources.Architecture getArchitecture() {
+ return architecture;
+ }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java
index 6050f238da7..cb2b76d845c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java
@@ -12,9 +12,25 @@ import java.util.Map;
public class MockMailer implements Mailer {
public final Map<String, List<Mail>> mails = new HashMap<>();
+ public final boolean blackhole;
+
+ public MockMailer() {
+ this(false);
+ }
+
+ MockMailer(boolean blackhole) {
+ this.blackhole = blackhole;
+ }
+
+ public static MockMailer blackhole() {
+ return new MockMailer(true);
+ }
@Override
public void send(Mail mail) {
+ if (blackhole) {
+ return;
+ }
for (String recipient : mail.recipients()) {
mails.putIfAbsent(recipient, new ArrayList<>());
mails.get(recipient).add(mail);
@@ -33,7 +49,10 @@ public class MockMailer implements Mailer {
/** Returns the list of mails sent to the given recipient. Modifications affect the set of mails stored in this. */
public List<Mail> inbox(String recipient) {
- return mails.get(recipient);
+ return mails.getOrDefault(recipient, List.of());
}
+ public void reset() {
+ mails.clear();
+ }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMeteringClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMeteringClient.java
index 003ecb32a32..ca094f98607 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMeteringClient.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMeteringClient.java
@@ -1,17 +1,14 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.stubs;
-import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringData;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringClient;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -32,14 +29,6 @@ public class MockMeteringClient implements MeteringClient {
}
@Override
- public MeteringData getMeteringData(TenantName tenantName, ApplicationName applicationName) {
- return meteringData.orElseGet(() -> {
- ResourceAllocation emptyAllocation = new ResourceAllocation(0, 0, 0);
- return new MeteringData(emptyAllocation, emptyAllocation, emptyAllocation, Collections.emptyMap());
- });
- }
-
- @Override
public List<ResourceSnapshot> getSnapshotHistoryForTenant(TenantName tenantName, YearMonth yearMonth) {
return new ArrayList<>(resources);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
index cc667175316..98c64a2a11e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
@@ -228,8 +228,8 @@ enum PathGroup {
hostedAccountant("/billing/v1/invoice/{*}",
"/billing/v1/billing"),
- /** Path used for listing endpoint certificate request info */
- endpointCertificateRequestInfo("/certificateRequests/"),
+ /** Path used for listing endpoint certificate request and re-requesting endpoint certificates */
+ endpointCertificates("/endpointcertificates/"),
/** Path used for secret store management */
secretStore(Matcher.tenant, "/application/v4/tenant/{tenant}/secret-store/{*}"),
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
index afecbf9d2e3..4d342e3b1ee 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
@@ -197,9 +197,9 @@ enum Policy {
.on(PathGroup.hostedAccountant, PathGroup.accountant)
.in(SystemName.PublicCd, SystemName.Public)),
- /** Listing endpoint certificate request info */
- endpointCertificateRequestInfo(Privilege.grant(Action.read)
- .on(PathGroup.endpointCertificateRequestInfo)
+ /** Listing endpoint certificates and re-requesting certificates */
+ endpointCertificateApi(Privilege.grant(Action.all())
+ .on(PathGroup.endpointCertificates)
.in(SystemName.all())),
/** Secret store operations */
diff --git a/controller-server/pom.xml b/controller-server/pom.xml
index fa4a0dc06d6..ca8951124ef 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -138,7 +138,7 @@
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
- <version>1.3.3</version>
+ <version>1.4</version>
</dependency>
<dependency>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index 6afeab9b4e6..c35e8c5a7ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -23,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.config.ControllerConfig;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.dns.NameServiceForwarder;
import com.yahoo.vespa.hosted.controller.notification.NotificationsDb;
+import com.yahoo.vespa.hosted.controller.notify.Notifier;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.JobControlFlags;
import com.yahoo.vespa.hosted.controller.security.AccessControl;
@@ -88,6 +89,7 @@ public class Controller extends AbstractComponent {
private final CuratorArchiveBucketDb archiveBucketDb;
private final NotificationsDb notificationsDb;
private final SupportAccessControl supportAccessControl;
+ private final Notifier notifier;
/**
* Creates a controller
@@ -126,6 +128,7 @@ public class Controller extends AbstractComponent {
auditLogger = new AuditLogger(curator, clock);
jobControl = new JobControl(new JobControlFlags(curator, flagSource));
archiveBucketDb = new CuratorArchiveBucketDb(this);
+ notifier = new Notifier(curator, serviceRegistry.mailer());
notificationsDb = new NotificationsDb(this);
supportAccessControl = new SupportAccessControl(this);
@@ -330,4 +333,7 @@ public class Controller extends AbstractComponent {
return supportAccessControl;
}
+ public Notifier notifier() {
+ return notifier;
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index 59877fce634..384a5d0f1ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -171,7 +171,15 @@ public class TenantController {
throw new IllegalArgumentException("Could not delete tenant '" + tenant.value()
+ "': This tenant has active applications");
- credentials.ifPresent(creds -> accessControl.deleteTenant(tenant, creds));
+ if (oldTenant.type() == Tenant.Type.athenz) {
+ credentials.ifPresent(creds -> accessControl.deleteTenant(tenant, creds));
+ } else if (oldTenant.type() == Tenant.Type.cloud) {
+ accessControl.deleteTenant(tenant, null);
+ } else {
+ throw new IllegalArgumentException("Could not delete tenant '" + tenant.value()
+ + ": This tenant is of unhandled type " + oldTenant.type());
+ }
+
controller.notificationsDb().removeNotifications(NotificationSource.from(tenant));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
index 5c7ae5041fe..258884a4d11 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
@@ -4,6 +4,9 @@ package com.yahoo.vespa.hosted.controller.application.pkg;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
import com.yahoo.component.Version;
+import com.yahoo.compress.ArchiveStreamReader;
+import com.yahoo.compress.ArchiveStreamReader.ArchiveFile;
+import com.yahoo.compress.ArchiveStreamReader.Options;
import com.yahoo.config.application.FileSystemWrapper;
import com.yahoo.config.application.FileSystemWrapper.FileWrapper;
import com.yahoo.config.application.XmlPreProcessor;
@@ -24,6 +27,7 @@ import com.yahoo.yolean.Exceptions;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -40,6 +44,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -108,7 +113,7 @@ public class ApplicationPackage {
this.trustedCertificates = files.get(trustedCertificatesFile).map(bytes -> X509CertificateUtils.certificateListFromPem(new String(bytes, UTF_8))).orElse(List.of());
- this.bundleHash = calculateBundleHash();
+ this.bundleHash = calculateBundleHash(zippedContent);
preProcessAndPopulateCache();
}
@@ -120,7 +125,7 @@ public class ApplicationPackage {
byte[] certificatesBytes = X509CertificateUtils.toPem(trustedCertificates).getBytes(UTF_8);
ByteArrayOutputStream modified = new ByteArrayOutputStream(zippedContent.length + certificatesBytes.length);
- ZipStreamReader.transferAndWrite(modified, new ByteArrayInputStream(zippedContent), trustedCertificatesFile, certificatesBytes);
+ ZipEntries.transferAndWrite(modified, new ByteArrayInputStream(zippedContent), trustedCertificatesFile, certificatesBytes);
return new ApplicationPackage(modified.toByteArray());
}
@@ -227,15 +232,23 @@ public class ApplicationPackage {
}
// Hashes all files and settings that require a deployment to be forwarded to configservers
- private String calculateBundleHash() {
+ private String calculateBundleHash(byte[] zippedContent) {
Predicate<String> entryMatcher = name -> ! name.endsWith(deploymentFile) && ! name.endsWith(buildMetaFile);
- SortedMap<String, Long> entryCRCs = ZipStreamReader.getEntryCRCs(new ByteArrayInputStream(zippedContent), entryMatcher);
- Funnel<SortedMap<String, Long>> funnel = (from, into) -> from.entrySet().forEach(entry -> {
- into.putBytes(entry.getKey().getBytes());
- into.putLong(entry.getValue());
+ SortedMap<String, Long> crcByEntry = new TreeMap<>();
+ Options options = Options.standard().pathPredicate(entryMatcher);
+ ArchiveFile file;
+ try (ArchiveStreamReader reader = ArchiveStreamReader.ofZip(new ByteArrayInputStream(zippedContent), options)) {
+ OutputStream discard = OutputStream.nullOutputStream();
+ while ((file = reader.readNextTo(discard)) != null) {
+ crcByEntry.put(file.path().toString(), file.crc32().orElse(-1));
+ }
+ }
+ Funnel<SortedMap<String, Long>> funnel = (from, into) -> from.forEach((key, value) -> {
+ into.putBytes(key.getBytes());
+ into.putLong(value);
});
return Hashing.sha1().newHasher()
- .putObject(entryCRCs, funnel)
+ .putObject(crcByEntry, funnel)
.putInt(deploymentSpec.deployableHashCode())
.hash().toString();
}
@@ -285,13 +298,13 @@ public class ApplicationPackage {
}
private Map<Path, Optional<byte[]>> read(Collection<String> names) {
- var entries = new ZipStreamReader(new ByteArrayInputStream(zip),
- name -> names.contains(withoutLegacyDir(name)),
- maxSize,
- true)
- .entries().stream()
- .collect(toMap(entry -> Paths.get(withoutLegacyDir(entry.zipEntry().getName())).normalize(),
- ZipStreamReader.ZipEntryWithContent::content));
+ var entries = ZipEntries.from(zip,
+ name -> names.contains(withoutLegacyDir(name)),
+ maxSize,
+ true)
+ .asList().stream()
+ .collect(toMap(entry -> Paths.get(withoutLegacyDir(entry.name())).normalize(),
+ ZipEntries.ZipEntryWithContent::content));
names.stream().map(Paths::get).forEach(path -> entries.putIfAbsent(path.normalize(), Optional.empty()));
return entries;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
index 97810b9de80..e18f6247cb1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
@@ -15,7 +15,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static com.yahoo.vespa.hosted.controller.application.pkg.ZipStreamReader.ZipEntryWithContent;
+import static com.yahoo.vespa.hosted.controller.application.pkg.ZipEntries.ZipEntryWithContent;
/**
* @author freva
@@ -75,8 +75,8 @@ public class ApplicationPackageDiff {
}
private static Map<String, ZipEntryWithContent> readContents(ApplicationPackage app, int maxFileSizeToDiff) {
- return new ZipStreamReader(new ByteArrayInputStream(app.zippedContent()), entry -> true, maxFileSizeToDiff, false).entries().stream()
- .collect(Collectors.toMap(entry -> entry.zipEntry().getName(), e -> e));
+ return ZipEntries.from(app.zippedContent(), entry -> true, maxFileSizeToDiff, false).asList().stream()
+ .collect(Collectors.toMap(ZipEntryWithContent::name, e -> e));
}
private static List<String> lines(byte[] data) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java
new file mode 100644
index 00000000000..a6cb7f23fc3
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java
@@ -0,0 +1,99 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.application.pkg;
+
+import com.yahoo.compress.ArchiveStreamReader;
+import com.yahoo.compress.ArchiveStreamReader.ArchiveFile;
+import com.yahoo.compress.ArchiveStreamReader.Options;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * A list of entries read from a ZIP archive, and their contents.
+ *
+ * @author bratseth
+ */
+public class ZipEntries {
+
+ private final List<ZipEntryWithContent> entries;
+
+ private ZipEntries(List<ZipEntryWithContent> entries) {
+ this.entries = List.copyOf(Objects.requireNonNull(entries));
+ }
+
+ /** Copies the zipped content from in to out, adding/overwriting an entry with the given name and content. */
+ public static void transferAndWrite(OutputStream out, InputStream in, String name, byte[] content) {
+ try (ZipOutputStream zipOut = new ZipOutputStream(out);
+ ZipInputStream zipIn = new ZipInputStream(in)) {
+ for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) {
+ if (entry.getName().equals(name))
+ continue;
+
+ zipOut.putNextEntry(new ZipEntry(entry.getName()));
+ zipIn.transferTo(zipOut);
+ zipOut.closeEntry();
+ }
+ zipOut.putNextEntry(new ZipEntry(name));
+ zipOut.write(content);
+ zipOut.closeEntry();
+ }
+ catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ /** Read ZIP entries from inputStream */
+ public static ZipEntries from(byte[] zip, Predicate<String> entryNameMatcher, int maxEntrySizeInBytes, boolean throwIfEntryExceedsMaxSize) {
+ Options options = Options.standard()
+ .pathPredicate(entryNameMatcher)
+ .maxSize(2 * (long) Math.pow(1024, 3)) // 2 GB
+ .maxEntrySize(maxEntrySizeInBytes)
+ .maxEntries(1024)
+ .truncateEntry(!throwIfEntryExceedsMaxSize);
+ List<ZipEntryWithContent> entries = new ArrayList<>();
+ try (ArchiveStreamReader reader = ArchiveStreamReader.ofZip(new ByteArrayInputStream(zip), options)) {
+ ArchiveFile file;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ while ((file = reader.readNextTo(baos)) != null) {
+ entries.add(new ZipEntryWithContent(file.path().toString(),
+ Optional.of(baos.toByteArray()).filter(b -> b.length > 0),
+ file.size()));
+ baos.reset();
+ }
+ }
+ return new ZipEntries(entries);
+ }
+
+ public List<ZipEntryWithContent> asList() { return entries; }
+
+ public static class ZipEntryWithContent {
+
+ private final String name;
+ private final Optional<byte[]> content;
+ private final long size;
+
+ public ZipEntryWithContent(String name, Optional<byte[]> content, long size) {
+ this.name = name;
+ this.content = content;
+ this.size = size;
+ }
+
+ public String name() { return name; }
+ public byte[] contentOrThrow() { return content.orElseThrow(); }
+ public Optional<byte[]> content() { return content; }
+ public long size() { return size; }
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReader.java
deleted file mode 100644
index 174ac4cb8b0..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReader.java
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.application.pkg;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.function.Predicate;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-/**
- * @author bratseth
- */
-public class ZipStreamReader {
-
- private final List<ZipEntryWithContent> entries = new ArrayList<>();
- private final int maxEntrySizeInBytes;
-
- public ZipStreamReader(InputStream input, Predicate<String> entryNameMatcher, int maxEntrySizeInBytes, boolean throwIfEntryExceedsMaxSize) {
- this.maxEntrySizeInBytes = maxEntrySizeInBytes;
- try (ZipInputStream zipInput = new ZipInputStream(input)) {
- ZipEntry zipEntry;
-
- while (null != (zipEntry = zipInput.getNextEntry())) {
- if (!entryNameMatcher.test(requireName(zipEntry.getName()))) continue;
- entries.add(readContent(zipEntry, zipInput, throwIfEntryExceedsMaxSize));
- }
- } catch (IOException e) {
- throw new UncheckedIOException("IO error reading zip content", e);
- }
- }
-
- /** Copies the zipped content from in to out, adding/overwriting an entry with the given name and content. */
- public static void transferAndWrite(OutputStream out, InputStream in, String name, byte[] content) {
- try (ZipOutputStream zipOut = new ZipOutputStream(out);
- ZipInputStream zipIn = new ZipInputStream(in)) {
- for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) {
- if (entry.getName().equals(name))
- continue;
-
- zipOut.putNextEntry(new ZipEntry(entry.getName()));
- zipIn.transferTo(zipOut);
- zipOut.closeEntry();
- }
- zipOut.putNextEntry(new ZipEntry(name));
- zipOut.write(content);
- zipOut.closeEntry();
- }
- catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- public static SortedMap<String, Long> getEntryCRCs(InputStream in, Predicate<String> entryNameMatcher) {
- SortedMap<String, Long> entryCRCs = new TreeMap<>();
- byte[] buffer = new byte[2048];
- try (ZipInputStream zipIn = new ZipInputStream(in)) {
- for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) {
- if ( ! entryNameMatcher.test(entry.getName()))
- continue;
- // CRC is not set until entry is read
- while ( -1 != zipIn.read(buffer)){}
- entryCRCs.put(entry.getName(), entry.getCrc());
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- return entryCRCs;
- }
-
- private ZipEntryWithContent readContent(ZipEntry zipEntry, ZipInputStream zipInput, boolean throwIfEntryExceedsMaxSize) {
- try (ByteArrayOutputStream bis = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[2048];
- int read;
- long size = 0;
- while ( -1 != (read = zipInput.read(buffer))) {
- size += read;
- if (size > maxEntrySizeInBytes) {
- if (throwIfEntryExceedsMaxSize) throw new IllegalArgumentException(
- "Entry in zip content exceeded size limit of " + maxEntrySizeInBytes + " bytes");
- } else bis.write(buffer, 0, read);
- }
-
- boolean hasContent = size <= maxEntrySizeInBytes;
- return new ZipEntryWithContent(zipEntry,
- Optional.of(bis).filter(__ -> hasContent).map(ByteArrayOutputStream::toByteArray),
- size);
- } catch (IOException e) {
- throw new UncheckedIOException("Failed reading from zipped content", e);
- }
- }
-
- public List<ZipEntryWithContent> entries() { return Collections.unmodifiableList(entries); }
-
- private static String requireName(String name) {
- if (List.of(name.split("/")).contains("..") ||
- !trimTrailingSlash(name).equals(Path.of(name).normalize().toString())) {
- throw new IllegalArgumentException("Unexpected non-normalized path found in zip content: '" + name + "'");
- }
- return name;
- }
-
- private static String trimTrailingSlash(String name) {
- if (name.endsWith("/")) return name.substring(0, name.length() - 1);
- return name;
- }
-
- public static class ZipEntryWithContent {
-
- private final ZipEntry zipEntry;
- private final Optional<byte[]> content;
- private final long size;
-
- public ZipEntryWithContent(ZipEntry zipEntry, Optional<byte[]> content, long size) {
- this.zipEntry = zipEntry;
- this.content = content;
- this.size = size;
- }
-
- public ZipEntry zipEntry() { return zipEntry; }
- public byte[] contentOrThrow() { return content.orElseThrow(); }
- public Optional<byte[]> content() { return content; }
- public long size() { return size; }
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
index 5e19b014083..996b53cc6f5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
/**
* Looks up stored endpoint certificate metadata, provisions new certificates if none is found,
- * re-provisions if zone is not covered, and uses refreshed certificates if a newer version is available.
+ * and re-provisions the certificate if the deploying-to zone is not covered.
*
* See also {@link com.yahoo.vespa.hosted.controller.maintenance.EndpointCertificateMaintainer}, which handles
* refreshes, deletions and triggers deployments.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesHandler.java
new file mode 100644
index 00000000000..dc59f513509
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesHandler.java
@@ -0,0 +1,77 @@
+package com.yahoo.vespa.hosted.controller.certificate;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.restapi.RestApiException;
+import com.yahoo.restapi.StringResponse;
+import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateRequestMetadata;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import com.yahoo.vespa.hosted.controller.persistence.EndpointCertificateMetadataSerializer;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static com.yahoo.jdisc.http.HttpRequest.Method.POST;
+
+/**
+ * List all certificate requests for a system, with their requested DNS names.
+ * Used for debugging, and verifying basic functionality of Cameo client in CD.
+ *
+ * @author andreer
+ */
+
+public class EndpointCertificatesHandler extends ThreadedHttpRequestHandler {
+
+ private final EndpointCertificateProvider endpointCertificateProvider;
+ private final CuratorDb curator;
+
+ public EndpointCertificatesHandler(Executor executor, ServiceRegistry serviceRegistry, CuratorDb curator) {
+ super(executor);
+ this.endpointCertificateProvider = serviceRegistry.endpointCertificateProvider();
+ this.curator = curator;
+ }
+
+ public HttpResponse handle(HttpRequest request) {
+ if (request.getMethod().equals(GET)) return listEndpointCertificates();
+ if (request.getMethod().equals(POST)) return reRequestEndpointCertificateFor(request.getProperty("application"));
+ throw new RestApiException.MethodNotAllowed(request);
+ }
+
+ public HttpResponse listEndpointCertificates() {
+ List<EndpointCertificateRequestMetadata> endpointCertificateMetadata = endpointCertificateProvider.listCertificates();
+
+ String requestsWithNames = endpointCertificateMetadata.stream()
+ .map(metadata -> metadata.requestId() + " : " +
+ String.join(", ", metadata.dnsNames().stream()
+ .map(dnsNameStatus -> dnsNameStatus.dnsName)
+ .collect(Collectors.joining(", "))))
+ .collect(Collectors.joining("\n"));
+
+ return new StringResponse(requestsWithNames);
+ }
+
+ public StringResponse reRequestEndpointCertificateFor(String instanceId) {
+ ApplicationId applicationId = ApplicationId.fromFullString(instanceId);
+
+ try (var lock = curator.lock(TenantAndApplicationId.from(applicationId))) {
+ EndpointCertificateMetadata endpointCertificateMetadata = curator.readEndpointCertificateMetadata(applicationId)
+ .orElseThrow(() -> new RestApiException.NotFound("No certificate found for application " + applicationId.serializedForm()));
+
+ EndpointCertificateMetadata reRequestedMetadata = endpointCertificateProvider.requestCaSignedCertificate(
+ applicationId, endpointCertificateMetadata.requestedDnsSans(), Optional.of(endpointCertificateMetadata));
+
+ curator.writeEndpointCertificateMetadata(applicationId, reRequestedMetadata);
+
+ return new StringResponse(EndpointCertificateMetadataSerializer.toSlime(reRequestedMetadata).toString());
+ }
+ }
+} \ No newline at end of file
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
index c9310349b9b..7ede040773e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
@@ -7,17 +7,24 @@ import com.yahoo.vespa.flags.ListFlag;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
+import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Duration;
+import java.time.Instant;
import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* Expires unused tenants from Vespa Cloud.
*
+ * TODO: Should support sending notifications some time before the various expiry events happen.
+ *
* @author ogronnesby
*/
public class CloudTrialExpirer extends ControllerMaintainer {
@@ -33,39 +40,56 @@ public class CloudTrialExpirer extends ControllerMaintainer {
@Override
protected double maintain() {
- var expiredTenants = controller().tenants().asList().stream()
- .filter(this::tenantIsCloudTenant) // only valid for cloud tenants
- .filter(this::tenantHasTrialPlan) // only valid to expire actual trial tenants
- .filter(this::tenantIsNotExemptFromExpiry) // feature flag might exempt tenant from expiry
- .filter(this::tenantReadersNotLoggedIn) // no user logged in last 14 days
- .filter(this::tenantHasNoDeployments) // no running deployments active
- .collect(Collectors.toList());
+ if (controller().system().equals(SystemName.PublicCd)) {
+ tombstoneNonePlanTenants();
+ }
+ moveInactiveTenantsToNonePlan();
+ return 1.0;
+ }
- if (! expiredTenants.isEmpty()) {
- var expiredTenantNames = expiredTenants.stream()
- .map(Tenant::name)
- .map(TenantName::value)
- .collect(Collectors.joining(", "));
+ private void moveInactiveTenantsToNonePlan() {
+ var predicate = tenantReadersNotLoggedIn(loginExpiry)
+ .and(this::tenantHasTrialPlan)
+ .and(this::tenantHasNoDeployments);
- log.info("Moving expired tenants to 'none' plan: " + expiredTenantNames);
- }
+ forTenant("'none' plan", predicate, this::setPlanNone);
+ }
+
+ private void tombstoneNonePlanTenants() {
+ // tombstone tenants that are inactive 14 days after being set as 'none'
+ var expiry = loginExpiry.plus(loginExpiry);
+ var predicate = tenantReadersNotLoggedIn(expiry).and(this::tenantHasNonePlan);
+ forTenant("tombstoned", predicate, this::tombstoneTenants);
+ }
- expireTenants(expiredTenants);
+ private void forTenant(String name, Predicate<Tenant> p, Consumer<List<Tenant>> c) {
+ var predicate = ((Predicate<Tenant>) this::tenantIsCloudTenant)
+ .and(this::tenantIsNotExemptFromExpiry);
+
+ var tenants = controller().tenants().asList().stream()
+ .filter(predicate.and(p))
+ .collect(Collectors.toList());
- return 1;
+ if (! tenants.isEmpty()) {
+ var tenantNames = tenants.stream().map(Tenant::name).map(TenantName::value).collect(Collectors.joining(", "));
+ log.info("Setting tenants as " + name + ": " + tenantNames);
+ }
+
+ c.accept(tenants);
}
private boolean tenantIsCloudTenant(Tenant tenant) {
return tenant.type() == Tenant.Type.cloud;
}
- private boolean tenantReadersNotLoggedIn(Tenant tenant) {
- return tenant.lastLoginInfo().get(LastLoginInfo.UserLevel.user)
- .map(instant -> {
- var sinceLastLogin = Duration.between(instant, controller().clock().instant());
- return sinceLastLogin.compareTo(loginExpiry) > 0;
- })
- .orElse(false);
+ private Predicate<Tenant> tenantReadersNotLoggedIn(Duration duration) {
+ // returns true if no user has logged in to the tenant after (now - duration)
+ return (Tenant tenant) -> {
+ var timeLimit = controller().clock().instant().minus(duration);
+ return tenant.lastLoginInfo().get(LastLoginInfo.UserLevel.user)
+ .map(instant -> instant.isBefore(timeLimit))
+ .orElse(false);
+ };
}
private boolean tenantHasTrialPlan(Tenant tenant) {
@@ -73,6 +97,11 @@ public class CloudTrialExpirer extends ControllerMaintainer {
return "trial".equals(planId.value());
}
+ private boolean tenantHasNonePlan(Tenant tenant) {
+ var planId = controller().serviceRegistry().billingController().getPlan(tenant.name());
+ return "none".equals(planId.value());
+ }
+
private boolean tenantIsNotExemptFromExpiry(Tenant tenant) {
return ! extendedTrialTenants.value().contains(tenant.name().value());
}
@@ -84,9 +113,15 @@ public class CloudTrialExpirer extends ControllerMaintainer {
.sum() == 0;
}
- private void expireTenants(List<Tenant> tenants) {
+ private void setPlanNone(List<Tenant> tenants) {
tenants.forEach(tenant -> {
controller().serviceRegistry().billingController().setPlan(tenant.name(), PlanId.from("none"), false);
});
}
+
+ private void tombstoneTenants(List<Tenant> tenants) {
+ tenants.forEach(tenant -> {
+ controller().tenants().delete(tenant.name(), Optional.empty(), false);
+ });
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
index b996901c5d0..15f8d6380c0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
@@ -34,7 +34,7 @@ import java.util.stream.Collectors;
/**
* Updates refreshed endpoint certificates and triggers redeployment, and deletes unused certificates.
* <p>
- * See also EndpointCertificateManager, which provisions, reprovisions and validates certificates on deploy
+ * See also class EndpointCertificates, which provisions, reprovisions and validates certificates on deploy
*
* @author andreer
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index acaf35133d7..d4905f7e20a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -33,7 +33,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
+import java.util.function.Function;
import java.util.logging.Level;
+import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
@@ -150,14 +152,14 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
.filter(this::unlessNodeOwnerIsSystemApplication)
.filter(this::isNodeStateMeterable)
.filter(this::isClusterTypeMeterable)
+ // Grouping by ApplicationId -> Architecture -> ResourceSnapshot
.collect(Collectors.groupingBy(node ->
- node.owner().get(),
- Collectors.collectingAndThen(Collectors.toList(),
- nodeList -> ResourceSnapshot.from(
- nodeList,
- clock.instant(),
- zoneId))
- )).values();
+ node.owner().get(),
+ groupSnapshotsByArchitecture(zoneId)))
+ .values()
+ .stream()
+ .flatMap(list -> list.values().stream())
+ .collect(Collectors.toList());
}
private boolean unlessNodeOwnerIsSystemApplication(Node node) {
@@ -182,7 +184,7 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
public static double cost(ClusterResources clusterResources, SystemName systemName) {
NodeResources nr = clusterResources.nodeResources();
- return cost(new ResourceAllocation(nr.vcpu(), nr.memoryGb(), nr.diskGb()).multiply(clusterResources.nodes()), systemName);
+ return cost(new ResourceAllocation(nr.vcpu(), nr.memoryGb(), nr.diskGb(), nr.architecture()).multiply(clusterResources.nodes()), systemName);
}
private static double cost(ResourceAllocation allocation, SystemName systemName) {
@@ -201,7 +203,7 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
metric.createContext(Collections.emptyMap()));
resourceSnapshots.forEach(snapshot -> {
- var context = getMetricContext(snapshot.getApplicationId(), snapshot.getZoneId());
+ var context = getMetricContext(snapshot);
metric.set("metering.vcpu", snapshot.getCpuCores(), context);
metric.set("metering.memoryGB", snapshot.getMemoryGb(), context);
metric.set("metering.diskGB", snapshot.getDiskGb(), context);
@@ -222,4 +224,29 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
"zoneId", zoneId.value()
));
}
+
+ private Metric.Context getMetricContext(ResourceSnapshot snapshot) {
+ return metric.createContext(Map.of(
+ "tenant", snapshot.getApplicationId().tenant().value(),
+ "applicationId", snapshot.getApplicationId().toFullString(),
+ "zoneId", snapshot.getZoneId().value(),
+ "architecture", snapshot.getArchitecture()
+ ));
+ }
+
+ private Collector<Node, ?, Map<NodeResources.Architecture, ResourceSnapshot>> groupSnapshotsByArchitecture(ZoneId zoneId) {
+ return Collectors.collectingAndThen(
+ Collectors.groupingBy(node -> node.resources().architecture()),
+ convertNodeListToResourceSnapshot(zoneId)
+ );
+ }
+
+ private Function<Map<NodeResources.Architecture, List<Node>>, Map<NodeResources.Architecture, ResourceSnapshot>> convertNodeListToResourceSnapshot(ZoneId zoneId) {
+ return nodeMap -> nodeMap.entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> ResourceSnapshot.from(entry.getValue(), clock.instant(), zoneId))
+ );
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
index 089767dc586..b36b2b9cad8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
@@ -57,7 +57,7 @@ public class CostCalculator {
Property property = propertyByTenantName.get(node.owner().get().tenant());
if (property == null) continue;
var allocation = allocationByProperty.getOrDefault(property, ResourceAllocation.ZERO);
- var nodeAllocation = new ResourceAllocation(node.resources().vcpu(), node.resources().memoryGb(), node.resources().diskGb());
+ var nodeAllocation = new ResourceAllocation(node.resources().vcpu(), node.resources().memoryGb(), node.resources().diskGb(), node.resources().architecture());
allocationByProperty.put(property, allocation.plus(nodeAllocation));
totalAllocation = totalAllocation.plus(nodeAllocation);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
index c0bd1ac03ff..aa62028749b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
@@ -9,6 +9,7 @@ import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.notify.Notifier;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.time.Clock;
@@ -32,14 +33,16 @@ public class NotificationsDb {
private final Clock clock;
private final CuratorDb curatorDb;
+ private final Notifier notifier;
public NotificationsDb(Controller controller) {
- this(controller.clock(), controller.curator());
+ this(controller.clock(), controller.curator(), controller.notifier());
}
- NotificationsDb(Clock clock, CuratorDb curatorDb) {
+ NotificationsDb(Clock clock, CuratorDb curatorDb, Notifier notifier) {
this.clock = clock;
this.curatorDb = curatorDb;
+ this.notifier = notifier;
}
public List<TenantName> listTenantsWithNotifications() {
@@ -61,13 +64,20 @@ public class NotificationsDb {
* already exists, it'll be replaced by this one instead
*/
public void setNotification(NotificationSource source, Type type, Level level, List<String> messages) {
+ Optional<Notification> changed = Optional.empty();
try (Lock lock = curatorDb.lockNotifications(source.tenant())) {
- List<Notification> notifications = curatorDb.readNotifications(source.tenant()).stream()
+ var existingNotifications = curatorDb.readNotifications(source.tenant());
+ List<Notification> notifications = existingNotifications.stream()
.filter(notification -> !source.equals(notification.source()) || type != notification.type())
.collect(Collectors.toCollection(ArrayList::new));
- notifications.add(new Notification(clock.instant(), type, level, source, messages));
+ var notification = new Notification(clock.instant(), type, level, source, messages);
+ if (!notificationExists(notification, existingNotifications, false)) {
+ changed = Optional.of(notification);
+ }
+ notifications.add(notification);
curatorDb.writeNotifications(source.tenant(), notifications);
}
+ changed.ifPresent(notifier::dispatch);
}
/** Remove the notification with the given source and type */
@@ -109,6 +119,7 @@ public class NotificationsDb {
*/
public void setDeploymentMetricsNotifications(DeploymentId deploymentId, List<ClusterMetrics> clusterMetrics) {
Instant now = clock.instant();
+ List<Notification> changed = List.of();
List<Notification> newNotifications = clusterMetrics.stream()
.flatMap(metric -> {
NotificationSource source = NotificationSource.from(deploymentId, ClusterSpec.Id.from(metric.getClusterId()));
@@ -130,10 +141,20 @@ public class NotificationsDb {
// ... and add the new notifications for this deployment
newNotifications.stream())
.collect(Collectors.toUnmodifiableList());
-
- if (!initial.equals(updated))
+ if (!initial.equals(updated)) {
curatorDb.writeNotifications(deploymentSource.tenant(), updated);
+ }
+ changed = newNotifications.stream().filter(n -> !notificationExists(n, initial, true)).collect(Collectors.toList());
}
+ notifier.dispatch(changed, deploymentSource);
+ }
+
+ private boolean notificationExists(Notification notification, List<Notification> existing, boolean mindHigherLevel) {
+ // Be conservative for now, only dispatch notifications if they are from new source or with new type.
+ // the message content and level is ignored for now
+ return existing.stream().anyMatch(e ->
+ notification.source().contains(e.source()) && notification.type().equals(e.type()) &&
+ (!mindHigherLevel || notification.level().ordinal() <= e.level().ordinal()));
}
private static Optional<Notification> createFeedBlockNotification(NotificationSource source, Instant at, ClusterMetrics metric) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java
new file mode 100644
index 00000000000..e186541c85c
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java
@@ -0,0 +1,85 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.notify;
+
+import com.yahoo.text.Text;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.MailerException;
+import com.yahoo.vespa.hosted.controller.notification.Notification;
+import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
+import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Notifier is responsible for dispatching user notifications to their chosen Contact points.
+ *
+ * @author enygaard
+ */
+public class Notifier {
+ private final CuratorDb curatorDb;
+ private final Mailer mailer;
+
+ private static final Logger log = Logger.getLogger(Notifier.class.getName());
+
+ public Notifier(CuratorDb curatorDb, Mailer mailer) {
+ this.curatorDb = Objects.requireNonNull(curatorDb);
+ this.mailer = Objects.requireNonNull(mailer);
+ }
+
+ public void dispatch(List<Notification> notifications, NotificationSource source) {
+ if (notifications.isEmpty()) {
+ return;
+ }
+ var tenant = curatorDb.readTenant(source.tenant());
+ tenant.stream().forEach(t -> {
+ if (t instanceof CloudTenant) {
+ var ct = (CloudTenant) t;
+ ct.info().contacts().all().stream()
+ .filter(c -> c.audiences().contains(TenantContacts.Audience.NOTIFICATIONS))
+ .collect(Collectors.groupingBy(TenantContacts.Contact::type, Collectors.toList()))
+ .entrySet()
+ .forEach(e -> notifications.forEach(n -> dispatch(n, e.getKey(), e.getValue())));
+ }
+ });
+ }
+
+ public void dispatch(Notification notification) {
+ dispatch(List.of(notification), notification.source());
+ }
+
+ private void dispatch(Notification notification, TenantContacts.Type type, Collection<? extends TenantContacts.Contact> contacts) {
+ switch (type) {
+ case EMAIL:
+ dispatch(notification, contacts.stream().map(c -> (TenantContacts.EmailContact) c).collect(Collectors.toList()));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown TenantContacts type " + type.name());
+ }
+ }
+
+ private void dispatch(Notification notification, Collection<TenantContacts.EmailContact> contacts) {
+ try {
+ mailer.send(mailOf(notification, contacts.stream().map(c -> c.email()).collect(Collectors.toList())));
+ } catch (MailerException e) {
+ log.log(Level.SEVERE, "Failed sending email", e);
+ }
+ }
+
+ private Mail mailOf(Notification n, Collection<String> recipients) {
+ var subject = Text.format("[%s] Vespa Notification for %s", n.level().toString().toUpperCase(), n.type().name());
+ var body = new StringBuilder();
+ body.append("Source: ").append(n.source().toString()).append("\n")
+ .append("\n")
+ .append(String.join("\n", n.messages()));
+ return new Mail(recipients, subject.toString(), body.toString());
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
index 2a1b8a19475..7e1c9c8884f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
@@ -3,6 +3,9 @@ package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
import com.yahoo.text.Text;
import java.io.InputStream;
import java.net.URI;
@@ -22,28 +25,27 @@ import static com.yahoo.jdisc.http.HttpRequest.Method;
public class ProxyRequest {
private final Method method;
- private final URI requestUri;
+ private final HttpURL requestUri;
private final Map<String, List<String>> headers;
private final InputStream requestData;
private final List<URI> targets;
- private final String targetPath;
+ private final Path targetPath;
- ProxyRequest(Method method, URI url, Map<String, List<String>> headers, InputStream body, List<URI> targets,
- String path) {
- Objects.requireNonNull(url);
- if (!url.getPath().endsWith(path)) {
- throw new IllegalArgumentException(Text.format("Request path '%s' does not end with proxy path '%s'", url.getPath(), path));
+ ProxyRequest(Method method, URI uri, Map<String, List<String>> headers, InputStream body, List<URI> targets, Path path) {
+ this.requestUri = HttpURL.from(uri);
+ if ( requestUri.path().segments().size() < path.segments().size()
+ || ! requestUri.path().tail(path.segments().size()).equals(path)) {
+ throw new IllegalArgumentException(Text.format("Request %s does not end with proxy %s", requestUri.path(), path));
}
if (targets.isEmpty()) {
throw new IllegalArgumentException("targets must be non-empty");
}
this.method = Objects.requireNonNull(method);
- this.requestUri = Objects.requireNonNull(url);
this.headers = Objects.requireNonNull(headers);
this.requestData = body;
this.targets = List.copyOf(targets);
- this.targetPath = path.startsWith("/") ? path : "/" + path;
+ this.targetPath = path;
}
@@ -64,24 +66,12 @@ public class ProxyRequest {
}
public URI createConfigServerRequestUri(URI baseURI) {
- try {
- return new URI(baseURI.getScheme(), baseURI.getUserInfo(), baseURI.getHost(),
- baseURI.getPort(), targetPath, requestUri.getQuery(), requestUri.getFragment());
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ return HttpURL.from(baseURI).withPath(targetPath).withQuery(requestUri.query()).asURI();
}
public URI getControllerPrefixUri() {
- String prefixPath = targetPath.equals("/") && !requestUri.getPath().endsWith("/") ?
- requestUri.getPath() + targetPath :
- requestUri.getPath().substring(0, requestUri.getPath().length() - targetPath.length() + 1);
- try {
- return new URI(requestUri.getScheme(), requestUri.getUserInfo(), requestUri.getHost(),
- requestUri.getPort(), prefixPath, null, null);
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ Path prefixPath = requestUri.path().cut(targetPath.segments().size()).withTrailingSlash();
+ return requestUri.withPath(prefixPath).withQuery(Query.empty()).asURI();
}
@Override
@@ -90,7 +80,7 @@ public class ProxyRequest {
}
/** Create a proxy request that repeatedly tries a single target */
- public static ProxyRequest tryOne(URI target, String path, HttpRequest request) {
+ public static ProxyRequest tryOne(URI target, Path path, HttpRequest request) {
return new ProxyRequest(request.getMethod(), request.getUri(), request.getJDiscRequest().headers(),
request.getData(), List.of(target), path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
index 0b629a577d0..9ac30898f8b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
import org.apache.http.client.utils.URIBuilder;
import java.io.IOException;
@@ -29,19 +31,8 @@ public class ProxyResponse extends HttpResponse {
super(statusResponse);
this.contentType = contentType;
- final String configServerPrefix;
- final String controllerRequestPrefix;
- try {
- configServerPrefix = new URIBuilder()
- .setScheme(configServer.getScheme())
- .setHost(configServer.getHost())
- .setPort(configServer.getPort())
- .setPath("/")
- .build().toString();
- controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString();
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ String configServerPrefix = HttpURL.from(configServer).withPath(Path.empty()).asURI().toString();
+ String controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString();
bodyResponseRewritten = bodyResponse.replace(configServerPrefix, controllerRequestPrefix);
}
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 d9a38a5b578..0f5322af176 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
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.Signatures;
+import ai.vespa.validation.Validation;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
@@ -25,8 +26,10 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.io.IOUtils;
+import com.yahoo.net.DomainName;
import com.yahoo.restapi.ByteArrayResponse;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
@@ -155,6 +158,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static ai.vespa.validation.Validation.requireAtLeast;
import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
import static com.yahoo.jdisc.Response.Status.CONFLICT;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -255,7 +259,6 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/diff/{number}")) return applicationPackageDiff(path.get("tenant"), path.get("application"), path.get("number"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), "default", request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), "default", request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/metering")) return metering(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance")) return applications(path.get("tenant"), Optional.of(path.get("application")), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
@@ -270,7 +273,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/reindexing")) return getReindexing(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/state/v1/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{host}/status/{*}")) return status(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.get("host"), path.getRest(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/content/{*}")) return content(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.getRest(), request);
@@ -284,7 +288,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/state/v1/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/{host}/status/{*}")) return status(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.get("host"), path.getRest(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap());
@@ -1762,68 +1767,6 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse metering(String tenant, String application, HttpRequest request) {
-
- Slime slime = new Slime();
- Cursor root = slime.setObject();
-
- MeteringData meteringData = controller.serviceRegistry()
- .meteringService()
- .getMeteringData(TenantName.from(tenant), ApplicationName.from(application));
-
- ResourceAllocation currentSnapshot = meteringData.getCurrentSnapshot();
- Cursor currentRate = root.setObject("currentrate");
- currentRate.setDouble("cpu", currentSnapshot.getCpuCores());
- currentRate.setDouble("mem", currentSnapshot.getMemoryGb());
- currentRate.setDouble("disk", currentSnapshot.getDiskGb());
-
- ResourceAllocation thisMonth = meteringData.getThisMonth();
- Cursor thismonth = root.setObject("thismonth");
- thismonth.setDouble("cpu", thisMonth.getCpuCores());
- thismonth.setDouble("mem", thisMonth.getMemoryGb());
- thismonth.setDouble("disk", thisMonth.getDiskGb());
-
- ResourceAllocation lastMonth = meteringData.getLastMonth();
- Cursor lastmonth = root.setObject("lastmonth");
- lastmonth.setDouble("cpu", lastMonth.getCpuCores());
- lastmonth.setDouble("mem", lastMonth.getMemoryGb());
- lastmonth.setDouble("disk", lastMonth.getDiskGb());
-
-
- Map<ApplicationId, List<ResourceSnapshot>> history = meteringData.getSnapshotHistory();
- Cursor details = root.setObject("details");
-
- Cursor detailsCpu = details.setObject("cpu");
- Cursor detailsMem = details.setObject("mem");
- Cursor detailsDisk = details.setObject("disk");
-
- history.forEach((applicationId, resources) -> {
- String instanceName = applicationId.instance().value();
- Cursor detailsCpuApp = detailsCpu.setObject(instanceName);
- Cursor detailsMemApp = detailsMem.setObject(instanceName);
- Cursor detailsDiskApp = detailsDisk.setObject(instanceName);
- Cursor detailsCpuData = detailsCpuApp.setArray("data");
- Cursor detailsMemData = detailsMemApp.setArray("data");
- Cursor detailsDiskData = detailsDiskApp.setArray("data");
-
- resources.forEach(resourceSnapshot -> {
- Cursor cpu = detailsCpuData.addObject();
- cpu.setLong("unixms", resourceSnapshot.getTimestamp().toEpochMilli());
- cpu.setDouble("value", resourceSnapshot.getCpuCores());
-
- Cursor mem = detailsMemData.addObject();
- mem.setLong("unixms", resourceSnapshot.getTimestamp().toEpochMilli());
- mem.setDouble("value", resourceSnapshot.getMemoryGb());
-
- Cursor disk = detailsDiskData.addObject();
- disk.setLong("unixms", resourceSnapshot.getTimestamp().toEpochMilli());
- disk.setDouble("value", resourceSnapshot.getDiskGb());
- });
- });
-
- return new SlimeJsonResponse(slime);
- }
-
private HttpResponse deploying(String tenantName, String applicationName, String instanceName, HttpRequest request) {
Instance instance = controller.applications().requireInstance(ApplicationId.from(tenantName, applicationName, instanceName));
Slime slime = new Slime();
@@ -1857,40 +1800,29 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return response;
}
- private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath, HttpRequest request) {
+ private HttpResponse status(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String host, HttpURL.Path restPath, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
+ String result = controller.serviceRegistry().configServer().getServiceStatusPage(deploymentId,
+ serviceName,
+ DomainName.of(host),
+ restPath); // TODO: add query
+ return new HtmlResponse(result);
+ }
- if (restPath.contains("/status/")) {
- String[] parts = restPath.split("/status/", 2);
- String result = controller.serviceRegistry().configServer().getServiceStatusPage(deploymentId, serviceName, parts[0], parts[1]);
- return new HtmlResponse(result);
- }
-
- String normalizedRestPath = URI.create(restPath).normalize().toString();
- // Only state/v1 is allowed
- if (! normalizedRestPath.startsWith("state/v1/")) {
- return ErrorResponse.forbidden("Access denied");
- }
-
+ private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, HttpURL.Path restPath, HttpRequest request) {
+ DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
Map<?,?> result = controller.serviceRegistry().configServer().getServiceApiResponse(deploymentId, serviceName, restPath);
ServiceApiResponse response = new ServiceApiResponse(deploymentId.zoneId(),
deploymentId.applicationId(),
List.of(controller.zoneRegistry().getConfigServerVipUri(deploymentId.zoneId())),
request.getUri());
- response.setResponse(result, serviceName, restPath);
+ response.setResponse(result, serviceName, HttpURL.Path.parse("/state/v1").append(restPath));
return response;
}
- private HttpResponse content(String tenantName, String applicationName, String instanceName, String environment, String region, String restPath, HttpRequest request) {
+ private HttpResponse content(String tenantName, String applicationName, String instanceName, String environment, String region, HttpURL.Path restPath, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
-
- String normalizedRestPath = URI.create("content/" + restPath).normalize().toString();
- // Only content/ is allowed
- if ( ! normalizedRestPath.startsWith("content/")) {
- return ErrorResponse.forbidden("Access denied");
- }
-
- return controller.serviceRegistry().configServer().getApplicationPackageContent(deploymentId, "/" + restPath, request.getUri());
+ return controller.serviceRegistry().configServer().getApplicationPackageContent(deploymentId, restPath, request.getUri());
}
private HttpResponse updateTenant(String tenantName, HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
index 8d03ca74500..a9e24943c0d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
@@ -5,6 +5,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
import org.apache.commons.fileupload.MultipartStream;
import org.apache.commons.fileupload.ParameterParser;
+import org.apache.commons.fileupload.util.Streams;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -21,6 +22,16 @@ import java.util.Map;
*/
public class MultipartParser {
+ private final long maxDataLength;
+
+ public MultipartParser() {
+ this(2 * (long) Math.pow(1024, 3)); // 2 GB
+ }
+
+ MultipartParser(long maxDataLength) {
+ this.maxDataLength = maxDataLength;
+ }
+
/**
* Parses the given multi-part request and returns all the parts indexed by their name.
*
@@ -37,10 +48,13 @@ public class MultipartParser {
*/
public Map<String, byte[]> parse(String contentTypeHeader, InputStream data, URI uri) {
try {
+ LimitedOutputStream output = new LimitedOutputStream(maxDataLength);
ParameterParser parameterParser = new ParameterParser();
Map<String, String> contentType = parameterParser.parse(contentTypeHeader, ';');
- if (contentType.containsKey("application/zip"))
- return Map.of(EnvironmentResource.APPLICATION_ZIP, data.readAllBytes());
+ if (contentType.containsKey("application/zip")) {
+ Streams.copy(data, output, false);
+ return Map.of(EnvironmentResource.APPLICATION_ZIP, output.toByteArray());
+ }
if ( ! contentType.containsKey("multipart/form-data"))
throw new IllegalArgumentException("Expected a multipart or application/zip message, but got Content-Type: " + contentTypeHeader);
String boundary = contentType.get("boundary");
@@ -55,17 +69,17 @@ public class MultipartParser {
if (contentDispositionContent == null)
throw new IllegalArgumentException("Missing Content-Disposition header in a multipart body part");
Map<String, String> contentDisposition = parameterParser.parse(contentDispositionContent, ';');
- ByteArrayOutputStream output = new ByteArrayOutputStream();
multipartStream.readBodyData(output);
parts.put(contentDisposition.get("name"), output.toByteArray());
+ output.reset();
nextPart = multipartStream.readBoundary();
}
return parts;
}
- catch(MultipartStream.MalformedStreamException e) {
+ catch (MultipartStream.MalformedStreamException e) {
throw new IllegalArgumentException("Malformed multipart/form-data request", e);
}
- catch(IOException e) {
+ catch (IOException e) {
throw new IllegalArgumentException("IO error reading multipart request " + uri, e);
}
}
@@ -80,4 +94,34 @@ public class MultipartParser {
return null;
}
+ /** A {@link java.io.ByteArrayOutputStream} that limits the number of bytes written to it */
+ private static class LimitedOutputStream extends ByteArrayOutputStream {
+
+ private long remaining;
+
+ /** Create a new OutputStream that can fit up to len bytes */
+ private LimitedOutputStream(long len) {
+ this.remaining = len;
+ }
+
+ @Override
+ public synchronized void write(int b) {
+ requireCapacity(1);
+ super.write(b);
+ remaining--;
+ }
+
+ @Override
+ public synchronized void write(byte[] b, int off, int len) {
+ requireCapacity(len);
+ super.write(b, off, len);
+ remaining -= len;
+ }
+
+ private void requireCapacity(int len) {
+ if (len > remaining) throw new IllegalArgumentException("Too many bytes to write");
+ }
+
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
index 888c402b6ec..a21f93bdaea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
@@ -4,6 +4,9 @@ package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.JsonFormat;
import com.yahoo.slime.Slime;
@@ -34,11 +37,11 @@ class ServiceApiResponse extends HttpResponse {
private final ApplicationId application;
private final List<URI> configServerURIs;
private final Slime slime;
- private final UriBuilder requestUri;
+ private final HttpURL requestUri;
// Only set for one of the setResponse calls
private String serviceName = null;
- private String restPath = null;
+ private Path restPath = null;
public ServiceApiResponse(ZoneId zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) {
super(200);
@@ -46,7 +49,7 @@ class ServiceApiResponse extends HttpResponse {
this.application = application;
this.configServerURIs = configServerURIs;
this.slime = new Slime();
- this.requestUri = new UriBuilder(requestUri).withoutParameters();
+ this.requestUri = HttpURL.from(requestUri).withQuery(Query.empty());
}
public void setResponse(ApplicationView applicationView) {
@@ -68,7 +71,7 @@ class ServiceApiResponse extends HttpResponse {
}
}
- public void setResponse(Map<?,?> responseData, String serviceName, String restPath) {
+ public void setResponse(Map<?,?> responseData, String serviceName, Path restPath) {
this.serviceName = serviceName;
this.restPath = restPath;
mapToSlime(responseData, slime.setObject());
@@ -138,7 +141,7 @@ class ServiceApiResponse extends HttpResponse {
mapToSlime((Map)entry, array.addObject());
}
- private String rewriteIfUrl(String urlOrAnyString, UriBuilder requestUri) {
+ private String rewriteIfUrl(String urlOrAnyString, HttpURL requestUri) {
if (urlOrAnyString == null) return null;
String hostPattern = "(" +
@@ -163,19 +166,18 @@ class ServiceApiResponse extends HttpResponse {
if (matcher.find()) {
String proxiedPath = urlOrAnyString.substring(matcher.group().length());
- return requestUri.append(proxiedPath).toString();
+ return requestUri.withPath(requestUri.path().append(Path.parse(proxiedPath))).asURI().toString();
} else {
return urlOrAnyString; // not a service url
}
}
- private UriBuilder generateLocalLinkPrefix(String identifier, String restPath) {
- String proxiedPath = identifier + "/" + restPath;
-
- if (this.requestUri.toString().endsWith(proxiedPath)) {
- return new UriBuilder(this.requestUri.toString().substring(0, this.requestUri.toString().length() - proxiedPath.length()));
+ private HttpURL generateLocalLinkPrefix(String identifier, Path restPath) {
+ Path proxiedPath = Path.parse(identifier).append(restPath);
+ if (requestUri.path().tail(proxiedPath.segments().size()).equals(proxiedPath)) {
+ return requestUri.withPath(requestUri.path().cut(proxiedPath.segments().size()));
} else {
- throw new IllegalStateException("Expected the resource path '" + this.requestUri + "' to end with '" + proxiedPath + "'");
+ throw new IllegalStateException("Expected the resource " + requestUri.path() + " to end with " + proxiedPath);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
index 0e09825ec41..27a8cbeaf3e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.zone.ZoneList;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
@@ -21,8 +22,11 @@ import com.yahoo.yolean.Exceptions;
import java.net.URI;
import java.util.List;
import java.util.logging.Level;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.yahoo.restapi.HttpURL.Path.parse;
+
/**
* REST API for proxying operator APIs to config servers in a given zone.
*
@@ -32,7 +36,9 @@ import java.util.stream.Stream;
public class ConfigServerApiHandler extends AuditLoggingRequestHandler {
private static final URI CONTROLLER_URI = URI.create("https://localhost:4443/");
- private static final List<String> WHITELISTED_APIS = List.of("/flags/v1/", "/nodes/v2/", "/orchestrator/v1/");
+ private static final List<HttpURL.Path> WHITELISTED_APIS = List.of(parse("/flags/v1/"),
+ parse("/nodes/v2/"),
+ parse("/orchestrator/v1/"));
private final ZoneRegistry zoneRegistry;
private final ConfigServerRestExecutor proxy;
@@ -84,17 +90,18 @@ public class ConfigServerApiHandler extends AuditLoggingRequestHandler {
}
ZoneId zoneId = ZoneId.from(path.get("environment"), path.get("region"));
- if (! zoneRegistry.hasZone(zoneId) && ! controllerZone.equals(zoneId)) {
+ if ( ! zoneRegistry.hasZone(zoneId) && ! controllerZone.equals(zoneId)) {
throw new IllegalArgumentException("No such zone: " + zoneId.value());
}
- String cfgPath = "/" + path.getRest();
- if (WHITELISTED_APIS.stream().noneMatch(cfgPath::startsWith)) {
- return ErrorResponse.forbidden("Cannot access '" + cfgPath +
- "' through /configserver/v1, following APIs are permitted: " + String.join(", ", WHITELISTED_APIS));
+ if (path.getRest().segments().size() < 2 || ! WHITELISTED_APIS.contains(path.getRest().head(2).withTrailingSlash())) {
+ return ErrorResponse.forbidden("Cannot access " + path.getRest() +
+ " through /configserver/v1, following APIs are permitted: " + WHITELISTED_APIS.stream()
+ .map(p -> "/" + String.join("/", p.segments()) + "/")
+ .collect(Collectors.joining(", ")));
}
- return proxy.handle(ProxyRequest.tryOne(getEndpoint(zoneId), cfgPath, request));
+ return proxy.handle(ProxyRequest.tryOne(getEndpoint(zoneId), path.getRest(), request));
}
private HttpResponse root(HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
index 850a9ef6107..33cd4948a7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
@@ -32,6 +32,7 @@ public class MeteringResponse extends SlimeJsonResponse {
object.setDouble("cpu", snapshot.getCpuCores());
object.setDouble("memory", snapshot.getMemoryGb());
object.setDouble("disk", snapshot.getDiskGb());
+ object.setString("architecture", snapshot.getArchitecture().name());
});
return slime;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 158cc6caede..1a4a42cb521 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.TreeMap;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -132,7 +133,7 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
entry -> entry.getValue().instanceJobs().get(entry.getKey())));
Cursor productionArray = versionObject.setArray("productionApplications");
statistics.productionSuccesses().stream()
- .collect(groupingBy(run -> run.id().application()))
+ .collect(groupingBy(run -> run.id().application(), TreeMap::new, toList()))
.forEach((id, runs) -> {
Cursor applicationObject = productionArray.addObject();
toSlime(applicationObject, id, request);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
index 766a51e3e8d..a7472ced09c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
@@ -4,12 +4,10 @@ package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.auth0.jwt.JWT;
import com.google.inject.Inject;
import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.Zone;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
import com.yahoo.restapi.Path;
@@ -90,6 +88,7 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
}
catch (Exception e) {
logger.log(Level.INFO, () -> "Exception mapping Athenz principal to roles: " + Exceptions.toMessageString(e));
+ return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Access denied"));
}
return Optional.empty();
}
@@ -104,7 +103,7 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
Optional<ApplicationName> application = Optional.ofNullable(path.get("application")).map(ApplicationName::from);
final Optional<ZoneId> zone;
- if(path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/{*}")) {
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/{*}")) {
zone = Optional.of(ZoneId.from(path.get("environment"), path.get("region")));
} else if(path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/{*}")) {
zone = Optional.of(ZoneId.from(path.get("environment"), path.get("region")));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index a7416cfa0ed..40a402f209b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -7,6 +7,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
@@ -107,7 +108,7 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
return ErrorResponse.notFoundError("Nothing at " + path);
}
- private ProxyRequest proxyRequest(ZoneId zoneId, String path, HttpRequest request) {
+ private ProxyRequest proxyRequest(ZoneId zoneId, HttpURL.Path path, HttpRequest request) {
return ProxyRequest.tryOne(zoneRegistry.getConfigServerVipUri(zoneId), path, request);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index de70fc54d3b..39ee2a6ce44 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -117,7 +117,6 @@ public class CloudAccessControl implements AccessControl {
@Override
public void deleteTenant(TenantName tenant, Credentials credentials) {
- deleteBillingInfo(tenant, credentials);
for (TenantRole role : Roles.tenantRoles(tenant))
userManagement.deleteRole(role);
}
@@ -134,13 +133,4 @@ public class CloudAccessControl implements AccessControl {
userManagement.deleteRole(role);
}
- private void deleteBillingInfo(TenantName tenant, Credentials credentials) {
- var users = Roles.tenantRoles(tenant)
- .stream()
- .flatMap(role -> userManagement.listUsers(role).stream())
- .collect(Collectors.toSet());
- var isPrivileged = allowedByPrivilegedRole((Auth0Credentials) credentials);
- billingController.deleteBillingInfo(tenant, users, isPrivileged);
- }
-
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
index 709a7967a5e..4e155e937b9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
@@ -5,7 +5,6 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import org.junit.Test;
-import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
@@ -154,9 +153,9 @@ public class ApplicationPackageTest {
}
private static Map<String, String> unzip(byte[] zip) {
- return new ZipStreamReader(new ByteArrayInputStream(zip), __ -> true, 1 << 10, true)
- .entries().stream()
- .collect(Collectors.toMap(entry -> entry.zipEntry().getName(),
+ return ZipEntries.from(zip, __ -> true, 1 << 10, true)
+ .asList().stream()
+ .collect(Collectors.toMap(ZipEntries.ZipEntryWithContent::name,
entry -> new String(entry.contentOrThrow(), UTF_8)));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntriesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntriesTest.java
new file mode 100644
index 00000000000..6908464640b
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntriesTest.java
@@ -0,0 +1,50 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.application.pkg;
+
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.X509CertificateBuilder;
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mpolden
+ */
+public class ZipEntriesTest {
+
+ @Test
+ public void test_replacement() {
+ ApplicationPackage applicationPackage = new ApplicationPackage(new byte[0]);
+ List<X509Certificate> certificates = IntStream.range(0, 3)
+ .mapToObj(i -> {
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ X500Principal subject = new X500Principal("CN=subject" + i);
+ return X509CertificateBuilder.fromKeypair(keyPair,
+ subject,
+ Instant.now(),
+ Instant.now().plusSeconds(1),
+ SignatureAlgorithm.SHA512_WITH_ECDSA,
+ BigInteger.valueOf(1))
+ .build();
+ })
+ .collect(Collectors.toUnmodifiableList());
+
+ assertEquals(List.of(), applicationPackage.trustedCertificates());
+ for (int i = 0; i < certificates.size(); i++) {
+ applicationPackage = applicationPackage.withTrustedCertificate(certificates.get(i));
+ assertEquals(certificates.subList(0, i + 1), applicationPackage.trustedCertificates());
+ }
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReaderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReaderTest.java
deleted file mode 100644
index 33c18d123d2..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipStreamReaderTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.application.pkg;
-
-import com.yahoo.security.KeyAlgorithm;
-import com.yahoo.security.KeyUtils;
-import com.yahoo.security.SignatureAlgorithm;
-import com.yahoo.security.X509CertificateBuilder;
-import org.junit.Test;
-
-import javax.security.auth.x500.X500Principal;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * @author mpolden
- */
-public class ZipStreamReaderTest {
-
- @Test
- public void test_size_limit() {
- Map<String, String> entries = Map.of("foo.xml", "foobar");
- try {
- new ZipStreamReader(new ByteArrayInputStream(zip(entries)), "foo.xml"::equals, 1, true);
- fail("Expected exception");
- } catch (IllegalArgumentException ignored) {}
-
- entries = Map.of("foo.xml", "foobar",
- "foo.jar", "0".repeat(100) // File not extracted and thus not subject to size limit
- );
- ZipStreamReader reader = new ZipStreamReader(new ByteArrayInputStream(zip(entries)), "foo.xml"::equals, 10, true);
- byte[] extracted = reader.entries().get(0).contentOrThrow();
- assertEquals("foobar", new String(extracted, StandardCharsets.UTF_8));
- }
-
- @Test
- public void test_paths() {
- Map<String, Boolean> tests = Map.of(
- "../../services.xml", true,
- "/../.././services.xml", true,
- "./application/././services.xml", true,
- "application//services.xml", true,
- "artifacts/", false, // empty dir
- "services..xml", false,
- "application/services.xml", false,
- "components/foo-bar-deploy.jar", false,
- "services.xml", false
- );
- tests.forEach((name, expectException) -> {
- try {
- new ZipStreamReader(new ByteArrayInputStream(zip(Map.of(name, "foo"))), name::equals, 1024, true);
- assertFalse("Expected exception for '" + name + "'", expectException);
- } catch (IllegalArgumentException ignored) {
- assertTrue("Unexpected exception for '" + name + "'", expectException);
- }
- });
- }
-
- @Test
- public void test_replacement() {
- ApplicationPackage applicationPackage = new ApplicationPackage(new byte[0]);
- List<X509Certificate> certificates = IntStream.range(0, 3)
- .mapToObj(i -> {
- KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
- X500Principal subject = new X500Principal("CN=subject" + i);
- return X509CertificateBuilder.fromKeypair(keyPair,
- subject,
- Instant.now(),
- Instant.now().plusSeconds(1),
- SignatureAlgorithm.SHA512_WITH_ECDSA,
- BigInteger.valueOf(1))
- .build();
- })
- .collect(Collectors.toUnmodifiableList());
-
- assertEquals(List.of(), applicationPackage.trustedCertificates());
- for (int i = 0; i < certificates.size(); i++) {
- applicationPackage = applicationPackage.withTrustedCertificate(certificates.get(i));
- assertEquals(certificates.subList(0, i + 1), applicationPackage.trustedCertificates());
- }
- }
-
- private static byte[] zip(Map<String, String> entries) {
- ByteArrayOutputStream zip = new ByteArrayOutputStream();
- try (ZipOutputStream out = new ZipOutputStream(zip)) {
- for (Map.Entry<String, String> entry : entries.entrySet()) {
- out.putNextEntry(new ZipEntry(entry.getKey()));
- out.write(entry.getValue().getBytes(StandardCharsets.UTF_8));
- out.closeEntry();
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- return zip.toByteArray();
- }
-
-}
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 640e6860eb6..a464e3d7e9b 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
@@ -264,36 +264,38 @@ public class ApplicationPackageBuilder {
xml.append(athenzIdentityAttributes);
}
xml.append(">\n");
- xml.append(" <instance id='").append(instances).append("'>\n");
- if (upgradePolicy != null || revisionTarget != null || revisionChange != null || upgradeRollout != null) {
- xml.append(" <upgrade ");
- if (upgradePolicy != null) xml.append("policy='").append(upgradePolicy).append("' ");
- if (revisionTarget != null) xml.append("revision-target='").append(revisionTarget).append("' ");
- if (revisionChange != null) xml.append("revision-change='").append(revisionChange).append("' ");
- if (upgradeRollout != null) xml.append("rollout='").append(upgradeRollout).append("' ");
- xml.append("/>\n");
- }
- xml.append(notifications);
- if (explicitSystemTest)
- xml.append(" <test />\n");
- if (explicitStagingTest)
- xml.append(" <staging />\n");
- xml.append(blockChange);
- xml.append(" <prod");
- if (globalServiceId != null) {
- xml.append(" global-service-id='");
- xml.append(globalServiceId);
- xml.append("'");
- }
- xml.append(">\n");
- xml.append(prodBody);
- xml.append(" </prod>\n");
- if (endpointsBody.length() > 0 ) {
- xml.append(" <endpoints>\n");
- xml.append(endpointsBody);
- xml.append(" </endpoints>\n");
+ for (String instance : instances.split(",")) {
+ xml.append(" <instance id='").append(instance).append("'>\n");
+ if (upgradePolicy != null || revisionTarget != null || revisionChange != null || upgradeRollout != null) {
+ xml.append(" <upgrade ");
+ if (upgradePolicy != null) xml.append("policy='").append(upgradePolicy).append("' ");
+ if (revisionTarget != null) xml.append("revision-target='").append(revisionTarget).append("' ");
+ if (revisionChange != null) xml.append("revision-change='").append(revisionChange).append("' ");
+ if (upgradeRollout != null) xml.append("rollout='").append(upgradeRollout).append("' ");
+ xml.append("/>\n");
+ }
+ xml.append(notifications);
+ if (explicitSystemTest)
+ xml.append(" <test />\n");
+ if (explicitStagingTest)
+ xml.append(" <staging />\n");
+ xml.append(blockChange);
+ xml.append(" <prod");
+ if (globalServiceId != null) {
+ xml.append(" global-service-id='");
+ xml.append(globalServiceId);
+ xml.append("'");
+ }
+ xml.append(">\n");
+ xml.append(prodBody);
+ xml.append(" </prod>\n");
+ if (endpointsBody.length() > 0) {
+ xml.append(" <endpoints>\n");
+ xml.append(endpointsBody);
+ xml.append(" </endpoints>\n");
+ }
+ xml.append(" </instance>\n");
}
- xml.append(" </instance>\n");
if (applicationEndpointsBody.length() > 0) {
xml.append(" <endpoints>\n");
xml.append(applicationEndpointsBody);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index 989a7c31821..86c21839c96 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -32,7 +32,6 @@ import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
-import com.yahoo.vespa.hosted.controller.application.pkg.ZipStreamReader;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunner;
import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher;
@@ -41,8 +40,6 @@ import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
import javax.security.auth.x500.X500Principal;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index e0ce1c060bc..1643f1f818b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -13,6 +13,8 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.text.Text;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
@@ -521,7 +523,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
// Returns a canned example response
@Override
- public Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, String restPath) {
+ public Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, Path restPath) {
Map<String,List<?>> root = new HashMap<>();
List<Map<?,?>> resources = new ArrayList<>();
Map<String,String> resource = new HashMap<>();
@@ -532,7 +534,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public String getServiceStatusPage(DeploymentId deployment, String serviceName, String node, String subPath) {
+ public String getServiceStatusPage(DeploymentId deployment, String serviceName, DomainName node, Path subPath) {
return "<h1>OK</h1>";
}
@@ -567,8 +569,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public ProxyResponse getApplicationPackageContent(DeploymentId deployment, String path, URI requestUri) {
- return new ProxyResponse(("{\"path\":\"" + path + "\"}").getBytes(StandardCharsets.UTF_8), "application/json", 200);
+ public ProxyResponse getApplicationPackageContent(DeploymentId deployment, Path path, URI requestUri) {
+ return new ProxyResponse(("{\"path\":\"/" + String.join("/", path.segments()) + "\"}").getBytes(StandardCharsets.UTF_8), "application/json", 200);
}
public void setLogStream(Supplier<String> log) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
index a2a1b4ba0a1..7b67db39350 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.TreeMap;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -224,7 +225,7 @@ public class NodeRepositoryMock implements NodeRepository {
}
public void putApplication(ZoneId zone, Application application) {
- applications.computeIfAbsent(zone, (k) -> new HashMap<>())
+ applications.computeIfAbsent(zone, (k) -> new TreeMap<>())
.put(application.id(), application);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
index c772ca8b8f7..ed00e7f5473 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
@@ -18,12 +18,13 @@ import java.time.Duration;
import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* @author ogronnesby
*/
public class CloudTrialExpirerTest {
- private final ControllerTester tester = new ControllerTester(SystemName.Public);
+ private final ControllerTester tester = new ControllerTester(SystemName.PublicCd);
private final DeploymentTester deploymentTester = new DeploymentTester(tester);
private final CloudTrialExpirer expirer = new CloudTrialExpirer(tester.controller(), Duration.ofMinutes(5));
@@ -35,6 +36,13 @@ public class CloudTrialExpirerTest {
}
@Test
+ public void tombstone_inactive_none() {
+ registerTenant("none-tenant", "none", Duration.ofDays(28).plusMillis(1));
+ expirer.maintain();
+ assertEquals(Tenant.Type.deleted, tester.controller().tenants().get(TenantName.from("none-tenant"), true).get().type());
+ }
+
+ @Test
public void keep_inactive_nontrial_tenants() {
registerTenant("not-a-trial-tenant", "pay-as-you-go", Duration.ofDays(30));
expirer.maintain();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
index 80ee0988658..10193b48837 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
@@ -38,7 +39,7 @@ public class CostReportMaintainerTest {
"1970-01-01,Property3,128.0,96.0,2000.0,0.3333333333333333\n" +
"1970-01-01,Property2,160.0,96.0,2000.0,0.3611111111111111",
csv),
- Map.of(new Property("Property3"), new ResourceAllocation(256, 192, 4000))
+ Map.of(new Property("Property3"), new ResourceAllocation(256, 192, 4000, NodeResources.Architecture.getDefault()))
);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
index 4acc9f91e79..6109890bae3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
@@ -58,18 +58,18 @@ public class ResourceMeterMaintainerTest {
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().cost().getAsDouble())));
List<ResourceSnapshot> resourceSnapshots = List.of(
- new ResourceSnapshot(app1, 12, 34, 56, Instant.EPOCH, z1),
- new ResourceSnapshot(app1, 23, 45, 67, Instant.EPOCH, z2),
- new ResourceSnapshot(app2, 34, 56, 78, Instant.EPOCH, z1));
+ new ResourceSnapshot(app1, 12, 34, 56, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1),
+ new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2),
+ new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1));
maintainer.updateDeploymentCost(resourceSnapshots);
assertCost.accept(app1, Map.of(z1, 1.40, z2, 2.50));
assertCost.accept(app2, Map.of(z1, 3.59));
// Remove a region from app1 and add region to app2
resourceSnapshots = List.of(
- new ResourceSnapshot(app1, 23, 45, 67, Instant.EPOCH, z2),
- new ResourceSnapshot(app2, 34, 56, 78, Instant.EPOCH, z1),
- new ResourceSnapshot(app2, 45, 67, 89, Instant.EPOCH, z2));
+ new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2),
+ new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1),
+ new ResourceSnapshot(app2, 45, 67, 89, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2));
maintainer.updateDeploymentCost(resourceSnapshots);
assertCost.accept(app1, Map.of(z2, 2.50));
assertCost.accept(app2, Map.of(z1, 3.59, z2, 4.68));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
index d51856b329d..d0dbb23ad1b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.notification;
+import com.google.common.collect.ImmutableBiMap;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.TenantName;
@@ -11,8 +12,14 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
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.stubs.MockMailer;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.notify.Notifier;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
+import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import org.junit.Before;
import org.junit.Test;
@@ -22,6 +29,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.stream.Collectors;
import static com.yahoo.vespa.hosted.controller.notification.Notification.Level;
@@ -36,6 +44,19 @@ import static org.junit.Assert.assertTrue;
public class NotificationsDbTest {
private static final TenantName tenant = TenantName.from("tenant1");
+ private static final String email = "user1@example.com";
+ private static final CloudTenant cloudTenant = new CloudTenant(tenant,
+ Instant.now(),
+ LastLoginInfo.EMPTY,
+ Optional.empty(),
+ ImmutableBiMap.of(),
+ TenantInfo.empty()
+ .withContacts(new TenantContacts(
+ List.of(new TenantContacts.EmailContact(
+ List.of(TenantContacts.Audience.NOTIFICATIONS),
+ email)))),
+ List.of(),
+ Optional.empty());
private static final List<Notification> notifications = List.of(
notification(1001, Type.deployment, Level.error, NotificationSource.from(tenant), "tenant msg"),
notification(1101, Type.applicationPackage, Level.warning, NotificationSource.from(TenantAndApplicationId.from(tenant.value(), "app1")), "app msg"),
@@ -46,7 +67,8 @@ public class NotificationsDbTest {
private final ManualClock clock = new ManualClock(Instant.ofEpochSecond(12345));
private final MockCuratorDb curatorDb = new MockCuratorDb();
- private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb);
+ private final MockMailer mailer = new MockMailer();
+ private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb, new Notifier(curatorDb, mailer));
@Test
public void list_test() {
@@ -75,6 +97,29 @@ public class NotificationsDbTest {
}
@Test
+ public void notifier_test() {
+ Notification notification1 = notification(12345, Type.deployment, Level.warning, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg #2");
+ Notification notification2 = notification(12345, Type.deployment, Level.error, NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), "instance msg #3");
+ Notification notification3 = notification(12345, Type.reindex, Level.warning, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg #2");
+
+ var a = notifications.get(0);
+ notificationsDb.setNotification(a.source(), a.type(), a.level(), a.messages());
+ assertEquals(0, mailer.inbox(email).size());
+
+ // Replace the 3rd notification. but don't change source or type
+ notificationsDb.setNotification(notification1.source(), notification1.type(), notification1.level(), notification1.messages());
+ assertEquals(0, mailer.inbox(email).size());
+
+ // Notification for a new app, add without replacement
+ notificationsDb.setNotification(notification2.source(), notification2.type(), notification2.level(), notification2.messages());
+ assertEquals(1, mailer.inbox(email).size());
+
+ // Notification for new type on existing app
+ notificationsDb.setNotification(notification3.source(), notification3.type(), notification3.level(), notification3.messages());
+ assertEquals(2, mailer.inbox(email).size());
+ }
+
+ @Test
public void remove_single_test() {
// Remove the 3rd notification
notificationsDb.removeNotification(NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), Type.deployment);
@@ -98,6 +143,29 @@ public class NotificationsDbTest {
}
@Test
+ public void deployment_metrics_notify_test() {
+ DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenant.value(), "app1", "instance1"), ZoneId.from("prod", "us-south-3"));
+ NotificationSource sourceCluster1 = NotificationSource.from(deploymentId, ClusterSpec.Id.from("cluster1"));
+ List<Notification> expected = new ArrayList<>(notifications);
+
+ // No metrics, no new notification
+ notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of());
+ assertEquals(0, mailer.inbox(email).size());
+
+ // Metrics that contain none of the feed block metrics does not create new notification
+ notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", null, null, null, null, Map.of())));
+ assertEquals(0, mailer.inbox(email).size());
+
+ // One resource is at warning
+ notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", 0.88, 0.9, 0.3, 0.5, Map.of())));
+ assertEquals(1, mailer.inbox(email).size());
+
+ // One resource over the limit
+ notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", 0.95, 0.9, 0.3, 0.5, Map.of())));
+ assertEquals(2, mailer.inbox(email).size());
+ }
+
+ @Test
public void feed_blocked_single_cluster_test() {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenant.value(), "app1", "instance1"), ZoneId.from("prod", "us-south-3"));
NotificationSource sourceCluster1 = NotificationSource.from(deploymentId, ClusterSpec.Id.from("cluster1"));
@@ -160,6 +228,8 @@ public class NotificationsDbTest {
@Before
public void init() {
curatorDb.writeNotifications(tenant, notifications);
+ curatorDb.writeTenant(cloudTenant);
+ mailer.reset();
}
private static List<Notification> notificationIndices(int... indices) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
index 0b28d94386d..e10a41cbae3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
@@ -6,6 +6,7 @@ import com.github.tomakehurst.wiremock.stubbing.Scenario;
import com.yahoo.config.provision.SystemName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.yolean.concurrent.Sleeper;
import org.apache.http.protocol.HttpContext;
@@ -46,7 +47,7 @@ public class ConfigServerRestExecutorImplTest {
stubRequests(path);
HttpRequest request = HttpRequest.createTestRequest(url.toString(), com.yahoo.jdisc.http.HttpRequest.Method.GET);
- ProxyRequest proxyRequest = ProxyRequest.tryOne(url, path, request);
+ ProxyRequest proxyRequest = ProxyRequest.tryOne(url, Path.parse(path), request);
// Request is retried
HttpResponse response = proxy.handle(proxyRequest);
@@ -71,7 +72,7 @@ public class ConfigServerRestExecutorImplTest {
stubRequests(path);
HttpRequest request = HttpRequest.createTestRequest(url.toString(), com.yahoo.jdisc.http.HttpRequest.Method.GET);
- ProxyRequest proxyRequest = ProxyRequest.tryOne(url, path, request);
+ ProxyRequest proxyRequest = ProxyRequest.tryOne(url, Path.parse(path), request);
// Connections are reused
assertEquals(200, proxy.handle(proxyRequest).getStatus());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
index 8542530628c..e8b5df7efa1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -11,20 +12,18 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
/**
* @author Haakon Dybdahl
*/
public class ProxyRequestTest {
- @SuppressWarnings("deprecation")
- @Rule
- public final ExpectedException exception = ExpectedException.none();
-
@Test
public void testBadUri() {
- exception.expectMessage("Request path '/path' does not end with proxy path '/zone/v2/'");
- testRequest("http://domain.tld/path", "/zone/v2/");
+ assertThrows("Request path '/path' does not end with proxy path '/zone/v2/'",
+ IllegalArgumentException.class,
+ () -> testRequest("http://domain.tld/path", "/zone/v2/"));
}
@Test
@@ -33,7 +32,7 @@ public class ProxyRequestTest {
// Root request
ProxyRequest request = testRequest("http://controller.domain.tld/my/path", "");
assertEquals(URI.create("http://controller.domain.tld/my/path/"), request.getControllerPrefixUri());
- assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"),
+ assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234"),
request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/")));
}
@@ -64,7 +63,7 @@ public class ProxyRequestTest {
private static ProxyRequest testRequest(String url, String pathPrefix) {
return new ProxyRequest(HttpRequest.Method.GET, URI.create(url), Map.of(), null,
- List.of(URI.create("http://example.com")), pathPrefix);
+ List.of(URI.create("http://example.com")), Path.parse(pathPrefix));
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
index 4cc2c1cc79e..1ba0200eec3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -20,7 +21,7 @@ public class ProxyResponseTest {
@Test
public void testRewriteUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("http://domain.tld/zone/v2/dev/us-north-1/configserver"),
- Map.of(), null, List.of(URI.create("http://example.com")), "configserver");
+ Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
@@ -31,14 +32,14 @@ public class ProxyResponseTest {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
proxyResponse.render(outputStream);
- String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+ String document = outputStream.toString(StandardCharsets.UTF_8);
assertEquals("response link is http://domain.tld/zone/v2/dev/us-north-1/bla/bla/", document);
}
@Test
public void testRewriteSecureUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("https://domain.tld/zone/v2/prod/eu-south-3/configserver"),
- Map.of(), null, List.of(URI.create("http://example.com")), "configserver");
+ Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
@@ -49,7 +50,7 @@ public class ProxyResponseTest {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
proxyResponse.render(outputStream);
- String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+ String document = outputStream.toString(StandardCharsets.UTF_8);
assertEquals("response link is https://domain.tld/zone/v2/prod/eu-south-3/bla/bla/", document);
}
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 f94f87b0f46..67b201bdc9d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
@@ -502,7 +503,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Get content/../foo
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/content/%2E%2E%2Ffoo", GET).userIdentity(USER_ID),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}", 403);
+ accessDenied, 403);
// Get content - root
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/content/", GET).userIdentity(USER_ID),
"{\"path\":\"/\"}");
@@ -1069,27 +1070,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("deploy-result.json"));
}
- @Test
- public void testMeteringResponses() {
- MockMeteringClient mockMeteringClient = tester.serviceRegistry().meteringService();
-
- // Mock response for MeteringClient
- ResourceAllocation currentSnapshot = new ResourceAllocation(1, 2, 3);
- ResourceAllocation thisMonth = new ResourceAllocation(12, 24, 1000);
- ResourceAllocation lastMonth = new ResourceAllocation(24, 48, 2000);
- ApplicationId applicationId = ApplicationId.from("doesnotexist", "doesnotexist", "default");
- Map<ApplicationId, List<ResourceSnapshot>> snapshotHistory = Map.of(applicationId, List.of(
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(123), ZoneId.defaultId()),
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(246), ZoneId.defaultId()),
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(492), ZoneId.defaultId())));
-
- mockMeteringClient.setMeteringData(new MeteringData(thisMonth, lastMonth, currentSnapshot, snapshotHistory));
-
- tester.assertResponse(request("/application/v4/tenant/doesnotexist/application/doesnotexist/metering", GET)
- .userIdentity(USER_ID)
- .oAuthCredentials(OKTA_CREDENTIALS),
- new File("instance1-metering.json"));
- }
@Test
public void testRemovingAllDeployments() {
@@ -1657,21 +1637,21 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/document/v1/", GET)
.userIdentity(USER_ID)
.oAuthCredentials(OKTA_CREDENTIALS),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}",
- 403);
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at path '/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/document/v1/'\"}",
+ 404);
// Test path traversal
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state/v1/../../document/v1/", GET)
.userIdentity(USER_ID)
.oAuthCredentials(OKTA_CREDENTIALS),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}",
- 403);
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at path '/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/document/v1/'\"}",
+ 404);
// Test urlencoded path traversal
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state%2Fv1%2F..%2F..%2Fdocument%2Fv1%2F", GET)
.userIdentity(USER_ID)
.oAuthCredentials(OKTA_CREDENTIALS),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}",
+ accessDenied,
403);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
index 2c81b1a7fd8..12a0a00713c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
@@ -11,12 +11,12 @@ import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.net.URI;
-import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* @author bratseth
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
public class MultipartParserTest {
@Test
- public void multipartParserTest() throws URISyntaxException {
+ public void parser() {
String data =
"Content-Type: multipart/form-data; boundary=AaB03x\r\n" +
"\r\n" +
@@ -43,13 +43,7 @@ public class MultipartParserTest {
"\r\n" +
"... contents of file1.txt ...\r\n" +
"--AaB03x--\r\n";
- ByteArrayInputStream dataStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
- HttpRequest request = HttpRequest.createRequest(new MockCurrentContainer(),
- new URI("http://foo"),
- com.yahoo.jdisc.http.HttpRequest.Method.POST,
- dataStream);
- request.getJDiscRequest().headers().put("Content-Type", "multipart/form-data; boundary=AaB03x");
- Map<String, byte[]> parts = new MultipartParser().parse(request);
+ Map<String, byte[]> parts = parse(data, Long.MAX_VALUE);
assertEquals(3, parts.size());
assertTrue(parts.containsKey("submit-name"));
assertTrue(parts.containsKey("submit-address"));
@@ -57,6 +51,41 @@ public class MultipartParserTest {
assertEquals("Larry", new String(parts.get("submit-name"), StandardCharsets.UTF_8));
assertEquals("... contents of file1.txt ...", new String(parts.get("files"), StandardCharsets.UTF_8));
}
+
+ @Test
+ public void max_length() {
+ String part1 = "Larry";
+ String part2 = "House 1";
+ String data =
+ "Content-Type: multipart/form-data; boundary=AaB03x\r\n" +
+ "\r\n" +
+ "--AaB03x\r\n" +
+ "Content-Disposition: form-data; name=\"submit-name\"\r\n" +
+ "\r\n" +
+ part1 + "\r\n" +
+ "--AaB03x\r\n" +
+ "Content-Disposition: form-data; name=\"submit-address\"\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\n" +
+ part2 + "\r\n" +
+ "--AaB03x--\r\n";
+ parse(data, part1.length() + part2.length());
+ try {
+ parse(data, part1.length() + part2.length() - 1);
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
+
+ private Map<String, byte[]> parse(String data, long maxLength) {
+ ByteArrayInputStream dataStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+ HttpRequest request = HttpRequest.createRequest(new MockCurrentContainer(),
+ URI.create("http://foo"),
+ com.yahoo.jdisc.http.HttpRequest.Method.POST,
+ dataStream);
+ request.getJDiscRequest().headers().put("Content-Type", "multipart/form-data; boundary=AaB03x");
+ return new MultipartParser(maxLength).parse(request);
+ }
private static class MockCurrentContainer implements CurrentContainer {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
index 9d0054f327a..ce6f713a13d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
@@ -50,6 +50,11 @@ public class ConfigServerApiHandlerTest extends ControllerContainerTest {
tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1"),
new File("root.json"));
+ // GET /configserver/v1/nodes/v2
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2"),
+ "ok");
+ assertLastRequest("https://cfg.prod.us-north-1.test.vip:4443/", "GET");
+
// GET /configserver/v1/nodes/v2/node/?recursive=true
tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node/?recursive=true"),
"ok");
@@ -83,12 +88,12 @@ public class ConfigServerApiHandlerTest extends ControllerContainerTest {
@Test
public void test_allowed_apis() {
// GET /configserver/v1/prod/us-north-1
- tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1"),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/"),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access path '/' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
403);
tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/application/v2/tenant/vespa"),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/application/v2/tenant/vespa' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access path '/application/v2/tenant/vespa' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
403);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
index 7bc01de2053..8a6244e19a0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.test.ManualClock;
@@ -158,8 +159,8 @@ public class ControllerApiTest extends ControllerContainerTest {
Instant timestamp = Instant.ofEpochMilli(123456789);
ZoneId zoneId = ZoneId.defaultId();
List<ResourceSnapshot> snapshots = List.of(
- new ResourceSnapshot(applicationId, 12,48,1200, timestamp, zoneId),
- new ResourceSnapshot(applicationId, 24, 96,2400, timestamp, zoneId)
+ new ResourceSnapshot(applicationId, 12,48,1200, NodeResources.Architecture.arm64, timestamp, zoneId),
+ new ResourceSnapshot(applicationId, 24, 96,2400, NodeResources.Architecture.x86_64, timestamp, zoneId)
);
tester.controller().serviceRegistry().meteringService().consume(snapshots);
tester.assertResponse(
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/metering.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/metering.json
index b64e8f26a63..1008ada6def 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/metering.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/metering.json
@@ -5,7 +5,8 @@
"zoneId": "prod.default",
"cpu": 12.0,
"memory": 48.0,
- "disk": 1200.0
+ "disk": 1200.0,
+ "architecture":"arm64"
},
{
"applicationId": "tenant.app.instance",
@@ -13,6 +14,7 @@
"zoneId": "prod.default",
"cpu": 24.0,
"memory": 96.0,
- "disk": 2400.0
+ "disk": 2400.0,
+ "architecture":"x86_64"
}
] \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
index 7d17e97e66b..cf4deb7b4bf 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
@@ -125,7 +125,7 @@ public class OsApiTest extends ControllerContainerTest {
// Error: Cancel firmware checks in an empty set of zones.
assertResponse(new Request("http://localhost:8080/os/v1/firmware/dev/", "", Request.Method.DELETE),
- "{\"error-code\":\"NOT_FOUND\",\"message\":\"No zones at path '/os/v1/firmware/dev'\"}", 404);
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"No zones at path '/os/v1/firmware/dev/'\"}", 404);
assertFalse("Actions are logged to audit log", tester.controller().auditLogger().readLog().entries().isEmpty());
}
diff --git a/dist/vespa.spec b/dist/vespa.spec
index e13f06ed0f5..ce2f0137262 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -109,7 +109,7 @@ BuildRequires: vespa-gtest = 1.11.0
%define _use_vespa_gtest 1
BuildRequires: vespa-icu-devel >= 65.1.0-1
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.7.1
+BuildRequires: vespa-onnxruntime-devel = 1.11.0
BuildRequires: vespa-openssl-devel >= 1.1.1n-1
%define _use_vespa_openssl 1
BuildRequires: vespa-protobuf-devel = 3.19.1
@@ -137,7 +137,7 @@ BuildRequires: vespa-openssl-devel >= 1.1.1n-1
BuildRequires: vespa-gtest = 1.11.0
%define _use_vespa_gtest 1
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.7.1
+BuildRequires: vespa-onnxruntime-devel = 1.11.0
BuildRequires: vespa-protobuf-devel = 3.19.1
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%endif
@@ -146,7 +146,7 @@ BuildRequires: cmake >= 3.20.2
BuildRequires: maven
BuildRequires: openssl-devel
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.7.1
+BuildRequires: vespa-onnxruntime-devel = 1.11.0
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
BuildRequires: protobuf-devel
BuildRequires: (llvm-devel >= 13.0.0 and llvm-devel < 14)
@@ -159,7 +159,7 @@ BuildRequires: cmake >= 3.9.1
BuildRequires: maven
BuildRequires: openssl-devel
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.7.1
+BuildRequires: vespa-onnxruntime-devel = 1.11.0
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%if 0%{?fc34}
BuildRequires: protobuf-devel
@@ -450,7 +450,7 @@ Requires: llvm-libs >= 13.0.1
Requires: llvm-libs >= 13.0.1
%endif
%endif
-Requires: vespa-onnxruntime = 1.7.1
+Requires: vespa-onnxruntime = 1.11.0
%description libs
diff --git a/docproc/src/main/java/com/yahoo/docproc/jdisc/messagebus/MessageFactory.java b/docproc/src/main/java/com/yahoo/docproc/jdisc/messagebus/MessageFactory.java
index 4668942b61e..1fe6d4cc86c 100644
--- a/docproc/src/main/java/com/yahoo/docproc/jdisc/messagebus/MessageFactory.java
+++ b/docproc/src/main/java/com/yahoo/docproc/jdisc/messagebus/MessageFactory.java
@@ -21,12 +21,13 @@ import java.util.logging.Logger;
/**
* @author Einar M R Rosenvinge
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
class MessageFactory {
private final static Logger log = Logger.getLogger(MessageFactory.class.getName());
private final Message requestMsg;
- private final LoadType loadType;
- private final DocumentProtocol.Priority priority;
+ private final LoadType loadType; // TODO: Remove on Vespa 8
+ private final DocumentProtocol.Priority priority; // TODO: Remove on Vespa 8
@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public MessageFactory(DocumentMessage requestMsg) {
diff --git a/document/src/main/java/com/yahoo/document/datatypes/StringFieldValue.java b/document/src/main/java/com/yahoo/document/datatypes/StringFieldValue.java
index de96c548652..51b068b4712 100644
--- a/document/src/main/java/com/yahoo/document/datatypes/StringFieldValue.java
+++ b/document/src/main/java/com/yahoo/document/datatypes/StringFieldValue.java
@@ -17,7 +17,7 @@ import com.yahoo.vespa.objects.Ids;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-import java.util.OptionalInt;
+import java.util.Objects;
/**
* A StringFieldValue is a wrapper class that holds a String in {@link com.yahoo.document.Document}s and
@@ -57,10 +57,9 @@ public class StringFieldValue extends FieldValue {
}
private static void validateTextString(String value) {
- OptionalInt illegalCodePoint = Text.validateTextString(value);
- if (illegalCodePoint.isPresent()) {
+ if ( ! Text.isValidTextString(value)) {
throw new IllegalArgumentException("The string field value contains illegal code point 0x" +
- Integer.toHexString(illegalCodePoint.getAsInt()).toUpperCase());
+ Integer.toHexString(Text.validateTextString(value).getAsInt()).toUpperCase());
}
}
@@ -88,7 +87,7 @@ public class StringFieldValue extends FieldValue {
public StringFieldValue clone() {
StringFieldValue strfval = (StringFieldValue) super.clone();
if (spanTrees != null) {
- strfval.spanTrees = new HashMap<String, SpanTree>(spanTrees.size());
+ strfval.spanTrees = new HashMap<>(spanTrees.size());
for (Map.Entry<String, SpanTree> entry : spanTrees.entrySet()) {
strfval.spanTrees.put(entry.getKey(), new SpanTree(entry.getValue()));
}
@@ -240,8 +239,8 @@ public class StringFieldValue extends FieldValue {
if (!(o instanceof StringFieldValue)) return false;
if (!super.equals(o)) return false;
StringFieldValue that = (StringFieldValue) o;
- if ((spanTrees != null) ? !spanTrees.equals(that.spanTrees) : that.spanTrees != null) return false;
- if ((value != null) ? !value.equals(that.value) : that.value != null) return false;
+ if (!Objects.equals(spanTrees, that.spanTrees)) return false;
+ if (!Objects.equals(value, that.value)) return false;
return true;
}
diff --git a/document/src/main/java/com/yahoo/document/idstring/IdString.java b/document/src/main/java/com/yahoo/document/idstring/IdString.java
index 55beff9eef9..40ac19dec6d 100644
--- a/document/src/main/java/com/yahoo/document/idstring/IdString.java
+++ b/document/src/main/java/com/yahoo/document/idstring/IdString.java
@@ -5,8 +5,6 @@ import com.yahoo.api.annotations.Beta;
import com.yahoo.text.Text;
import com.yahoo.text.Utf8String;
-import java.util.OptionalInt;
-
/**
* To be used with DocumentId constructor.
*
@@ -81,10 +79,9 @@ public abstract class IdString {
}
private static void validateTextString(String id) {
- OptionalInt illegalCodePoint = Text.validateTextString(id);
- if (illegalCodePoint.isPresent()) {
+ if ( ! Text.isValidTextString(id)) {
throw new IllegalArgumentException("Unparseable id '" + id + "': Contains illegal code point 0x" +
- Integer.toHexString(illegalCodePoint.getAsInt()).toUpperCase());
+ Integer.toHexString(Text.validateTextString(id).getAsInt()).toUpperCase());
}
}
diff --git a/document/src/tests/repo/doctype_config_test.cpp b/document/src/tests/repo/doctype_config_test.cpp
index 84ec1414fcc..eab40e04617 100644
--- a/document/src/tests/repo/doctype_config_test.cpp
+++ b/document/src/tests/repo/doctype_config_test.cpp
@@ -659,4 +659,14 @@ TEST("Tensor fields have tensor types") {
EXPECT_TRUE(&tensorField1.getDataType() == tensorFieldValue1->getDataType());
}
+TEST("requireThatImportedFieldsWorks") {
+ DocumentTypeRepo repo(readDocumenttypesConfig(TEST_PATH("import-dt.cfg")));
+ ASSERT_TRUE(repo.getDocumentType("document"));
+ ASSERT_TRUE(repo.getDocumentType("grandparent"));
+ ASSERT_TRUE(repo.getDocumentType("parent_a"));
+ ASSERT_TRUE(repo.getDocumentType("parent_b"));
+ ASSERT_TRUE(repo.getDocumentType("child"));
+}
+
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/repo/import-dt.cfg b/document/src/tests/repo/import-dt.cfg
new file mode 100644
index 00000000000..742f377e65c
--- /dev/null
+++ b/document/src/tests/repo/import-dt.cfg
@@ -0,0 +1,139 @@
+enablecompression false
+usev8geopositions false
+doctype[0].name "document"
+doctype[0].idx 10000
+doctype[0].internalid 8
+doctype[0].contentstruct 10001
+doctype[0].primitivetype[0].idx 10002
+doctype[0].primitivetype[0].name "bool"
+doctype[0].primitivetype[1].idx 10003
+doctype[0].primitivetype[1].name "byte"
+doctype[0].primitivetype[2].idx 10004
+doctype[0].primitivetype[2].name "double"
+doctype[0].primitivetype[3].idx 10005
+doctype[0].primitivetype[3].name "float"
+doctype[0].primitivetype[4].idx 10006
+doctype[0].primitivetype[4].name "float16"
+doctype[0].primitivetype[5].idx 10007
+doctype[0].primitivetype[5].name "int"
+doctype[0].primitivetype[6].idx 10008
+doctype[0].primitivetype[6].name "long"
+doctype[0].primitivetype[7].idx 10010
+doctype[0].primitivetype[7].name "predicate"
+doctype[0].primitivetype[8].idx 10011
+doctype[0].primitivetype[8].name "raw"
+doctype[0].primitivetype[9].idx 10012
+doctype[0].primitivetype[9].name "string"
+doctype[0].primitivetype[10].idx 10014
+doctype[0].primitivetype[10].name "uri"
+doctype[0].wsettype[0].idx 10013
+doctype[0].wsettype[0].elementtype 10012
+doctype[0].wsettype[0].createifnonexistent true
+doctype[0].wsettype[0].removeifzero true
+doctype[0].wsettype[0].internalid 18
+doctype[0].structtype[0].idx 10001
+doctype[0].structtype[0].name "document.header"
+doctype[0].structtype[0].internalid -284186494
+doctype[0].structtype[1].idx 10009
+doctype[0].structtype[1].name "position"
+doctype[0].structtype[1].field[0].name "x"
+doctype[0].structtype[1].field[0].internalid 914677694
+doctype[0].structtype[1].field[0].type 10007
+doctype[0].structtype[1].field[1].name "y"
+doctype[0].structtype[1].field[1].internalid 900009410
+doctype[0].structtype[1].field[1].type 10007
+doctype[0].structtype[1].internalid 1381038251
+doctype[1].name "child"
+doctype[1].idx 10015
+doctype[1].internalid 746267614
+doctype[1].inherits[0].idx 10000
+doctype[1].contentstruct 10016
+doctype[1].fieldsets{myfieldset}.fields[0] "my_ancient_int_field"
+doctype[1].fieldsets{myfieldset}.fields[1] "my_int_field"
+doctype[1].fieldsets{myfieldset}.fields[2] "my_string_field"
+doctype[1].fieldsets{[document]}.fields[0] "a_ref"
+doctype[1].fieldsets{[document]}.fields[1] "b_ref"
+doctype[1].fieldsets{[document]}.fields[2] "b_ref_with_summary"
+doctype[1].importedfield[0].name "my_int_field"
+doctype[1].importedfield[1].name "my_string_field"
+doctype[1].importedfield[2].name "my_int_array_field"
+doctype[1].importedfield[3].name "my_int_wset_field"
+doctype[1].importedfield[4].name "my_ancient_int_field"
+doctype[1].documentref[0].idx 10017
+doctype[1].documentref[0].targettype 10018
+doctype[1].documentref[0].internalid -1586898847
+doctype[1].documentref[1].idx 10019
+doctype[1].documentref[1].targettype 10020
+doctype[1].documentref[1].internalid -1586898816
+doctype[1].structtype[0].idx 10016
+doctype[1].structtype[0].name "child.header"
+doctype[1].structtype[0].field[0].name "a_ref"
+doctype[1].structtype[0].field[0].internalid 16961427
+doctype[1].structtype[0].field[0].type 10017
+doctype[1].structtype[0].field[1].name "b_ref"
+doctype[1].structtype[0].field[1].internalid 590403512
+doctype[1].structtype[0].field[1].type 10019
+doctype[1].structtype[0].field[2].name "b_ref_with_summary"
+doctype[1].structtype[0].field[2].internalid 232360236
+doctype[1].structtype[0].field[2].type 10019
+doctype[1].structtype[0].internalid 81425825
+doctype[2].name "grandparent"
+doctype[2].idx 10021
+doctype[2].internalid -154107656
+doctype[2].inherits[0].idx 10000
+doctype[2].contentstruct 10022
+doctype[2].fieldsets{[document]}.fields[0] "int_field"
+doctype[2].structtype[0].idx 10022
+doctype[2].structtype[0].name "grandparent.header"
+doctype[2].structtype[0].field[0].name "int_field"
+doctype[2].structtype[0].field[0].internalid 2128822283
+doctype[2].structtype[0].field[0].type 10007
+doctype[2].structtype[0].internalid 990971719
+doctype[3].name "parent_a"
+doctype[3].idx 10018
+doctype[3].internalid -244366130
+doctype[3].inherits[0].idx 10000
+doctype[3].contentstruct 10023
+doctype[3].fieldsets{[document]}.fields[0] "grandparent_ref"
+doctype[3].fieldsets{[document]}.fields[1] "int_array_field"
+doctype[3].fieldsets{[document]}.fields[2] "int_field"
+doctype[3].fieldsets{[document]}.fields[3] "int_wset_field"
+doctype[3].importedfield[0].name "ancient_int_field"
+doctype[3].arraytype[0].idx 10025
+doctype[3].arraytype[0].elementtype 10007
+doctype[3].arraytype[0].internalid -1245117006
+doctype[3].wsettype[0].idx 10026
+doctype[3].wsettype[0].elementtype 10007
+doctype[3].wsettype[0].createifnonexistent false
+doctype[3].wsettype[0].removeifzero false
+doctype[3].wsettype[0].internalid 519906144
+doctype[3].documentref[0].idx 10024
+doctype[3].documentref[0].targettype 10021
+doctype[3].documentref[0].internalid -1714181319
+doctype[3].structtype[0].idx 10023
+doctype[3].structtype[0].name "parent_a.header"
+doctype[3].structtype[0].field[0].name "grandparent_ref"
+doctype[3].structtype[0].field[0].internalid 29565679
+doctype[3].structtype[0].field[0].type 10024
+doctype[3].structtype[0].field[1].name "int_field"
+doctype[3].structtype[0].field[1].internalid 2128822283
+doctype[3].structtype[0].field[1].type 10007
+doctype[3].structtype[0].field[2].name "int_array_field"
+doctype[3].structtype[0].field[2].internalid 85807681
+doctype[3].structtype[0].field[2].type 10025
+doctype[3].structtype[0].field[3].name "int_wset_field"
+doctype[3].structtype[0].field[3].internalid 1945161474
+doctype[3].structtype[0].field[3].type 10026
+doctype[3].structtype[0].internalid 236742321
+doctype[4].name "parent_b"
+doctype[4].idx 10020
+doctype[4].internalid -244365169
+doctype[4].inherits[0].idx 10000
+doctype[4].contentstruct 10027
+doctype[4].fieldsets{[document]}.fields[0] "string_field"
+doctype[4].structtype[0].idx 10027
+doctype[4].structtype[0].name "parent_b.header"
+doctype[4].structtype[0].field[0].name "string_field"
+doctype[4].structtype[0].field[0].internalid 1222457840
+doctype[4].structtype[0].field[0].type 10012
+doctype[4].structtype[0].internalid 40228816
diff --git a/document/src/vespa/document/base/fieldpath.cpp b/document/src/vespa/document/base/fieldpath.cpp
index fcac59847cb..01855af55eb 100644
--- a/document/src/vespa/document/base/fieldpath.cpp
+++ b/document/src/vespa/document/base/fieldpath.cpp
@@ -11,7 +11,6 @@ using vespalib::make_string;
namespace document {
-FieldPathEntry::FieldPathEntry(const FieldPathEntry &) = default;
FieldPathEntry::~FieldPathEntry() = default;
FieldPathEntry::FieldPathEntry() :
@@ -46,7 +45,7 @@ FieldPathEntry::FieldPathEntry(const Field &fieldRef) :
_lookupIndex(0),
_lookupKey(),
_variableName(),
- _fillInVal(fieldRef.createValue().release())
+ _fillInVal(fieldRef.createValue())
{ }
FieldPathEntry::FieldPathEntry(const DataType & dataType, const DataType& fillType,
@@ -56,13 +55,24 @@ FieldPathEntry::FieldPathEntry(const DataType & dataType, const DataType& fillTy
_field(),
_dataType(&dataType),
_lookupIndex(0),
- _lookupKey(lookupKey.release()),
+ _lookupKey(std::move(lookupKey)),
_variableName(),
_fillInVal()
{
setFillValue(fillType);
}
+FieldPathEntry::FieldPathEntry(const FieldPathEntry &rhs)
+ : _type(rhs._type),
+ _name(rhs._name),
+ _field(rhs._field),
+ _dataType(rhs._dataType),
+ _lookupIndex(rhs._lookupIndex),
+ _lookupKey(rhs._lookupKey ? rhs._lookupKey->clone() : nullptr),
+ _variableName(rhs._variableName),
+ _fillInVal(rhs._fillInVal ? rhs._fillInVal->clone() : nullptr)
+{}
+
void
FieldPathEntry::setFillValue(const DataType & dataType)
{
@@ -82,7 +92,7 @@ FieldPathEntry::setFillValue(const DataType & dataType)
}
}
if (dt->isPrimitive()) {
- _fillInVal.reset(dt->createFieldValue().release());
+ _fillInVal = dt->createFieldValue();
}
}
@@ -123,7 +133,7 @@ FieldPathEntry::getDataType() const
FieldValue::UP
FieldPathEntry::stealFieldValueToSet() const
{
- return FieldValue::UP(_fillInVal.release());
+ return std::move(_fillInVal);
}
vespalib::string
@@ -171,17 +181,20 @@ FieldPathEntry::parseKey(vespalib::stringref & key)
return v;
}
-FieldPath::FieldPath()
- : _path()
-{ }
-
-FieldPath::FieldPath(const FieldPath &) = default;
-FieldPath & FieldPath::operator=(const FieldPath &) = default;
+FieldPath::FieldPath() = default;
FieldPath::~FieldPath() = default;
+FieldPath::FieldPath(const FieldPath & rhs)
+ : _path()
+{
+ _path.reserve(rhs.size());
+ for (const auto & e : rhs._path) {
+ _path.emplace_back(std::make_unique<FieldPathEntry>(*e));
+ }
+}
FieldPath::iterator
FieldPath::insert(iterator pos, std::unique_ptr<FieldPathEntry> entry) {
- return _path.insert(pos, vespalib::CloneablePtr<FieldPathEntry>(entry.release()));
+ return _path.insert(pos, std::move(entry));
}
void FieldPath::push_back(std::unique_ptr<FieldPathEntry> entry) { _path.emplace_back(entry.release()); }
void FieldPath::pop_back() { _path.pop_back(); }
diff --git a/document/src/vespa/document/base/fieldpath.h b/document/src/vespa/document/base/fieldpath.h
index a79e4595a61..95f916af118 100644
--- a/document/src/vespa/document/base/fieldpath.h
+++ b/document/src/vespa/document/base/fieldpath.h
@@ -2,7 +2,6 @@
#pragma once
#include "field.h"
-#include <vespa/vespalib/util/memory.h>
namespace document {
@@ -23,13 +22,12 @@ public:
VARIABLE,
NONE
};
- using FieldValueCP = vespalib::CloneablePtr<FieldValue>;
-
FieldPathEntry();
- FieldPathEntry(FieldPathEntry &&) = default;
- FieldPathEntry & operator=(FieldPathEntry &&) = default;
+ FieldPathEntry(FieldPathEntry &&) noexcept = default;
+ FieldPathEntry & operator=(FieldPathEntry &&) noexcept = default;
FieldPathEntry(const FieldPathEntry &);
+ FieldPathEntry & operator=(const FieldPathEntry &) = delete;
/**
Creates a field path entry for a struct field lookup.
@@ -58,8 +56,6 @@ public:
*/
FieldPathEntry(const DataType & dataType, vespalib::stringref variableName);
- FieldPathEntry * clone() const { return new FieldPathEntry(*this); }
-
Type getType() const { return _type; }
const vespalib::string & getName() const { return _name; }
@@ -70,7 +66,7 @@ public:
uint32_t getIndex() const { return _lookupIndex; }
- const FieldValueCP & getLookupKey() const { return _lookupKey; }
+ const FieldValue & getLookupKey() const { return *_lookupKey; }
const vespalib::string& getVariableName() const { return _variableName; }
@@ -85,20 +81,20 @@ public:
static vespalib::string parseKey(vespalib::stringref & key);
private:
void setFillValue(const DataType & dataType);
- Type _type;
- vespalib::string _name;
- Field _field;
- const DataType * _dataType;
- uint32_t _lookupIndex;
- FieldValueCP _lookupKey;
- vespalib::string _variableName;
- mutable FieldValueCP _fillInVal;
+ Type _type;
+ vespalib::string _name;
+ Field _field;
+ const DataType * _dataType;
+ uint32_t _lookupIndex;
+ std::unique_ptr<FieldValue> _lookupKey;
+ vespalib::string _variableName;
+ mutable std::unique_ptr<FieldValue> _fillInVal;
};
//typedef std::deque<FieldPathEntry> FieldPath;
// Facade over FieldPathEntry container that exposes cloneability
class FieldPath {
- typedef std::vector<vespalib::CloneablePtr<FieldPathEntry>> Container;
+ typedef std::vector<std::unique_ptr<FieldPathEntry>> Container;
public:
typedef Container::reference reference;
typedef Container::const_reference const_reference;
@@ -110,16 +106,11 @@ public:
FieldPath();
FieldPath(const FieldPath &);
- FieldPath & operator=(const FieldPath &);
+ FieldPath & operator=(const FieldPath &) = delete;
FieldPath(FieldPath &&) noexcept = default;
FieldPath & operator=(FieldPath &&) noexcept = default;
~FieldPath();
- template <typename InputIterator>
- FieldPath(InputIterator first, InputIterator last)
- : _path(first, last)
- { }
-
iterator insert(iterator pos, std::unique_ptr<FieldPathEntry> entry);
void push_back(std::unique_ptr<FieldPathEntry> entry);
diff --git a/document/src/vespa/document/fieldvalue/iteratorhandler.h b/document/src/vespa/document/fieldvalue/iteratorhandler.h
index bef013e1845..2f7b5d522e9 100644
--- a/document/src/vespa/document/fieldvalue/iteratorhandler.h
+++ b/document/src/vespa/document/fieldvalue/iteratorhandler.h
@@ -77,7 +77,7 @@ public:
void setArrayIndex(uint32_t index) { _arrayIndexStack.back() = index; }
ModificationStatus modify(FieldValue &fv) { return doModify(fv); }
fieldvalue::VariableMap &getVariables() { return _variables; }
- void setVariables(const fieldvalue::VariableMap &vars) { _variables = vars; }
+ void setVariables(fieldvalue::VariableMap vars) { _variables = std::move(vars); }
virtual bool createMissingPath() const { return false; }
private:
virtual bool onComplex(const Content &fv) {
diff --git a/document/src/vespa/document/fieldvalue/mapfieldvalue.cpp b/document/src/vespa/document/fieldvalue/mapfieldvalue.cpp
index 33ce4357f03..7385b261339 100644
--- a/document/src/vespa/document/fieldvalue/mapfieldvalue.cpp
+++ b/document/src/vespa/document/fieldvalue/mapfieldvalue.cpp
@@ -421,9 +421,9 @@ MapFieldValue::iterateNestedImpl(PathRange nested,
case FieldPathEntry::MAP_KEY:
{
LOG(spam, "MAP_KEY");
- const_iterator iter = find(*fpe.getLookupKey());
+ const_iterator iter = find(fpe.getLookupKey());
if (iter != end()) {
- wasModified = checkAndRemove(*fpe.getLookupKey(),
+ wasModified = checkAndRemove(fpe.getLookupKey(),
iter->second->iterateNested(nested.next(), handler),
wasModified, keysToRemove);
} else if (handler.createMissingPath()) {
@@ -431,7 +431,7 @@ MapFieldValue::iterateNestedImpl(PathRange nested,
FieldValue::UP val = getMapType().getValueType().createFieldValue();
ModificationStatus status = val->iterateNested(nested.next(), handler);
if (status == ModificationStatus::MODIFIED) {
- const_cast<MapFieldValue&>(*this).put(FieldValue::UP(fpe.getLookupKey()->clone()), std::move(val));
+ const_cast<MapFieldValue&>(*this).put(FieldValue::UP(fpe.getLookupKey().clone()), std::move(val));
return status;
}
}
diff --git a/document/src/vespa/document/fieldvalue/variablemap.cpp b/document/src/vespa/document/fieldvalue/variablemap.cpp
index 481e5288a24..1ad4e1b716b 100644
--- a/document/src/vespa/document/fieldvalue/variablemap.cpp
+++ b/document/src/vespa/document/fieldvalue/variablemap.cpp
@@ -11,8 +11,8 @@ IndexValue::IndexValue(int index_) : index(index_), key() {}
bool
IndexValue::operator==(const IndexValue& other) const {
- if (key.get() != NULL) {
- if (other.key.get() != NULL && *key == *other.key) {
+ if (key) {
+ if (other.key && *key == *other.key) {
return true;
}
return false;
@@ -22,28 +22,32 @@ IndexValue::operator==(const IndexValue& other) const {
}
IndexValue::IndexValue(const FieldValue& key_)
- : index(-1),
- key(FieldValue::CP(key_.clone()))
+ : index(-1),
+ key(key_.clone())
{ }
+IndexValue::IndexValue(IndexValue && rhs) noexcept = default;
+IndexValue & IndexValue::operator = (IndexValue && rhs) noexcept = default;
IndexValue::IndexValue(const IndexValue & rhs) = default;
IndexValue & IndexValue::operator = (const IndexValue & rhs) = default;
-IndexValue::~IndexValue() { }
+IndexValue::~IndexValue() = default;
vespalib::string
IndexValue::toString() const {
- if (key.get() != NULL) {
+ if (key) {
return key->toString();
} else {
return vespalib::make_string("%d", index);
}
}
-VariableMap::VariableMap() {}
-VariableMap::~VariableMap() {}
+VariableMap::VariableMap(VariableMap && rhs) noexcept = default;
+VariableMap & VariableMap::operator = (VariableMap && rhs) noexcept = default;
VariableMap::VariableMap(const VariableMap & rhs) = default;
VariableMap & VariableMap::operator = (const VariableMap & rhs) = default;
+VariableMap::VariableMap() = default;
+VariableMap::~VariableMap() = default;
vespalib::string
VariableMap::toString() const {
diff --git a/document/src/vespa/document/fieldvalue/variablemap.h b/document/src/vespa/document/fieldvalue/variablemap.h
index 70dba1551ef..68b8bcf09e8 100644
--- a/document/src/vespa/document/fieldvalue/variablemap.h
+++ b/document/src/vespa/document/fieldvalue/variablemap.h
@@ -17,8 +17,8 @@ public:
IndexValue();
IndexValue(int index_);
IndexValue(const FieldValue& key_);
- IndexValue(IndexValue && rhs) = default;
- IndexValue & operator = (IndexValue && rhs) = default;
+ IndexValue(IndexValue && rhs) noexcept;
+ IndexValue & operator = (IndexValue && rhs) noexcept;
IndexValue(const IndexValue & rhs);
IndexValue & operator = (const IndexValue & rhs);
@@ -36,8 +36,8 @@ using VariableMapT = std::map<vespalib::string, IndexValue>;
class VariableMap : public VariableMapT {
public:
VariableMap();
- VariableMap(VariableMap && rhs) = default;
- VariableMap & operator = (VariableMap && rhs) = default;
+ VariableMap(VariableMap && rhs) noexcept;
+ VariableMap & operator = (VariableMap && rhs) noexcept;
VariableMap(const VariableMap & rhs);
VariableMap & operator = (const VariableMap & rhs);
~VariableMap();
diff --git a/document/src/vespa/document/repo/documenttyperepo.cpp b/document/src/vespa/document/repo/documenttyperepo.cpp
index d8f272d5d55..147e79bbf32 100644
--- a/document/src/vespa/document/repo/documenttyperepo.cpp
+++ b/document/src/vespa/document/repo/documenttyperepo.cpp
@@ -37,12 +37,20 @@ namespace document {
namespace internal {
-using DocumentTypeMapT = vespalib::hash_map<int32_t, DataTypeRepo *>;
+using DocumentTypeMapT = std::map<int32_t, std::unique_ptr<DataTypeRepo>>;
class DocumentTypeMap : public DocumentTypeMapT
{
public:
using DocumentTypeMapT::DocumentTypeMapT;
+ DataTypeRepo * findRepo(int32_t doc_type_id) const {
+ auto iter = find(doc_type_id);
+ if (iter == end()) {
+ return nullptr;
+ } else {
+ return iter->second.get();
+ }
+ }
};
}
@@ -50,28 +58,16 @@ public:
using DocumentTypeMap = internal::DocumentTypeMap;
namespace {
-template <typename Container>
-void DeleteContent(Container &c) {
- for (auto ptr : c) {
- delete ptr;
- }
-}
-template <typename Map>
-void DeleteMapContent(Map &m) {
- for (auto & entry : m) {
- delete entry.second;
- }
-}
// A collection of data types.
class Repo {
- vector<const DataType *> _owned_types;
- hash_map<int32_t, const DataType *> _types;
+ vector<std::unique_ptr<const DataType>> _owned_types;
+ hash_map<int32_t, const DataType *> _id_map;
hash_map<string, const DataType *> _tensorTypes;
hash_map<string, const DataType *> _name_map;
public:
- ~Repo() { DeleteContent(_owned_types); }
+ ~Repo() {}
void inherit(const Repo &parent);
bool addDataType(const DataType &type);
@@ -85,14 +81,14 @@ public:
};
void Repo::inherit(const Repo &parent) {
- _types.insert(parent._types.begin(), parent._types.end());
+ _id_map.insert(parent._id_map.begin(), parent._id_map.end());
_tensorTypes.insert(parent._tensorTypes.begin(), parent._tensorTypes.end());
_name_map.insert(parent._name_map.begin(), parent._name_map.end());
}
// Returns true if a reference to type is stored.
bool Repo::addDataType(const DataType &type) {
- const DataType *& data_type = _types[type.getId()];
+ const DataType *& data_type = _id_map[type.getId()];
if (data_type) {
if (data_type->equals(type) && (data_type->getName() == type.getName())) {
return false; // Redefinition of identical type is ok.
@@ -117,9 +113,9 @@ template <typename T>
const DataType* Repo::addDataType(unique_ptr<T> type) {
int id = type->getId();
if (addDataType(*type)) {
- _owned_types.push_back(type.release());
+ _owned_types.emplace_back(std::move(type));
}
- return _types[id];
+ return _id_map[id];
}
@@ -127,28 +123,21 @@ const DataType &
Repo::addTensorType(const string &spec)
{
auto type = TensorDataType::fromSpec(spec);
- auto insres = _tensorTypes.insert(std::make_pair(spec, type.get()));
- if (insres.second) {
- _owned_types.push_back(type.release());
+ auto [ iter, inserted ] = _tensorTypes.insert(std::make_pair(spec, type.get()));
+ if (inserted) {
+ _owned_types.emplace_back(std::move(type));
}
- return *insres.first->second;
-}
-
-template <typename Map>
-typename Map::mapped_type FindPtr(const Map &m, typename Map::key_type key) {
- typename Map::const_iterator it = m.find(key);
- if (it != m.end()) {
- return it->second;
- }
- return typename Map::mapped_type();
+ return *iter->second;
}
const DataType *Repo::lookup(int32_t id) const {
- return FindPtr(_types, id);
+ auto iter = _id_map.find(id);
+ return (iter == _id_map.end()) ? nullptr : iter->second;
}
const DataType *Repo::lookup(stringref n) const {
- return FindPtr(_name_map, n);
+ auto iter = _name_map.find(n);
+ return (iter == _name_map.end()) ? nullptr : iter->second;
}
const DataType &Repo::findOrThrow(int32_t id) const {
@@ -169,11 +158,11 @@ Repo::findOrThrowOrCreate(int32_t id, const string &detailedType)
}
class AnnotationTypeRepo {
- vector<const AnnotationType *> _owned_types;
+ vector<std::unique_ptr<const AnnotationType>> _owned_types;
hash_map<int32_t, AnnotationType *> _annotation_types;
public:
- ~AnnotationTypeRepo() { DeleteContent(_owned_types); }
+ ~AnnotationTypeRepo() {}
void inherit(const AnnotationTypeRepo &parent);
AnnotationType * addAnnotationType(AnnotationType::UP annotation_type);
@@ -196,7 +185,7 @@ AnnotationType * AnnotationTypeRepo::addAnnotationType(AnnotationType::UP type)
}
} else {
a_type = type.get();
- _owned_types.push_back(type.release());
+ _owned_types.emplace_back(std::move(type));
}
return a_type;
}
@@ -215,7 +204,8 @@ void AnnotationTypeRepo::setAnnotationDataType(int32_t id, const DataType &d) {
}
const AnnotationType *AnnotationTypeRepo::lookup(int32_t id) const {
- return FindPtr(_annotation_types, id);
+ auto iter = _annotation_types.find(id);
+ return (iter == _annotation_types.end()) ? nullptr : iter->second;
}
} // namespace
@@ -225,12 +215,14 @@ const AnnotationType *AnnotationTypeRepo::lookup(int32_t id) const {
struct DataTypeRepo {
typedef unique_ptr<DataTypeRepo> UP;
- DocumentType *doc_type;
+ std::unique_ptr<DocumentType> doc_type;
Repo repo;
AnnotationTypeRepo annotations;
- DataTypeRepo() : doc_type(nullptr) {}
- ~DataTypeRepo() { delete doc_type; }
+ DataTypeRepo() : doc_type() {}
+ ~DataTypeRepo() {}
+
+ DocumentType * doc() const { return doc_type.get(); }
};
namespace {
@@ -363,13 +355,15 @@ void addDataTypes(const vector<Datatype> &types, Repo &repo, const AnnotationTyp
}
void addDocumentTypes(const DocumentTypeMap &type_map, Repo &repo) {
- for (const auto & entry : type_map) {
- repo.addDataType(*entry.second->doc_type);
+ for (const auto & [ key, data_type_repo ] : type_map) {
+ repo.addDataType(*data_type_repo->doc());
}
}
const DocumentType *
addDefaultDocument(DocumentTypeMap &type_map) {
+ const uint32_t typeId = DataType::T_DOCUMENT;
+
auto data_types = std::make_unique<DataTypeRepo>();
vector<const DataType *> default_types = DataType::getDefaultDataTypes();
for (size_t i = 0; i < default_types.size(); ++i) {
@@ -377,25 +371,23 @@ addDefaultDocument(DocumentTypeMap &type_map) {
}
data_types->repo.addDataType(UrlDataType::getInstance());
data_types->repo.addDataType(PositionDataType::getInstance());
- data_types->doc_type = new DocumentType("document", 8);
+ data_types->doc_type = std::make_unique<DocumentType>("document", typeId);
vector<const AnnotationType *> annotation_types(AnnotationType::getDefaultAnnotationTypes());
for(size_t i(0); i < annotation_types.size(); ++i) {
data_types->annotations.addAnnotationType(std::make_unique<AnnotationType>(*annotation_types[i]));
}
-
- uint32_t typeId = data_types->doc_type->getId();
- const DocumentType * docType = data_types->doc_type;
- type_map[typeId] = data_types.release();
+ const DocumentType * docType = data_types->doc();
+ type_map[typeId] = std::move(data_types);
return docType;
}
const DataTypeRepo &lookupRepo(int32_t id, const DocumentTypeMap &type_map) {
- DocumentTypeMap::const_iterator it = type_map.find(id);
- if (it == type_map.end()) {
+ if (const auto * p = type_map.findRepo(id)) {
+ return *p;
+ } else {
throw IllegalArgumentException(make_string("Unable to find document type %d.", id));
}
- return *it->second;
}
void inheritDataTypes(const vector<DocumenttypesConfig::Documenttype::Inherits> &base_types,
@@ -427,7 +419,7 @@ makeDataTypeRepo(const DocumentType &doc_type, const DocumentTypeMap &type_map)
auto data_types = std::make_unique<DataTypeRepo>();
data_types->repo.inherit(lookupRepo(DataType::T_DOCUMENT, type_map).repo);
data_types->annotations.inherit(lookupRepo(DataType::T_DOCUMENT, type_map).annotations);
- data_types->doc_type = new DocumentType(doc_type);
+ data_types->doc_type = std::make_unique<DocumentType>(doc_type);
return data_types;
}
@@ -446,8 +438,8 @@ void addReferenceTypes(const vector<DocumenttypesConfig::Documenttype::Reference
Repo& data_type_repo, const DocumentTypeMap& doc_type_map)
{
for (const auto& ref_type : ref_types) {
- const auto* target_doc_type = lookupRepo(ref_type.targetTypeId, doc_type_map).doc_type;
- data_type_repo.addDataType(std::make_unique<ReferenceDataType>(*target_doc_type, ref_type.id));
+ const auto & repo = lookupRepo(ref_type.targetTypeId, doc_type_map);
+ data_type_repo.addDataType(std::make_unique<ReferenceDataType>(*repo.doc_type, ref_type.id));
}
}
@@ -460,31 +452,31 @@ void add_imported_fields(const DocumenttypesConfig::Documenttype::ImportedfieldV
}
void configureDataTypeRepo(const DocumenttypesConfig::Documenttype &doc_type, DocumentTypeMap &type_map) {
- DataTypeRepo *data_types = type_map[doc_type.id];
+ const auto & data_types = type_map[doc_type.id];
inheritAnnotationTypes(doc_type.inherits, type_map, data_types->annotations);
addAnnotationTypes(doc_type.annotationtype, data_types->annotations);
inheritDataTypes(doc_type.inherits, type_map, data_types->repo);
addReferenceTypes(doc_type.referencetype, data_types->repo, type_map);
addDataTypes(doc_type.datatype, data_types->repo, data_types->annotations);
setAnnotationDataTypes(doc_type.annotationtype, data_types->annotations, data_types->repo);
- inheritDocumentTypes(doc_type.inherits, type_map, *data_types->doc_type);
- addFieldSet(doc_type.fieldsets, *data_types->doc_type);
- add_imported_fields(doc_type.importedfield, *data_types->doc_type);
+ inheritDocumentTypes(doc_type.inherits, type_map, *data_types->doc());
+ addFieldSet(doc_type.fieldsets, *data_types->doc());
+ add_imported_fields(doc_type.importedfield, *data_types->doc());
}
void addDataTypeRepo(DataTypeRepo::UP data_types, DocumentTypeMap &doc_types) {
- DataTypeRepo *& p = doc_types[data_types->doc_type->getId()];
+ auto & p = doc_types[data_types->doc()->getId()];
if (p) {
- LOG(warning, "Type repo already exists for id %d.", data_types->doc_type->getId());
+ LOG(warning, "Type repo already exists for id %d.", data_types->doc()->getId());
throw IllegalArgumentException("Trying to redefine a document type.");
}
- p = data_types.release();
+ p = std::move(data_types);
}
DataTypeRepo::UP makeSkeletonDataTypeRepo(const DocumenttypesConfig::Documenttype &type) {
auto data_types = std::make_unique<DataTypeRepo>();
auto type_ap = std::make_unique<StructDataType>(type.name + ".header", type.headerstruct);
- data_types->doc_type = new DocumentType(type.name, type.id, *type_ap);
+ data_types->doc_type = std::make_unique<DocumentType>(type.name, type.id, *type_ap);
data_types->repo.addDataType(std::move(type_ap));
return data_types;
}
@@ -534,22 +526,23 @@ private:
struct DocTypeInProgress {
const CDocType & cfg;
- DataTypeRepo * data_type_repo;
- DocumentType * dtype = nullptr;
+ DataTypeRepo * data_type_repo = nullptr;
bool builtin = false;
DocTypeInProgress(const CDocType & config, DocumentTypeMap &doc_types)
- : cfg(config),
- data_type_repo(doc_types[cfg.internalid])
+ : cfg(config)
{
- if (data_type_repo) {
+ auto iter = doc_types.find(cfg.internalid);
+ if (iter != doc_types.end()) {
LOG(debug, "old doct : %s [%d]", cfg.name.c_str(), cfg.internalid);
builtin = true;
} else {
LOG(debug, "new doct : %s [%d]", cfg.name.c_str(), cfg.internalid);
- data_type_repo = new DataTypeRepo();
- doc_types[cfg.internalid] = data_type_repo;
+ doc_types.emplace(cfg.internalid, std::make_unique<DataTypeRepo>());
}
+ iter = doc_types.find(cfg.internalid);
+ LOG_ASSERT(iter != doc_types.end());
+ data_type_repo = iter->second.get();
}
Repo& repo() { return data_type_repo->repo; }
@@ -587,14 +580,13 @@ private:
createEmptyStructs(dtInP);
initializeDocTypeAndInheritAnnotations(dtInP);
createEmptyAnnotationTypes(dtInP);
+ }
+ for (auto & [id, dtInP] : _doc_types_in_progress) {
createReferenceTypes(dtInP);
}
createComplexTypes();
fillStructs();
- for (const CDocType & docT : _input) {
- auto iter = _doc_types_in_progress.find(docT.idx);
- LOG_ASSERT(iter != _doc_types_in_progress.end());
- auto & dtInP = iter->second;
+ for (auto & [id, dtInP] : _doc_types_in_progress) {
fillDocument(dtInP);
fillAnnotationTypes(dtInP);
}
@@ -674,15 +666,15 @@ private:
void initializeDocTypeAndInheritAnnotations(DocTypeInProgress & dtInP) {
if (dtInP.builtin) {
- madeType(dtInP.data_type_repo->doc_type, dtInP.cfg.idx);
+ madeType(dtInP.data_type_repo->doc(), dtInP.cfg.idx);
return;
}
- LOG_ASSERT(dtInP.data_type_repo->doc_type == nullptr);
+ LOG_ASSERT(dtInP.data_type_repo->doc() == nullptr);
const auto & docT = dtInP.cfg;
const StructDataType * fields = findStruct(docT.contentstruct);
if (fields != nullptr) {
- dtInP.data_type_repo->doc_type = new DocumentType(docT.name, docT.internalid, *fields);
- madeType(dtInP.data_type_repo->doc_type, docT.idx);
+ dtInP.data_type_repo->doc_type = std::make_unique<DocumentType>(docT.name, docT.internalid, *fields);
+ madeType(dtInP.data_type_repo->doc(), docT.idx);
} else {
LOG(error, "Missing content struct for '%s' (idx %d not found)",
docT.name.c_str(), docT.contentstruct);
@@ -696,7 +688,7 @@ private:
inheritD.idx, docT.name.c_str());
throw IllegalArgumentException("Unable to find document for inheritance");
}
- DataTypeRepo * parentRepo = FindPtr(_output, dt->getId());
+ const DataTypeRepo *parentRepo = _output.findRepo(dt->getId());
if (parentRepo == nullptr) {
LOG(error, "parent repo [id %d] missing for document %s",
dt->getId(), docT.name.c_str());
@@ -822,7 +814,7 @@ private:
return;
}
const CDocType & docT = dtInP.cfg;
- auto * doc_type = dtInP.data_type_repo->doc_type;
+ auto * doc_type = dtInP.data_type_repo->doc();
LOG_ASSERT(doc_type != nullptr);
for (const auto & importD : docT.importedfield) {
doc_type->add_imported_field_name(importD.name);
@@ -1011,7 +1003,6 @@ DocumentTypeRepo::DocumentTypeRepo(const DocumentType & type) :
try {
addDataTypeRepo(makeDataTypeRepo(type, *_doc_types), *_doc_types);
} catch (...) {
- DeleteMapContent(*_doc_types);
throw;
}
}
@@ -1029,31 +1020,32 @@ DocumentTypeRepo::DocumentTypeRepo(const DocumenttypesConfig &config) :
configureAllRepos(config.documenttype, *_doc_types);
}
} catch (...) {
- DeleteMapContent(*_doc_types);
throw;
}
}
DocumentTypeRepo::~DocumentTypeRepo() {
- DeleteMapContent(*_doc_types);
+}
+
+DataTypeRepo *DocumentTypeRepo::findRepo(int32_t doc_type_id) const {
+ return _doc_types->findRepo(doc_type_id);
}
const DocumentType *
DocumentTypeRepo::getDocumentType(int32_t type_id) const noexcept {
- const DataTypeRepo *repo = FindPtr(*_doc_types, type_id);
- return repo ? repo->doc_type : nullptr;
+ const DataTypeRepo *repo = findRepo(type_id);
+ return repo ? repo->doc() : nullptr;
}
const DocumentType *
DocumentTypeRepo::getDocumentType(stringref name) const noexcept {
- DocumentTypeMap::const_iterator it = _doc_types->find(DocumentType::createId(name));
-
- if (it != _doc_types->end() && it->second->doc_type->getName() == name) {
- return it->second->doc_type;
+ const auto * rp = findRepo(DocumentType::createId(name));
+ if (rp && rp->doc()->getName() == name) {
+ return rp->doc();
}
- for (it = _doc_types->begin(); it != _doc_types->end(); ++it) {
- if (it->second->doc_type->getName() == name) {
- return it->second->doc_type;
+ for (const auto & [ id, p ] : *_doc_types) {
+ if (p->doc()->getName() == name) {
+ return p->doc();
}
}
return nullptr;
@@ -1061,26 +1053,26 @@ DocumentTypeRepo::getDocumentType(stringref name) const noexcept {
const DataType *
DocumentTypeRepo::getDataType(const DocumentType &doc_type, int32_t id) const {
- const DataTypeRepo *dt_repo = FindPtr(*_doc_types, doc_type.getId());
+ const DataTypeRepo *dt_repo = findRepo(doc_type.getId());
return dt_repo ? dt_repo->repo.lookup(id) : nullptr;
}
const DataType *
DocumentTypeRepo::getDataType(const DocumentType &doc_type, stringref name) const {
- const DataTypeRepo *dt_repo = FindPtr(*_doc_types, doc_type.getId());
+ const DataTypeRepo *dt_repo = findRepo(doc_type.getId());
return dt_repo ? dt_repo->repo.lookup(name) : nullptr;
}
const AnnotationType *
DocumentTypeRepo::getAnnotationType(const DocumentType &doc_type, int32_t id) const {
- const DataTypeRepo *dt_repo = FindPtr(*_doc_types, doc_type.getId());
+ const DataTypeRepo *dt_repo = findRepo(doc_type.getId());
return dt_repo ? dt_repo->annotations.lookup(id) : nullptr;
}
void
DocumentTypeRepo::forEachDocumentType(std::function<void(const DocumentType &)> handler) const {
- for (const auto & entry : *_doc_types) {
- handler(*entry.second->doc_type);
+ for (const auto & [ it, rp ] : *_doc_types) {
+ handler(*rp->doc());
}
}
diff --git a/document/src/vespa/document/repo/documenttyperepo.h b/document/src/vespa/document/repo/documenttyperepo.h
index 1a3898a1b65..78f7edd078f 100644
--- a/document/src/vespa/document/repo/documenttyperepo.h
+++ b/document/src/vespa/document/repo/documenttyperepo.h
@@ -37,9 +37,10 @@ public:
void forEachDocumentType(std::function<void(const DocumentType &)> handler) const;
const DocumentType *getDefaultDocType() const { return _default; }
private:
-
std::unique_ptr<internal::DocumentTypeMap> _doc_types;
const DocumentType * _default;
+
+ DataTypeRepo * findRepo(int32_t doc_type_id) const;
};
} // namespace document
diff --git a/document/src/vespa/document/select/branch.cpp b/document/src/vespa/document/select/branch.cpp
index bb08f0a5ba1..d8dc58edf20 100644
--- a/document/src/vespa/document/select/branch.cpp
+++ b/document/src/vespa/document/select/branch.cpp
@@ -12,8 +12,8 @@ And::And(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* na
_left(std::move(left)),
_right(std::move(right))
{
- assert(_left.get());
- assert(_right.get());
+ assert(_left);
+ assert(_right);
}
@@ -39,11 +39,9 @@ namespace {
ResultList traceAndValue(const T& value, std::ostream& out,
const Node& leftNode, const Node& rightNode)
{
- ResultList left = leftNode.contains(value);
- out << "And - Left branch returned " << left << ".\n";
- ResultList right = rightNode.contains(value);
- out << "And - Right branch returned " << right << ".\n";
- return left && right;
+ out << "And - Left branch returned " << leftNode.contains(value) << ".\n";
+ out << "And - Right branch returned " << rightNode.contains(value) << ".\n";
+ return leftNode.contains(value) && rightNode.contains(value);
}
}
@@ -85,11 +83,9 @@ namespace {
ResultList traceOrValue(const T& value, std::ostream& out,
const Node& leftNode, const Node& rightNode)
{
- ResultList left = leftNode.contains(value);
- out << "Or - Left branch returned " << left << ".\n";
- ResultList right = rightNode.contains(value);
- out << "Or - Right branch returned " << right << ".\n";
- return left || right;
+ out << "Or - Left branch returned " << leftNode.contains(value) << ".\n";
+ out << "Or - Right branch returned " << rightNode.contains(value) << ".\n";
+ return leftNode.contains(value) || rightNode.contains(value);
}
}
@@ -124,13 +120,11 @@ Not::print(std::ostream& out, bool verbose, const std::string& indent) const
namespace {
template<typename T>
- ResultList traceNotValue(const T& value, std::ostream& out,
- const Node& node)
+ ResultList traceNotValue(const T& value, std::ostream& out, const Node& node)
{
- ResultList result = node.contains(value);
- out << "Not - Child returned " << result
+ out << "Not - Child returned " << node.contains(value)
<< ". Returning opposite.\n";
- return !result;
+ return !node.contains(value);
}
}
diff --git a/document/src/vespa/document/select/compare.cpp b/document/src/vespa/document/select/compare.cpp
index 1d792041c4e..36ef3b7cdbe 100644
--- a/document/src/vespa/document/select/compare.cpp
+++ b/document/src/vespa/document/select/compare.cpp
@@ -54,12 +54,10 @@ namespace {
document::BucketId b( static_cast<IntegerValue&>(bVal).getValue());
document::BucketId s( static_cast<IntegerValue&>(nVal).getValue());
- ResultList resultList(Result::get(s.contains(b)));
-
if (op == FunctionOperator::NE) {
- return !resultList;
+ return ! ResultList(Result::get(s.contains(b)));
}
- return resultList;
+ return ResultList(Result::get(s.contains(b)));
} else {
return ResultList(Result::Invalid);
}
@@ -87,11 +85,9 @@ namespace {
document::BucketId s(
static_cast<IntegerValue&>(nVal).getValue());
- ResultList resultList(Result::get(s.contains(b)));
-
- if (op == FunctionOperator::NE) {
- resultList = !resultList;
- }
+ ResultList resultList = (op == FunctionOperator::NE)
+ ? !ResultList(Result::get(s.contains(b)))
+ : ResultList(Result::get(s.contains(b)));
out << "Checked if " << b.toString() << " is ";
if (op == FunctionOperator::NE) { out << "not "; }
diff --git a/document/src/vespa/document/select/resultlist.cpp b/document/src/vespa/document/select/resultlist.cpp
index e72eb36cbdb..5bb2f510e0d 100644
--- a/document/src/vespa/document/select/resultlist.cpp
+++ b/document/src/vespa/document/select/resultlist.cpp
@@ -7,7 +7,8 @@
namespace document::select {
ResultList::ResultList() = default;
-
+ResultList::ResultList(ResultList &&) noexcept = default;
+ResultList & ResultList::operator = (ResultList &&) noexcept = default;
ResultList::~ResultList() = default;
ResultList::ResultList(const Result& result) {
@@ -15,9 +16,9 @@ ResultList::ResultList(const Result& result) {
}
void
-ResultList::add(const fieldvalue::VariableMap& variables, const Result& result)
+ResultList::add(fieldvalue::VariableMap variables, const Result& result)
{
- _results.push_back(ResultPair(variables, &result));
+ _results.emplace_back(std::move(variables), &result);
}
void
@@ -109,7 +110,7 @@ ResultList::operator&&(const ResultList& other) const
if (vars.empty()) {
resultForNoVariables.set(result.toEnum());
} else {
- results.add(vars, result);
+ results.add(std::move(vars), result);
}
}
}
@@ -137,7 +138,7 @@ ResultList::operator||(const ResultList& other) const
if (vars.empty()) {
resultForNoVariables.set(result.toEnum());
} else {
- results.add(vars, result);
+ results.add(std::move(vars), result);
}
}
}
diff --git a/document/src/vespa/document/select/resultlist.h b/document/src/vespa/document/select/resultlist.h
index b59a4f1a904..3f810004168 100644
--- a/document/src/vespa/document/select/resultlist.h
+++ b/document/src/vespa/document/select/resultlist.h
@@ -17,8 +17,10 @@ public:
using const_reverse_iterator = Results::const_reverse_iterator;
ResultList();
- ResultList(ResultList &&) = default;
- ResultList & operator = (ResultList &&) = default;
+ ResultList(ResultList &&) noexcept;
+ ResultList & operator = (ResultList &&) noexcept;
+ ResultList(const ResultList &) = delete;
+ ResultList & operator = (const ResultList &) = delete;
~ResultList();
/**
@@ -26,7 +28,7 @@ public:
*/
explicit ResultList(const Result& result);
- void add(const VariableMap& variables, const Result& result);
+ void add(VariableMap variables, const Result& result);
ResultList operator&&(const ResultList& other) const;
ResultList operator||(const ResultList& other) const;
diff --git a/document/src/vespa/document/select/value.cpp b/document/src/vespa/document/select/value.cpp
index e0a2bf84f09..aabd09b2558 100644
--- a/document/src/vespa/document/select/value.cpp
+++ b/document/src/vespa/document/select/value.cpp
@@ -2,6 +2,7 @@
#include "value.h"
#include "operator.h"
+#include "variablemap.h"
#include <vespa/document/fieldvalue/fieldvalue.h>
#include <bitset>
#include <ostream>
@@ -45,8 +46,7 @@ InvalidValue::operator==(const Value&) const
}
void
-InvalidValue::print(std::ostream& out, bool verbose,
- const std::string& indent) const
+InvalidValue::print(std::ostream& out, bool verbose, const std::string& indent) const
{
(void) verbose; (void) indent;
out << "invalid";
@@ -123,8 +123,7 @@ StringValue::operator==(const Value& value) const
}
void
-StringValue::print(std::ostream& out, bool verbose,
- const std::string& indent) const
+StringValue::print(std::ostream& out, bool verbose, const std::string& indent) const
{
(void) verbose; (void) indent;
out << "\"" << _value << "\"";
@@ -156,8 +155,7 @@ IntegerValue::operator==(const Value& value) const
}
void
-IntegerValue::print(std::ostream& out, bool verbose,
- const std::string& indent) const
+IntegerValue::print(std::ostream& out, bool verbose, const std::string& indent) const
{
(void) verbose; (void) indent;
out << _value << 'i';
@@ -189,8 +187,7 @@ FloatValue::operator==(const Value& value) const
}
void
-FloatValue::print(std::ostream& out, bool verbose,
- const std::string& indent) const
+FloatValue::print(std::ostream& out, bool verbose, const std::string& indent) const
{
(void) verbose; (void) indent;
out << _value << 'f';
@@ -312,8 +309,7 @@ ArrayValue::regexTrace(const Value& value, std::ostream& trace) const
}
void
-ArrayValue::print(std::ostream& out, bool verbose,
- const std::string& indent) const
+ArrayValue::print(std::ostream& out, bool verbose, const std::string& indent) const
{
(void) verbose; (void) indent;
out << "<no array representation in language yet>";
diff --git a/document/src/vespa/document/select/valuenodes.cpp b/document/src/vespa/document/select/valuenodes.cpp
index cd66bac9635..c770974adfe 100644
--- a/document/src/vespa/document/select/valuenodes.cpp
+++ b/document/src/vespa/document/select/valuenodes.cpp
@@ -223,7 +223,7 @@ namespace {
class IteratorHandler : public fieldvalue::IteratorHandler {
public:
IteratorHandler();
- ~IteratorHandler();
+ ~IteratorHandler() override;
bool hasSingleValue() const;
std::unique_ptr<Value> stealSingleValue() &&;
std::vector<ArrayValue::VariableValue> stealValues() &&;
@@ -263,7 +263,7 @@ IteratorHandler::onPrimitive(uint32_t fid, const Content& fv) {
if (!_firstValue && getVariables().empty()) {
_firstValue = getInternalValue(fv.getValue());
} else {
- _values.emplace_back(getVariables(), Value::SP(getInternalValue(fv.getValue()).release()));
+ _values.emplace_back(std::move(getVariables()), Value::SP(getInternalValue(fv.getValue()).release()));
}
}
diff --git a/document/src/vespa/document/update/fieldpathupdate.cpp b/document/src/vespa/document/update/fieldpathupdate.cpp
index 9267ff62e1b..5b47b48dd57 100644
--- a/document/src/vespa/document/update/fieldpathupdate.cpp
+++ b/document/src/vespa/document/update/fieldpathupdate.cpp
@@ -74,10 +74,10 @@ FieldPathUpdate::applyTo(Document& doc) const
} else {
std::unique_ptr<select::Node> whereClause = parseDocumentSelection(_originalWhereClause, *doc.getRepo());
select::ResultList results = whereClause->contains(doc);
- for (select::ResultList::const_reverse_iterator i = results.rbegin(); i != results.rend(); ++i) {
+ for (auto i = results.rbegin(); i != results.rend(); ++i) {
LOG(spam, "vars = %s", handler->getVariables().toString().c_str());
if (*i->second == select::Result::True) {
- handler->setVariables(i->first);
+ handler->setVariables(std::move(i->first));
doc.iterateNested(path, *handler);
}
}
diff --git a/documentapi/abi-spec.json b/documentapi/abi-spec.json
index 2e68d4803cb..1a2afa3621f 100644
--- a/documentapi/abi-spec.json
+++ b/documentapi/abi-spec.json
@@ -1844,6 +1844,7 @@
"public static com.yahoo.documentapi.messagebus.protocol.DocumentProtocol$Priority getPriorityByName(java.lang.String)",
"public void <init>(com.yahoo.document.DocumentTypeManager)",
"public void <init>(com.yahoo.document.DocumentTypeManager, java.lang.String)",
+ "public void <init>(com.yahoo.document.DocumentTypeManager, com.yahoo.documentapi.messagebus.protocol.DocumentProtocolPoliciesConfig, com.yahoo.vespa.config.content.DistributionConfig)",
"public void <init>(com.yahoo.document.DocumentTypeManager, com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet, com.yahoo.documentapi.messagebus.protocol.DocumentProtocolPoliciesConfig, com.yahoo.vespa.config.content.DistributionConfig)",
"public void <init>(com.yahoo.document.DocumentTypeManager, java.lang.String, com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet)",
"public com.yahoo.documentapi.messagebus.protocol.DocumentProtocol putRoutingPolicyFactory(java.lang.String, com.yahoo.documentapi.messagebus.protocol.RoutingPolicyFactory)",
@@ -3123,7 +3124,8 @@
],
"methods": [
"public abstract boolean encode(com.yahoo.messagebus.Routable, com.yahoo.document.serialization.DocumentSerializer)",
- "public abstract com.yahoo.messagebus.Routable decode(com.yahoo.document.serialization.DocumentDeserializer, com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet)"
+ "public abstract com.yahoo.messagebus.Routable decode(com.yahoo.document.serialization.DocumentDeserializer, com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet)",
+ "public com.yahoo.messagebus.Routable decode(com.yahoo.document.serialization.DocumentDeserializer)"
],
"fields": []
},
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/VisitorParameters.java b/documentapi/src/main/java/com/yahoo/documentapi/VisitorParameters.java
index a9dfe0128e0..7446c681dec 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/VisitorParameters.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/VisitorParameters.java
@@ -20,6 +20,7 @@ import java.util.TreeMap;
*
* @author HÃ¥kon Humberset
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class VisitorParameters extends Parameters {
private String documentSelection;
@@ -47,7 +48,7 @@ public class VisitorParameters extends Parameters {
private int maxBucketsPerVisitor = 1;
private boolean dynamicallyIncreaseMaxBucketsPerVisitor = false;
private float dynamicMaxBucketsIncreaseFactor = 2;
- private LoadType loadType = LoadType.DEFAULT;
+ private LoadType loadType = LoadType.DEFAULT; // TODO: Remove on Vespa 8
private DocumentProtocol.Priority priority = null;
private int traceLevel = 0;
private ThrottlePolicy throttlePolicy = null;
@@ -332,10 +333,18 @@ public class VisitorParameters extends Parameters {
throttlePolicy = policy;
}
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public void setLoadType(LoadType loadType) {
this.loadType = loadType;
}
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public LoadType getLoadType() {
return loadType;
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusDocumentAccess.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusDocumentAccess.java
index de09049b49e..c2e6dde7f60 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusDocumentAccess.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusDocumentAccess.java
@@ -53,6 +53,7 @@ public class MessageBusDocumentAccess extends DocumentAccess {
*
* @param params All parameters for construction.
*/
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public MessageBusDocumentAccess(MessageBusParams params) {
super(params);
this.params = params;
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusParams.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusParams.java
index 628bca4098f..bb8c3a3b1b1 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusParams.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusParams.java
@@ -14,6 +14,7 @@ import static java.util.Objects.requireNonNull;
/**
* @author Einar M R Rosenvinge
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class MessageBusParams extends DocumentAccessParams {
private String routingConfigId = null;
@@ -26,12 +27,16 @@ public class MessageBusParams extends DocumentAccessParams {
private RPCNetworkParams rpcNetworkParams = new RPCNetworkParams();
private com.yahoo.messagebus.MessageBusParams mbusParams = new com.yahoo.messagebus.MessageBusParams();
private SourceSessionParams sourceSessionParams = new SourceSessionParams();
- private LoadTypeSet loadTypes;
+ private LoadTypeSet loadTypes; // TODO remove on Vespa 8
public MessageBusParams() {
this(new LoadTypeSet());
}
+ /**
+ * @deprecated load types are deprecated. Use default constructor instead
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public MessageBusParams(LoadTypeSet loadTypes) {
this.loadTypes = loadTypes;
}
@@ -39,7 +44,9 @@ public class MessageBusParams extends DocumentAccessParams {
/**
*
* @return Returns the set of load types accepted by this Vespa installation
+ * @deprecated load types are deprecated
*/
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public LoadTypeSet getLoadTypes() {
return loadTypes;
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadType.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadType.java
index 53c09dcbcb6..133736a8542 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadType.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadType.java
@@ -4,8 +4,10 @@ package com.yahoo.documentapi.messagebus.loadtypes;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
/**
+ * @deprecated load types are deprecated
* @author thomasg
*/
+@Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public class LoadType {
private int id;
private String name;
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadTypeSet.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadTypeSet.java
index e28d760eddf..a3fbed472f0 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadTypeSet.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/loadtypes/LoadTypeSet.java
@@ -20,9 +20,15 @@ import java.util.TreeMap;
*
* For testing, you may want to use the empty constructor and add
* load types yourself with addType().
+ *
+ * @deprecated load types are deprecated
*/
+@Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class LoadTypeSet {
+ public static final LoadTypeSet EMPTY = new LoadTypeSet();
+
class DualMap {
Map<String, LoadType> nameMap = new TreeMap<String, LoadType>();
Map<Integer, LoadType> idMap = new HashMap<Integer, LoadType>();
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentMessage.java
index d1e3b61f998..21f7c243c6f 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentMessage.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentMessage.java
@@ -9,10 +9,11 @@ import com.yahoo.text.Utf8String;
/**
* @author Simon Thoresen Hult
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public abstract class DocumentMessage extends Message {
private DocumentProtocol.Priority priority = DocumentProtocol.Priority.NORMAL_3;
- private LoadType loadType = LoadType.DEFAULT;
+ private LoadType loadType = LoadType.DEFAULT; // TODO: Remove on Vespa 8
/**
* Constructs a new message with no content.
@@ -65,10 +66,20 @@ public abstract class DocumentMessage extends Message {
this.priority = priority;
}
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public LoadType getLoadType() {
return loadType;
}
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public void setLoadType(LoadType loadType) {
if (loadType != null) {
this.loadType = loadType;
@@ -79,7 +90,7 @@ public abstract class DocumentMessage extends Message {
@Override
public int getApproxSize() {
- return 4 + 1; // type + priority
+ return 4 + 1; // type + priority // TODO update on Vespa 8 to not include deprecated fields
}
@Override
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
index ac946b80429..5db426a5db4 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
@@ -32,6 +32,7 @@ import static java.util.Objects.requireNonNull;
*
* @author Simon Thoresen Hult
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class DocumentProtocol implements Protocol {
private static final Logger log = Logger.getLogger(DocumentProtocol.class.getName());
@@ -246,12 +247,27 @@ public class DocumentProtocol implements Protocol {
this(docMan, configId, new LoadTypeSet());
}
+ public DocumentProtocol(DocumentTypeManager documentTypeManager,
+ DocumentProtocolPoliciesConfig policiesConfig,
+ DistributionConfig distributionConfig) {
+ this(requireNonNull(documentTypeManager), null, new LoadTypeSet(),
+ requireNonNull(policiesConfig), requireNonNull(distributionConfig));
+ }
+
+ /**
+ * @deprecated load types are deprecated. Use constructor without LoadTypeSet instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public DocumentProtocol(DocumentTypeManager documentTypeManager, LoadTypeSet loadTypes,
DocumentProtocolPoliciesConfig policiesConfig, DistributionConfig distributionConfig) {
this(requireNonNull(documentTypeManager), null, requireNonNull(loadTypes),
- requireNonNull(policiesConfig), requireNonNull(distributionConfig));
+ requireNonNull(policiesConfig), requireNonNull(distributionConfig));
}
+ /**
+ * @deprecated load types are deprecated. Use constructor without LoadTypeSet instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public DocumentProtocol(DocumentTypeManager docMan, String configId, LoadTypeSet set) {
this(docMan, configId == null ? "client" : configId, set, null, null);
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
index 60c8a613bb5..42172b04b90 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
@@ -6,22 +6,17 @@ import com.yahoo.document.Document;
import com.yahoo.document.DocumentId;
import com.yahoo.document.DocumentPut;
import com.yahoo.document.DocumentUpdate;
-import com.yahoo.document.FixedBucketSpaces;
import com.yahoo.document.TestAndSetCondition;
import com.yahoo.document.serialization.DocumentDeserializer;
import com.yahoo.document.serialization.DocumentSerializer;
-import com.yahoo.document.serialization.DocumentSerializerFactory;
import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
-import java.util.logging.Level;
import com.yahoo.messagebus.Routable;
import com.yahoo.vdslib.DocumentSummary;
import com.yahoo.vdslib.SearchResult;
import com.yahoo.vdslib.VisitorStatistics;
import com.yahoo.vespa.objects.Deserializer;
-import com.yahoo.vespa.objects.Serializer;
import java.util.Map;
-import java.util.logging.Logger;
import static com.yahoo.documentapi.messagebus.protocol.AbstractRoutableFactory.decodeString;
import static com.yahoo.documentapi.messagebus.protocol.AbstractRoutableFactory.encodeString;
@@ -86,7 +81,7 @@ public abstract class RoutableFactories60 {
DocumentMessage msg = doDecode(in);
if (msg != null) {
msg.setPriority(DocumentProtocol.getPriority(pri));
- msg.setLoadType(loadTypes.getIdMap().get(loadType));
+ msg.setLoadType(loadTypes.getIdMap().get(loadType)); // TODO: ignore on Vespa 8
}
return msg;
}
@@ -136,6 +131,7 @@ public abstract class RoutableFactories60 {
return doEncode(reply, out);
}
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public Routable decode(DocumentDeserializer in, LoadTypeSet loadTypes) {
byte pri = in.getByte(null);
DocumentReply reply = doDecode(in);
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java
index d38671fa313..7baa41e5c6a 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactory.java
@@ -22,7 +22,7 @@ public interface RoutableFactory {
/**
* <p>This method encodes the content of the given routable into a byte buffer that can later be decoded using the
- * {@link #decode(DocumentDeserializer, com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet)} method.</p> <p>Return false to signal failure.</p>
+ * {@link #decode(DocumentDeserializer)} method.</p> <p>Return false to signal failure.</p>
* <p>This method is NOT exception safe.</p>
*
* @param obj The routable to encode.
@@ -38,7 +38,15 @@ public interface RoutableFactory {
* @param in The buffer to read from.
* @param loadTypes The LoadTypeSet to inject into the Routable.
* @return The decoded routable.
+ * @deprecated load types are deprecated. Use method without LoadTypeSet instead
*/
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
Routable decode(DocumentDeserializer in, LoadTypeSet loadTypes);
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
+ default Routable decode(DocumentDeserializer in) {
+ return decode(in, LoadTypeSet.EMPTY);
+ }
+
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java
index 24677a9a322..2360cbe8bc3 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableRepository.java
@@ -28,13 +28,21 @@ import java.util.logging.Logger;
*
* @author Simon Thoresen Hult
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
final class RoutableRepository {
private static final Logger log = Logger.getLogger(RoutableRepository.class.getName());
private final CopyOnWriteHashMap<Integer, VersionMap> factoryTypes = new CopyOnWriteHashMap<>();
private final CopyOnWriteHashMap<CacheKey, RoutableFactory> cache = new CopyOnWriteHashMap<>();
- private LoadTypeSet loadTypes;
+ private LoadTypeSet loadTypes; // TODO remove on Vespa 8
+ public RoutableRepository() {}
+
+ /**
+ * @deprecated load types are deprecated. Use default constructor instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public RoutableRepository(LoadTypeSet set) {
loadTypes = set;
}
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/VisitorParametersTestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/VisitorParametersTestCase.java
index caed3867d99..b1187d48374 100644
--- a/documentapi/src/test/java/com/yahoo/documentapi/VisitorParametersTestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/VisitorParametersTestCase.java
@@ -7,7 +7,9 @@ import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
import org.junit.Test;
import static org.junit.Assert.*;
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class VisitorParametersTestCase {
+ // TODO: Remove on Vespa 8
private LoadType loadType = new LoadType(3, "samnmax", DocumentProtocol.Priority.HIGH_3);
@SuppressWarnings("removal")// TODO: Vespa 8: remove
@@ -21,12 +23,12 @@ public class VisitorParametersTestCase {
params.setLibraryParameter("groovy", "dudes");
params.setLibraryParameter("ninja", "turtles");
params.setMaxBucketsPerVisitor(55);
- params.setPriority(DocumentProtocol.Priority.HIGHEST);
+ params.setPriority(DocumentProtocol.Priority.HIGHEST); // TODO: Remove on Vespa 8
params.setRoute("extraterrestrial/highway");
params.setTimeoutMs(1337);
params.setMaxPending(111);
params.setFieldSet(AllFields.NAME);
- params.setLoadType(loadType);
+ params.setLoadType(loadType); // TODO: Remove on Vespa 8
params.setVisitRemoves(true);
params.setVisitInconsistentBuckets(true);
params.setTraceLevel(9);
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/loadtypes/test/LoadTypesTestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/loadtypes/test/LoadTypesTestCase.java
index 73c343174d4..18269971f88 100644
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/loadtypes/test/LoadTypesTestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/loadtypes/test/LoadTypesTestCase.java
@@ -9,6 +9,8 @@ import static org.junit.Assert.assertEquals;
/**
* @author thomasg
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
+// TODO Vespa 8: remove test case once load types are gone
public class LoadTypesTestCase {
@Test
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
index 5250a6b1db7..deecba96aa6 100644
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
@@ -143,6 +143,7 @@ public class Messages60TestCase extends MessagesTestBase {
private static final String BUCKET_SPACE = "beartato";
@Override
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public void run() {
GetBucketListMessage msg = new GetBucketListMessage(new BucketId(16, 123));
msg.setBucketSpace(BUCKET_SPACE);
@@ -151,7 +152,7 @@ public class Messages60TestCase extends MessagesTestBase {
for (Language lang : LANGUAGES) {
msg = (GetBucketListMessage)deserialize("GetBucketListMessage", DocumentProtocol.MESSAGE_GETBUCKETLIST, lang);
assertEquals(new BucketId(16, 123), msg.getBucketId());
- assertEquals("default", msg.getLoadType().getName());
+ assertEquals("default", msg.getLoadType().getName()); // TODO: Remove on Vespa 8
assertEquals(BUCKET_SPACE, msg.getBucketSpace());
}
}
@@ -162,9 +163,10 @@ public class Messages60TestCase extends MessagesTestBase {
private static final String BUCKET_SPACE = "andrei";
@Override
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public void run() {
StatBucketMessage msg = new StatBucketMessage(new BucketId(16, 123), "id.user=123");
- msg.setLoadType(null);
+ msg.setLoadType(null); // TODO: Remove on Vespa 8
msg.setBucketSpace(BUCKET_SPACE);
assertEquals(BASE_MESSAGE_LENGTH + 27 + serializedLength(BUCKET_SPACE), serialize("StatBucketMessage", msg));
@@ -172,7 +174,7 @@ public class Messages60TestCase extends MessagesTestBase {
msg = (StatBucketMessage)deserialize("StatBucketMessage", DocumentProtocol.MESSAGE_STATBUCKET, lang);
assertEquals(new BucketId(16, 123), msg.getBucketId());
assertEquals("id.user=123", msg.getDocumentSelection());
- assertEquals("default", msg.getLoadType().getName());
+ assertEquals("default", msg.getLoadType().getName()); // TODO: Remove on Vespa 8
assertEquals(BUCKET_SPACE, msg.getBucketSpace());
}
}
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
index 19f77ee1335..74d06c05383 100755
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
@@ -19,6 +19,7 @@ import static org.junit.Assert.*;
/**
* @author Simon Thoresen Hult
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public abstract class MessagesTestBase {
protected enum Language {
@@ -28,7 +29,7 @@ public abstract class MessagesTestBase {
protected static final Set<Language> LANGUAGES = EnumSet.allOf(Language.class);
protected final DocumentTypeManager docMan = new DocumentTypeManager();
- protected final LoadTypeSet loadTypes = new LoadTypeSet();
+ protected final LoadTypeSet loadTypes = new LoadTypeSet(); // TODO remove on Vespa 8
protected final DocumentProtocol protocol = new DocumentProtocol(docMan, null, loadTypes);
public MessagesTestBase() {
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/test/MessageBusVisitorSessionTestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/test/MessageBusVisitorSessionTestCase.java
index 0c66c05f35e..ab881e143b7 100755
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/test/MessageBusVisitorSessionTestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/test/MessageBusVisitorSessionTestCase.java
@@ -697,6 +697,7 @@ public class MessageBusVisitorSessionTestCase {
}
@Test
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
public void testMessageParameters() {
MockSender sender = new MockSender();
MockReceiver receiver = new MockReceiver();
@@ -716,7 +717,7 @@ public class MessageBusVisitorSessionTestCase {
params.setTimeoutMs(1337);
params.setMaxPending(111);
params.setFieldSet(DocIdOnly.NAME);
- params.setLoadType(new LoadType(3, "samnmax", DocumentProtocol.Priority.HIGH_3));
+ params.setLoadType(new LoadType(3, "samnmax", DocumentProtocol.Priority.HIGH_3)); // TODO: Remove on Vespa 8
params.setVisitRemoves(true);
params.setVisitInconsistentBuckets(true);
params.setTraceLevel(9);
diff --git a/fastos/src/vespa/fastos/app.cpp b/fastos/src/vespa/fastos/app.cpp
index 05e885a7d37..8477f79f00d 100644
--- a/fastos/src/vespa/fastos/app.cpp
+++ b/fastos/src/vespa/fastos/app.cpp
@@ -16,18 +16,6 @@ FastOS_ApplicationInterface::FastOS_ApplicationInterface() :
_argc(0),
_argv(nullptr)
{
-#ifdef __linux__
- char * fadvise = getenv("VESPA_FADVISE_OPTIONS");
- if (fadvise != nullptr) {
- int fadviseOptions(0);
- if (strstr(fadvise, "SEQUENTIAL")) { fadviseOptions |= POSIX_FADV_SEQUENTIAL; }
- if (strstr(fadvise, "RANDOM")) { fadviseOptions |= POSIX_FADV_RANDOM; }
- if (strstr(fadvise, "WILLNEED")) { fadviseOptions |= POSIX_FADV_WILLNEED; }
- if (strstr(fadvise, "DONTNEED")) { fadviseOptions |= POSIX_FADV_DONTNEED; }
- if (strstr(fadvise, "NOREUSE")) { fadviseOptions |= POSIX_FADV_NOREUSE; }
- FastOS_FileInterface::setDefaultFAdviseOptions(fadviseOptions);
- }
-#endif
}
FastOS_ApplicationInterface::~FastOS_ApplicationInterface () = default;
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 5c76eb274df..d915b18ebdf 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -209,7 +209,7 @@ public class Flags {
public static final UnboundBooleanFlag ENABLED_HORIZON_DASHBOARD = defineFeatureFlag(
"enabled-horizon-dashboard", false,
- List.of("olaa"), "2021-09-13", "2022-04-01",
+ List.of("olaa"), "2021-09-13", "2022-07-01",
"Enable Horizon dashboard",
"Takes effect immediately",
TENANT_ID, CONSOLE_USER_EMAIL
@@ -277,7 +277,7 @@ public class Flags {
public static final UnboundBooleanFlag ENABLE_DATA_HIGHWAY_IN_AWS = defineFeatureFlag(
"enable-data-highway-in-aws", false,
- List.of("hmusum"), "2022-01-06", "2022-04-06",
+ List.of("hmusum"), "2022-01-06", "2022-06-01",
"Enable Data Highway in AWS",
"Takes effect on restart of Docker container",
ZONE_ID, APPLICATION_ID);
@@ -397,6 +397,13 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
+ public static final UnboundStringFlag CONTROLLER_LOCK_SCHEME = defineStringFlag(
+ "new-controller-lock-scheme", "OLD",
+ List.of("hmusum"), "2022-04-07", "2022-05-07",
+ "Lock scheme to application-related controller locks (valid values: OLD, BOTH, NEW)",
+ "Takes effect immediately",
+ ZONE_ID);
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,
diff --git a/fnet/src/tests/frt/detach_supervisor/detach_supervisor_test.cpp b/fnet/src/tests/frt/detach_supervisor/detach_supervisor_test.cpp
index 9f251d4d491..953b2e7432e 100644
--- a/fnet/src/tests/frt/detach_supervisor/detach_supervisor_test.cpp
+++ b/fnet/src/tests/frt/detach_supervisor/detach_supervisor_test.cpp
@@ -11,6 +11,7 @@
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/util/time.h>
#include <vespa/fastos/thread.h>
+#include <thread>
using namespace vespalib;
using vespalib::make_string_short::fmt;
diff --git a/fnet/src/vespa/fnet/controlpacket.cpp b/fnet/src/vespa/fnet/controlpacket.cpp
index 9ff69a76210..9aa03ad5e3a 100644
--- a/fnet/src/vespa/fnet/controlpacket.cpp
+++ b/fnet/src/vespa/fnet/controlpacket.cpp
@@ -95,7 +95,10 @@ FNET_ControlPacket
FNET_ControlPacket::IOCClose(FNET_CMD_IOC_CLOSE);
FNET_ControlPacket
-FNET_ControlPacket::DetachServerAdapter(FNET_CMD_DETACH_SERVER_ADAPTER);
+FNET_ControlPacket::DetachServerAdapterInit(FNET_CMD_DETACH_SERVER_ADAPTER_INIT);
+
+FNET_ControlPacket
+FNET_ControlPacket::DetachServerAdapterFini(FNET_CMD_DETACH_SERVER_ADAPTER_FINI);
FNET_ControlPacket
FNET_ControlPacket::Execute(FNET_CMD_EXECUTE);
diff --git a/fnet/src/vespa/fnet/controlpacket.h b/fnet/src/vespa/fnet/controlpacket.h
index 8dd2d034ae6..ad846d37c30 100644
--- a/fnet/src/vespa/fnet/controlpacket.h
+++ b/fnet/src/vespa/fnet/controlpacket.h
@@ -38,7 +38,8 @@ public:
FNET_CMD_IOC_ENABLE_WRITE,
FNET_CMD_IOC_HANDSHAKE_ACT,
FNET_CMD_IOC_CLOSE,
- FNET_CMD_DETACH_SERVER_ADAPTER,
+ FNET_CMD_DETACH_SERVER_ADAPTER_INIT,
+ FNET_CMD_DETACH_SERVER_ADAPTER_FINI,
FNET_CMD_EXECUTE,
FNET_CMD_TIMEOUT,
FNET_CMD_BAD_PACKET,
@@ -51,7 +52,8 @@ public:
static FNET_ControlPacket IOCEnableWrite;
static FNET_ControlPacket IOCHandshakeACT;
static FNET_ControlPacket IOCClose;
- static FNET_ControlPacket DetachServerAdapter;
+ static FNET_ControlPacket DetachServerAdapterInit;
+ static FNET_ControlPacket DetachServerAdapterFini;
static FNET_ControlPacket Execute;
static FNET_ControlPacket Timeout;
static FNET_ControlPacket BadPacket;
diff --git a/fnet/src/vespa/fnet/frt/supervisor.cpp b/fnet/src/vespa/fnet/frt/supervisor.cpp
index 0a31b9d882b..1681321b239 100644
--- a/fnet/src/vespa/fnet/frt/supervisor.cpp
+++ b/fnet/src/vespa/fnet/frt/supervisor.cpp
@@ -30,12 +30,10 @@ FRT_Supervisor::FRT_Supervisor(FNET_Transport *transport)
FRT_Supervisor::~FRT_Supervisor()
{
+ _transport->detach(this);
if (_connector != nullptr) {
- _connector->Owner()->Close(_connector, /* needref */ false);
+ _connector->SubRef();
}
- _transport->wait_for_pending_resolves();
- _transport->detach(this);
- _transport->sync();
}
FNET_Scheduler *
diff --git a/fnet/src/vespa/fnet/transport.cpp b/fnet/src/vespa/fnet/transport.cpp
index 0a79f324bb9..1130b6d3e5e 100644
--- a/fnet/src/vespa/fnet/transport.cpp
+++ b/fnet/src/vespa/fnet/transport.cpp
@@ -125,6 +125,11 @@ TransportConfig::time_tools() const {
} // fnet
+void
+FNET_Transport::wait_for_pending_resolves() {
+ _async_resolver->wait_for_pending_resolves();
+}
+
FNET_Transport::FNET_Transport(const fnet::TransportConfig &cfg)
: _async_resolver(cfg.resolver()),
_crypto_engine(cfg.crypto()),
@@ -158,11 +163,6 @@ FNET_Transport::resolve_async(const vespalib::string &spec,
_async_resolver->resolve_async(spec, std::move(result_handler));
}
-void
-FNET_Transport::wait_for_pending_resolves() {
- _async_resolver->wait_for_pending_resolves();
-}
-
vespalib::CryptoSocket::UP
FNET_Transport::create_client_crypto_socket(vespalib::SocketHandle socket, const vespalib::SocketSpec &spec)
{
@@ -221,8 +221,14 @@ void
FNET_Transport::detach(FNET_IServerAdapter *server_adapter)
{
for (const auto &thread: _threads) {
- thread->detach(server_adapter);
+ thread->init_detach(server_adapter);
+ }
+ wait_for_pending_resolves();
+ sync();
+ for (const auto &thread: _threads) {
+ thread->fini_detach(server_adapter);
}
+ sync();
}
FNET_Scheduler *
diff --git a/fnet/src/vespa/fnet/transport.h b/fnet/src/vespa/fnet/transport.h
index 7dbfd80dfe7..d6e4aefb02b 100644
--- a/fnet/src/vespa/fnet/transport.h
+++ b/fnet/src/vespa/fnet/transport.h
@@ -113,6 +113,11 @@ private:
Threads _threads;
const FNET_Config _config;
+ /**
+ * Wait for all pending resolve requests.
+ **/
+ void wait_for_pending_resolves();
+
public:
FNET_Transport(const FNET_Transport &) = delete;
FNET_Transport & operator = (const FNET_Transport &) = delete;
@@ -160,11 +165,6 @@ public:
vespalib::AsyncResolver::ResultHandler::WP result_handler);
/**
- * Wait for all pending resolve requests.
- **/
- void wait_for_pending_resolves();
-
- /**
* Wrap a plain socket endpoint (client side) in a CryptoSocket. The
* implementation will be determined by the CryptoEngine used by
* this Transport.
@@ -258,11 +258,8 @@ public:
* Detach a server adapter from this transport.
*
* This will close all connectors and connections referencing the
- * server adapter. Note that this is an async
- * operation. 'wait_for_pending_resolves' should be called before
- * this to make sure any in-flight connections are added
- * first. 'sync' should be called after this to drain any pending
- * call-backs.
+ * server adapter. Note that this function will also synchronize
+ * with async address resolving and underlying transport threads.
**/
void detach(FNET_IServerAdapter *server_adapter);
diff --git a/fnet/src/vespa/fnet/transport_thread.cpp b/fnet/src/vespa/fnet/transport_thread.cpp
index 53363740bae..d46d174c670 100644
--- a/fnet/src/vespa/fnet/transport_thread.cpp
+++ b/fnet/src/vespa/fnet/transport_thread.cpp
@@ -162,7 +162,7 @@ FNET_TransportThread::SafeDiscardEvent(FNET_ControlPacket *cpacket,
void
FNET_TransportThread::handle_add_cmd(FNET_IOComponent *ioc)
{
- if (ioc->handle_add_event()) {
+ if ((_detaching.count(ioc->server_adapter()) == 0) && ioc->handle_add_event()) {
AddComponent(ioc);
ioc->_flags._ioc_added = true;
ioc->attach_selector(_selector);
@@ -186,8 +186,9 @@ FNET_TransportThread::handle_close_cmd(FNET_IOComponent *ioc)
void
-FNET_TransportThread::handle_detach_server_adapter_cmd(FNET_IServerAdapter *server_adapter)
+FNET_TransportThread::handle_detach_server_adapter_init_cmd(FNET_IServerAdapter *server_adapter)
{
+ _detaching.insert(server_adapter);
FNET_IOComponent *component = _componentsHead;
while (component != nullptr) {
FNET_IOComponent *tmp = component;
@@ -201,6 +202,12 @@ FNET_TransportThread::handle_detach_server_adapter_cmd(FNET_IServerAdapter *serv
}
+void
+FNET_TransportThread::handle_detach_server_adapter_fini_cmd(FNET_IServerAdapter *server_adapter)
+{
+ _detaching.erase(server_adapter);
+}
+
extern "C" {
static void pipehandler(int)
@@ -241,7 +248,8 @@ FNET_TransportThread::FNET_TransportThread(FNET_Transport &owner_in)
_pseudo_thread(),
_started(false),
_shutdown(false),
- _finished(false)
+ _finished(false),
+ _detaching()
{
trapsigpipe();
}
@@ -348,9 +356,15 @@ FNET_TransportThread::Close(FNET_IOComponent *comp, bool needRef)
}
void
-FNET_TransportThread::detach(FNET_IServerAdapter *server_adapter)
+FNET_TransportThread::init_detach(FNET_IServerAdapter *server_adapter)
{
- PostEvent(&FNET_ControlPacket::DetachServerAdapter, FNET_Context(server_adapter));
+ PostEvent(&FNET_ControlPacket::DetachServerAdapterInit, FNET_Context(server_adapter));
+}
+
+void
+FNET_TransportThread::fini_detach(FNET_IServerAdapter *server_adapter)
+{
+ PostEvent(&FNET_ControlPacket::DetachServerAdapterFini, FNET_Context(server_adapter));
}
bool
@@ -432,8 +446,13 @@ FNET_TransportThread::handle_wakeup()
continue;
}
- if (packet->GetCommand() == FNET_ControlPacket::FNET_CMD_DETACH_SERVER_ADAPTER) {
- handle_detach_server_adapter_cmd(context._value.SERVER_ADAPTER);
+ if (packet->GetCommand() == FNET_ControlPacket::FNET_CMD_DETACH_SERVER_ADAPTER_INIT) {
+ handle_detach_server_adapter_init_cmd(context._value.SERVER_ADAPTER);
+ continue;
+ }
+
+ if (packet->GetCommand() == FNET_ControlPacket::FNET_CMD_DETACH_SERVER_ADAPTER_FINI) {
+ handle_detach_server_adapter_fini_cmd(context._value.SERVER_ADAPTER);
continue;
}
diff --git a/fnet/src/vespa/fnet/transport_thread.h b/fnet/src/vespa/fnet/transport_thread.h
index c120894ac9c..b507c5dc31d 100644
--- a/fnet/src/vespa/fnet/transport_thread.h
+++ b/fnet/src/vespa/fnet/transport_thread.h
@@ -13,6 +13,7 @@
#include <mutex>
#include <condition_variable>
#include <chrono>
+#include <set>
namespace fnet { struct TimeTools; }
class FNET_Transport;
@@ -51,6 +52,7 @@ private:
std::atomic<bool> _started; // event loop started ?
std::atomic<bool> _shutdown; // should stop event loop ?
std::atomic<bool> _finished; // event loop stopped ?
+ std::set<FNET_IServerAdapter*> _detaching; // server adapters being detached
/**
* Add an IOComponent to the list of components. This operation is
@@ -143,7 +145,8 @@ private:
void handle_add_cmd(FNET_IOComponent *ioc);
void handle_close_cmd(FNET_IOComponent *ioc);
- void handle_detach_server_adapter_cmd(FNET_IServerAdapter *server_adapter);
+ void handle_detach_server_adapter_init_cmd(FNET_IServerAdapter *server_adapter);
+ void handle_detach_server_adapter_fini_cmd(FNET_IServerAdapter *server_adapter);
/**
* This method is called to initialize the transport thread event
@@ -336,16 +339,16 @@ public:
void Close(FNET_IOComponent *comp, bool needRef = true);
/**
- * Detach a server adapter from this transport.
- *
- * This will close all connectors and connections referencing the
- * server adapter. Note that this is an async
- * operation. 'wait_for_pending_resolves' (on the owning
- * Transport) should be called before this to make sure any
- * in-flight connections are added first. 'sync' should be called
- * after this to drain any pending call-backs.
+ * Start the operation of detaching a server adapter from this
+ * transport.
+ **/
+ void init_detach(FNET_IServerAdapter *server_adapter);
+
+ /**
+ * Complete the operation of detaching a server adapter from this
+ * transport.
**/
- void detach(FNET_IServerAdapter *server_adapter);
+ void fini_detach(FNET_IServerAdapter *server_adapter);
/**
* Post an execution event on the transport event queue. The return
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngine.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngine.java
index cfa0452ebf9..2aa1d12c491 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngine.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngine.java
@@ -20,8 +20,11 @@ import java.util.Optional;
*/
public interface ContainerEngine {
- /** Create a new container */
- void createContainer(NodeAgentContext context, ContainerData containerData, ContainerResources containerResources);
+ /**
+ * Create a new container
+ * @return ContainerData that can be used to write files inside container
+ */
+ ContainerData createContainer(NodeAgentContext context, ContainerResources containerResources);
/** Start a created container */
void startContainer(NodeAgentContext context);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
index 8a66373c28b..9060261b806 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
@@ -41,8 +41,8 @@ public class ContainerOperations {
this.containerStatsCollector = new ContainerStatsCollector(cgroup, fileSystem);
}
- public void createContainer(NodeAgentContext context, ContainerData containerData, ContainerResources containerResources) {
- containerEngine.createContainer(context, containerData, containerResources);
+ public ContainerData createContainer(NodeAgentContext context, ContainerResources containerResources) {
+ return containerEngine.createContainer(context, containerResources);
}
public void startContainer(NodeAgentContext context) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/ContainerData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/ContainerData.java
index 26c3d101c69..10c62695f70 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/ContainerData.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/ContainerData.java
@@ -23,14 +23,20 @@ public interface ContainerData {
*/
void addFile(ContainerPath path, String data, String permissions);
- /** Add directory in container at path. */
- void addDirectory(ContainerPath path);
+ /**
+ * @param path Container path to create directory at
+ * @param permissions optional file permissions, see {@link UnixPath#setPermissions(String)} for format.
+ */
+ void addDirectory(ContainerPath path, String... permissions);
/**
* Symlink to a file in container at path.
* @param symlink The path to the symlink inside the container
* @param target The path to the target file for the symbolic link inside the container
*/
- void createSymlink(ContainerPath symlink, Path target);
+ void addSymlink(ContainerPath symlink, Path target);
+
+ /** Writes all the files, directories and symlinks that were previously added */
+ void converge(NodeAgentContext context);
}
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 9da7f1dbdb6..61e777a9576 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
@@ -27,9 +27,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.identity.CredentialsMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.VespaServiceDumper;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
-import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
-import java.nio.file.Path;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
@@ -233,10 +231,10 @@ public class NodeAgentImpl implements NodeAgent {
}
private Container startContainer(NodeAgentContext context) {
- ContainerData containerData = createContainerData(context);
ContainerResources wantedResources = warmUpDuration(context).isNegative() ?
getContainerResources(context) : getContainerResources(context).withUnlimitedCpus();
- containerOperations.createContainer(context, containerData, wantedResources);
+ ContainerData containerData = containerOperations.createContainer(context, wantedResources);
+ writeContainerData(context, containerData);
containerOperations.startContainer(context);
currentRebootGeneration = context.node().wantedRebootGeneration();
@@ -598,29 +596,7 @@ public class NodeAgentImpl implements NodeAgent {
}
}
- protected ContainerData createContainerData(NodeAgentContext context) {
- return new ContainerData() {
- @Override
- public void addFile(ContainerPath path, String data) {
- throw new UnsupportedOperationException("addFile not implemented");
- }
-
- @Override
- public void addFile(ContainerPath path, String data, String permissions) {
- throw new UnsupportedOperationException("addFile not implemented");
- }
-
- @Override
- public void addDirectory(ContainerPath path) {
- throw new UnsupportedOperationException("addDirectory not implemented");
- }
-
- @Override
- public void createSymlink(ContainerPath symlink, Path target) {
- throw new UnsupportedOperationException("createSymlink not implemented");
- }
- };
- }
+ protected void writeContainerData(NodeAgentContext context, ContainerData containerData) { }
protected List<CredentialsMaintainer> credentialsMaintainers() {
return credentialsMaintainers;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectory.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectory.java
index 61c4e7475a6..175f9a581a4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectory.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectory.java
@@ -50,21 +50,17 @@ public class MakeDirectory {
throw new UncheckedIOException(new NotDirectoryException(path.toString()));
}
} else {
+ Optional<String> permissions = attributeSync.getPermissions();
if (createParents) {
// We'll skip logging system modification here, as we'll log about the creation
// of the directory next.
- path.createParents();
+ permissions.ifPresentOrElse(path::createParents, path::createParents);
}
context.recordSystemModification(logger, "Creating directory " + path);
systemModified = true;
- Optional<String> permissions = attributeSync.getPermissions();
- if (permissions.isPresent()) {
- path.createDirectory(permissions.get());
- } else {
- path.createDirectory();
- }
+ permissions.ifPresentOrElse(path::createDirectory, path::createDirectory);
}
systemModified |= attributeSync.converge(context, attributesCache);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
index cc780e277ad..ac5035216e9 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
@@ -114,9 +114,7 @@ public class UnixPath {
public UnixPath writeBytes(byte[] content, String permissions, OpenOption... options) {
FileAttribute<?>[] attributes = Optional.ofNullable(permissions)
- .map(this::getPosixFilePermissionsFromString)
- .map(PosixFilePermissions::asFileAttribute)
- .map(attribute -> new FileAttribute<?>[]{attribute})
+ .map(this::permissionsAsFileAttributes)
.orElseGet(() -> new FileAttribute<?>[0]);
Set<OpenOption> optionsSet = options.length == 0 ? DEFAULT_OPEN_OPTIONS : Set.of(options);
@@ -202,27 +200,20 @@ public class UnixPath {
return ifExists(this::getAttributes);
}
- public UnixPath createNewFile() {
- uncheck(() -> Files.createFile(path));
+ public UnixPath createNewFile(String... permissions) {
+ uncheck(() -> Files.createFile(path, permissionsAsFileAttributes(permissions)));
return this;
}
- public UnixPath createParents() {
- uncheck(() -> Files.createDirectories(path.getParent()));
+ public UnixPath createParents(String... permissions) {
+ getParent().createDirectories(permissions);
return this;
}
- public UnixPath createDirectory(String permissions) {
- Set<PosixFilePermission> set = getPosixFilePermissionsFromString(permissions);
- FileAttribute<Set<PosixFilePermission>> attribute = PosixFilePermissions.asFileAttribute(set);
- uncheck(() -> Files.createDirectory(path, attribute));
- return this;
- }
-
- /** Create directory unless it already exists, and return this. */
- public UnixPath createDirectory() {
+ /** Create directory with given permissions, unless it already exists, and return this. */
+ public UnixPath createDirectory(String... permissions) {
try {
- Files.createDirectory(path);
+ Files.createDirectory(path, permissionsAsFileAttributes(permissions));
} catch (FileAlreadyExistsException ignore) {
} catch (IOException e) {
throw new UncheckedIOException(e);
@@ -230,8 +221,8 @@ public class UnixPath {
return this;
}
- public UnixPath createDirectories() {
- uncheck(() -> Files.createDirectories(path));
+ public UnixPath createDirectories(String... permissions) {
+ uncheck(() -> Files.createDirectories(path, permissionsAsFileAttributes(permissions)));
return this;
}
@@ -318,13 +309,19 @@ public class UnixPath {
return new UnixPath(link);
}
- public FileSnapshot getFileSnapshot() { return FileSnapshot.forPath(path); }
-
@Override
public String toString() {
return path.toString();
}
+ private FileAttribute<?>[] permissionsAsFileAttributes(String... permissions) {
+ if (permissions.length == 0) return new FileAttribute<?>[0];
+ if (permissions.length > 1)
+ throw new IllegalArgumentException("Expected permissions to not be set or be a single string");
+
+ return new FileAttribute<?>[]{PosixFilePermissions.asFileAttribute(getPosixFilePermissionsFromString(permissions[0]))};
+ }
+
private Set<PosixFilePermission> getPosixFilePermissionsFromString(String permissions) {
try {
return PosixFilePermissions.fromString(permissions);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java
index 25cdff4b726..2d3a4976fe5 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java
@@ -7,8 +7,10 @@ import com.yahoo.vespa.hosted.node.admin.container.image.Image;
import com.yahoo.vespa.hosted.node.admin.nodeagent.ContainerData;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixUser;
+import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
+import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@@ -66,8 +68,34 @@ public class ContainerEngineMock implements ContainerEngine {
}
@Override
- public void createContainer(NodeAgentContext context, ContainerData containerData, ContainerResources containerResources) {
+ public ContainerData createContainer(NodeAgentContext context, ContainerResources containerResources) {
addContainer(createContainer(context, PartialContainer.State.created, containerResources));
+ return new ContainerData() {
+ @Override
+ public void addFile(ContainerPath path, String data) {
+ throw new UnsupportedOperationException("addFile not implemented");
+ }
+
+ @Override
+ public void addFile(ContainerPath path, String data, String permissions) {
+ throw new UnsupportedOperationException("addFile not implemented");
+ }
+
+ @Override
+ public void addDirectory(ContainerPath path, String... permissions) {
+ throw new UnsupportedOperationException("addDirectory not implemented");
+ }
+
+ @Override
+ public void addSymlink(ContainerPath symlink, Path target) {
+ throw new UnsupportedOperationException("addSymlink not implemented");
+ }
+
+ @Override
+ public void converge(NodeAgentContext context) {
+ throw new UnsupportedOperationException("converge not implemented");
+ }
+ };
}
@Override
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerFailTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerFailTest.java
index a2dbfe0db4b..5649da7c4ea 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerFailTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerFailTest.java
@@ -2,8 +2,8 @@
package com.yahoo.vespa.hosted.node.admin.integration;
import com.yahoo.config.provision.DockerImage;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
import com.yahoo.vespa.test.file.TestFileSystem;
@@ -36,13 +36,13 @@ public class ContainerFailTest {
NodeAgentContext context = NodeAgentContextImpl.builder(nodeSpec).fileSystem(TestFileSystem.create()).build();
- tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any(), any());
+ tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any());
tester.inOrder(tester.containerOperations).resumeNode(containerMatcher(containerName));
tester.containerOperations.removeContainer(context, tester.containerOperations.getContainer(context).get());
tester.inOrder(tester.containerOperations).removeContainer(containerMatcher(containerName), any());
- tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any(), any());
+ tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any());
tester.inOrder(tester.containerOperations).resumeNode(containerMatcher(containerName));
verify(tester.nodeRepository, never()).updateNodeAttributes(any(), any());
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/MultiContainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/MultiContainerTest.java
index 5bb279323e9..79546d8cf78 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/MultiContainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/MultiContainerTest.java
@@ -48,7 +48,7 @@ public class MultiContainerTest {
tester.addChildNodeRepositoryNode(nodeSpec);
ContainerName containerName = ContainerName.fromHostname(hostName);
- tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any(), any());
+ tester.inOrder(tester.containerOperations).createContainer(containerMatcher(containerName), any());
tester.inOrder(tester.containerOperations).resumeNode(containerMatcher(containerName));
tester.inOrder(tester.nodeRepository).updateNodeAttributes(eq(hostName), any());
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RebootTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RebootTest.java
index 4d95991dd29..f3635be1b4b 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RebootTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RebootTest.java
@@ -2,8 +2,8 @@
package com.yahoo.vespa.hosted.node.admin.integration;
import com.yahoo.config.provision.DockerImage;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
import org.junit.Test;
@@ -31,7 +31,7 @@ public class RebootTest {
tester.addChildNodeRepositoryNode(NodeSpec.Builder.testSpec(hostname).wantedDockerImage(dockerImage).build());
ContainerName host1 = new ContainerName("host1");
- tester.inOrder(tester.containerOperations).createContainer(containerMatcher(host1), any(), any());
+ tester.inOrder(tester.containerOperations).createContainer(containerMatcher(host1), any());
tester.setWantedState(NodeAdminStateUpdater.State.SUSPENDED);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java
index 13b3db1c55c..0675626c0e2 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java
@@ -2,9 +2,9 @@
package com.yahoo.vespa.hosted.node.admin.integration;
import com.yahoo.config.provision.DockerImage;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeAttributes;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import org.junit.Test;
import java.util.List;
@@ -29,7 +29,7 @@ public class RestartTest {
tester.addChildNodeRepositoryNode(nodeSpec);
ContainerName host1 = new ContainerName("host1");
- tester.inOrder(tester.containerOperations).createContainer(containerMatcher(host1), any(), any());
+ tester.inOrder(tester.containerOperations).createContainer(containerMatcher(host1), any());
tester.inOrder(tester.nodeRepository).updateNodeAttributes(
eq(hostname), eq(new NodeAttributes().withDockerImage(dockerImage).withVespaVersion(dockerImage.tagAsVersion())));
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 28ef19d4065..2eff2c64cec 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
@@ -154,7 +154,7 @@ public class NodeAgentImplTest {
nodeAgent.stopForHostSuspension(context);
nodeAgent.doConverge(context);
- inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any());
inOrder.verify(containerOperations, times(1)).startContainer(eq(context));
inOrder.verify(containerOperations, times(0)).startServices(eq(context)); // done as part of startContainer
inOrder.verify(containerOperations, times(1)).resumeNode(eq(context));
@@ -181,7 +181,7 @@ public class NodeAgentImplTest {
final InOrder inOrder = inOrder(containerOperations, orchestrator, nodeRepository, aclMaintainer, healthChecker);
inOrder.verify(containerOperations, times(1)).pullImageAsyncIfNeeded(any(), eq(dockerImage), any());
- inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any());
inOrder.verify(containerOperations, times(1)).startContainer(eq(context));
inOrder.verify(aclMaintainer, times(1)).converge(eq(context));
inOrder.verify(containerOperations, times(1)).resumeNode(eq(context));
@@ -310,7 +310,7 @@ public class NodeAgentImplTest {
fail("Expected to throw an exception");
} catch (OrchestratorException ignored) { }
- verify(containerOperations, never()).createContainer(eq(context), any(), any());
+ verify(containerOperations, never()).createContainer(eq(context), any());
verify(containerOperations, never()).startContainer(eq(context));
verify(orchestrator, never()).resume(any(String.class));
verify(nodeRepository, never()).updateNodeAttributes(any(String.class), any(NodeAttributes.class));
@@ -343,7 +343,7 @@ public class NodeAgentImplTest {
// First time we fail to resume because health verification fails
verify(orchestrator, times(1)).suspend(eq(hostName));
verify(containerOperations, times(1)).removeContainer(eq(context), any());
- verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ verify(containerOperations, times(1)).createContainer(eq(context), any());
verify(containerOperations, times(1)).startContainer(eq(context));
verify(orchestrator, never()).resume(eq(hostName));
verify(nodeRepository, never()).updateNodeAttributes(any(), any());
@@ -352,7 +352,7 @@ public class NodeAgentImplTest {
// Do not reboot the container again
verify(containerOperations, times(1)).removeContainer(eq(context), any());
- verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ verify(containerOperations, 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)));
@@ -393,7 +393,7 @@ public class NodeAgentImplTest {
// Should only be called once, when we initialize
verify(containerOperations, times(1)).getContainer(eq(context));
verify(containerOperations, never()).removeContainer(eq(context), any());
- verify(containerOperations, never()).createContainer(eq(context), any(), any());
+ verify(containerOperations, never()).createContainer(eq(context), any());
verify(containerOperations, never()).startContainer(eq(context));
verify(orchestrator, never()).resume(any(String.class));
verify(nodeRepository, never()).updateNodeAttributes(eq(hostName), any());
@@ -458,7 +458,7 @@ public class NodeAgentImplTest {
inOrder.verify(storageMaintainer, times(1)).archiveNodeStorage(eq(context));
inOrder.verify(nodeRepository, times(1)).setNodeState(eq(hostName), eq(NodeState.ready));
- verify(containerOperations, never()).createContainer(eq(context), any(), any());
+ verify(containerOperations, never()).createContainer(eq(context), any());
verify(containerOperations, never()).startContainer(eq(context));
verify(containerOperations, never()).suspendNode(eq(context));
verify(containerOperations, times(1)).stopServices(eq(context));
@@ -508,7 +508,7 @@ public class NodeAgentImplTest {
nodeAgent.doConverge(context);
verify(containerOperations, times(1)).removeContainer(eq(context), any());
- verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ verify(containerOperations, times(1)).createContainer(eq(context), any());
verify(containerOperations, times(1)).startContainer(eq(context));
}
@@ -568,7 +568,7 @@ public class NodeAgentImplTest {
} catch (RuntimeException ignored) { }
verify(containerOperations, never()).removeContainer(eq(context), any());
- verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ verify(containerOperations, times(1)).createContainer(eq(context), any());
verify(containerOperations, times(1)).startContainer(eq(context));
verify(nodeAgent, never()).resumeNodeIfNeeded(any());
@@ -578,7 +578,7 @@ public class NodeAgentImplTest {
nodeAgent.doConverge(context);
verify(containerOperations, times(1)).removeContainer(eq(context), any());
- verify(containerOperations, times(2)).createContainer(eq(context), any(), any());
+ verify(containerOperations, times(2)).createContainer(eq(context), any());
verify(containerOperations, times(2)).startContainer(eq(context));
verify(nodeAgent, times(1)).resumeNodeIfNeeded(any());
}
@@ -605,7 +605,7 @@ public class NodeAgentImplTest {
final InOrder inOrder = inOrder(containerOperations, orchestrator, nodeRepository, aclMaintainer);
inOrder.verify(containerOperations, times(1)).pullImageAsyncIfNeeded(any(), eq(dockerImage), any());
- inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any(), any());
+ inOrder.verify(containerOperations, times(1)).createContainer(eq(context), any());
inOrder.verify(containerOperations, times(1)).startContainer(eq(context));
inOrder.verify(aclMaintainer, times(1)).converge(eq(context));
inOrder.verify(containerOperations, times(1)).resumeNode(eq(context));
@@ -770,10 +770,10 @@ public class NodeAgentImplTest {
mockGetContainer(dockerImage, isRunning);
doAnswer(invoc -> {
NodeAgentContext context = invoc.getArgument(0, NodeAgentContext.class);
- ContainerResources resources = invoc.getArgument(2, ContainerResources.class);
+ ContainerResources resources = invoc.getArgument(1, ContainerResources.class);
mockGetContainer(context.node().wantedDockerImage().get(), resources, true);
return null;
- }).when(containerOperations).createContainer(any(), any(), any());
+ }).when(containerOperations).createContainer(any(), any());
doAnswer(invoc -> {
NodeAgentContext context = invoc.getArgument(0, NodeAgentContext.class);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
index cb82585fe01..fc0da672e6f 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
@@ -322,7 +322,11 @@ public class Nodes {
if (parkOnDeallocationOf(node, agent)) {
return park(node.hostname(), false, agent, reason, transaction);
} else {
- return db.writeTo(Node.State.dirty, List.of(node), agent, Optional.of(reason), transaction).get(0);
+ Node.State toState = Node.State.dirty;
+ if (node.state() == Node.State.parked && node.status().wantToDeprovision()) {
+ throw new IllegalArgumentException("Cannot move " + node + " to " + toState + ": It's being deprovisioned");
+ }
+ return db.writeTo(toState, List.of(node), agent, Optional.of(reason), transaction).get(0);
}
}
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 379bb2566df..a9abc352d8c 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
@@ -43,8 +43,8 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import static com.yahoo.stream.CustomCollectors.toLinkedMap;
import static java.util.stream.Collectors.collectingAndThen;
-import static java.util.stream.Collectors.toMap;
/**
* Client which reads and writes nodes to a curator database.
@@ -453,7 +453,8 @@ public class CuratorDatabaseClient {
.map(this::readLoadBalancer)
.filter(Optional::isPresent)
.map(Optional::get)
- .collect(collectingAndThen(toMap(LoadBalancer::id, Function.identity()),
+ .collect(collectingAndThen(toLinkedMap(LoadBalancer::id,
+ Function.identity()),
Collections::unmodifiableMap));
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
index 68d75db8a4c..64cee87d942 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java
@@ -53,6 +53,7 @@ import static com.yahoo.vespa.hosted.provision.testutils.MockHostProvisioner.Beh
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* @author freva
@@ -496,6 +497,12 @@ public class DynamicProvisioningMaintainerTest {
tester.nodeRepository().nodes().deallocate(hostToRemove.get(), Agent.system, getClass().getSimpleName());
assertSame("Host moves to parked", Node.State.parked, hostToRemove.get().state());
+ // deprovisioning host cannot be unparked
+ try {
+ tester.nodeRepository().nodes().deallocate(hostToRemove.get(), Agent.operator, getClass().getSimpleName());
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {}
+
// Host and child is removed
dynamicProvisioningTester.maintainer.maintain();
allNodes = tester.nodeRepository().nodes().list();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
index cc121ba8104..48ee23c7b60 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java
@@ -334,6 +334,9 @@ public class NodeSerializerTest {
" \"hostname\" : \"myHostname\",\n" +
" \"ipAddresses\" : [\"127.0.0.1\"],\n" +
" \"instance\": {\n" +
+ " \"tenantId\":\"t\",\n" +
+ " \"applicationId\":\"a\",\n" +
+ " \"instanceId\":\"i\",\n" +
" \"serviceId\": \"content/myId/0/0/stateful\",\n" +
" \"wantedVespaVersion\": \"6.42.2\"\n" +
" }\n" +
diff --git a/parent/pom.xml b/parent/pom.xml
index 5e3ec3d20ea..d90dc7a292f 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -886,6 +886,17 @@
<version>2.3.0</version>
</dependency>
<dependency>
+ <groupId>org.xerial.snappy</groupId>
+ <artifactId>snappy-java</artifactId>
+ <version>1.1.7</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>3.2.5</version>
+ </dependency>
+
+ <dependency>
<groupId>uk.co.datumedge</groupId>
<artifactId>hamcrest-json</artifactId>
<version>0.2</version>
diff --git a/routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/nginx/NginxMetricsReporter.java b/routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/nginx/NginxMetricsReporter.java
index 79381b8c99e..b9ab1dbe9b6 100644
--- a/routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/nginx/NginxMetricsReporter.java
+++ b/routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/nginx/NginxMetricsReporter.java
@@ -58,7 +58,7 @@ public class NginxMetricsReporter extends AbstractComponent implements Runnable
@Inject
public NginxMetricsReporter(ApplicationIdConfig applicationId, Metric metric, HealthStatus healthStatus, RoutingGenerator routingGenerator) {
- this(new ApplicationId(applicationId), metric, healthStatus, FileSystems.getDefault(), interval, routingGenerator::routingTable);
+ this(ApplicationId.from(applicationId), metric, healthStatus, FileSystems.getDefault(), interval, routingGenerator::routingTable);
}
NginxMetricsReporter(ApplicationId application, Metric metric, HealthStatus healthStatus, FileSystem fileSystem, Duration interval,
diff --git a/screwdriver/release-java-artifacts.sh b/screwdriver/release-java-artifacts.sh
index 8030638cf5b..8d80bb45578 100755
--- a/screwdriver/release-java-artifacts.sh
+++ b/screwdriver/release-java-artifacts.sh
@@ -52,6 +52,8 @@ for MODULE in $(comm -2 -3 \
echo "No javadoc available for module" > $MODULE/src/main/javadoc/README
done
+# Workaround for broken nexus-staging-maven-plugin instead of swapping JDK
+export MAVEN_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED"
export VESPA_MAVEN_EXTRA_OPTS="--show-version --batch-mode"
./bootstrap.sh
@@ -69,20 +71,12 @@ mvn $COMMON_MAVEN_OPTS --file ./maven-plugins/pom.xml -DskipStagingRepositoryClo
# Deploy the rest of the artifacts
mvn $COMMON_MAVEN_OPTS --threads 8 -DskipStagingRepositoryClose=true -DstagingRepositoryId=$STG_REPO deploy
-# Workaround for nexus-staging-maven-plugin:1.6.12:rc-release not working with maven+jdk17
-SWAP_MAVEN_JAVA_WORKAROUND=false
-if rpm -q maven-openjdk17 &> /dev/null; then SWAP_MAVEN_JAVA_WORKAROUND=true; fi
-if $SWAP_MAVEN_JAVA_WORKAROUND; then dnf swap -y maven-openjdk17 maven-openjdk11; fi
-
# Close with checks
mvn $COMMON_MAVEN_OPTS -N org.sonatype.plugins:nexus-staging-maven-plugin:1.6.12:rc-close -DnexusUrl=https://oss.sonatype.org/ -DserverId=ossrh -DstagingRepositoryId=$STG_REPO
# Release if ok
mvn $COMMON_MAVEN_OPTS -N org.sonatype.plugins:nexus-staging-maven-plugin:1.6.12:rc-release -DnexusUrl=https://oss.sonatype.org/ -DserverId=ossrh -DstagingRepositoryId=$STG_REPO
-# Swap back if we swapped previously
-if $SWAP_MAVEN_JAVA_WORKAROUND; then dnf swap -y maven-openjdk11 maven-openjdk17; fi
-
# Delete the GPG rings
rm -rf $SD_SOURCE_DIR/screwdriver/deploy
diff --git a/screwdriver/settings-publish.xml b/screwdriver/settings-publish.xml
index 5524bf9d7ac..2d6dc2d187c 100644
--- a/screwdriver/settings-publish.xml
+++ b/screwdriver/settings-publish.xml
@@ -30,7 +30,7 @@
<gpg.publickeyring>pubring.gpg</gpg.publickeyring>
<gpg.secretkeyring>secring.gpg</gpg.secretkeyring>
<maven.gpg.plugin.version>1.6</maven.gpg.plugin.version>
- <nexus.staging.maven.plugin.version>1.6.7</nexus.staging.maven.plugin.version>
+ <nexus.staging.maven.plugin.version>1.6.12</nexus.staging.maven.plugin.version>
</properties>
</profile>
</profiles>
diff --git a/screwdriver/update-vespa-version-in-sample-apps.sh b/screwdriver/update-vespa-version-in-sample-apps.sh
index dbcb5d46c3a..ca163de347c 100755
--- a/screwdriver/update-vespa-version-in-sample-apps.sh
+++ b/screwdriver/update-vespa-version-in-sample-apps.sh
@@ -10,23 +10,25 @@ if [[ $# -ne 1 ]]; then
fi
readonly VESPA_RELEASE="$1"
-readonly TRUE="true"
-readonly FALSE="false"
-
export JAVA_HOME=$(dirname $(dirname $(readlink -f /usr/bin/java)))
function is_published {
- BUNDLE_PLUGIN_HTTP_CODE=$(curl --write-out %{http_code} --silent --location --output /dev/null https://repo.maven.apache.org/maven2/com/yahoo/vespa/bundle-plugin/${VESPA_RELEASE}/)
- if [ $BUNDLE_PLUGIN_HTTP_CODE = "200" ] ; then
- echo "$TRUE"
+ local TMP_MVN_REPO=/tmp/maven-repo
+ echo $TMP_MVN_REPO
+ mkdir -p $TMP_MVN_REPO
+ rm -rf $TMP_MVN_REPO/com/yahoo/vespa
+ # Because the transfer of artifacts to Maven Central is not atomic we can't just check a simple pom or jar to be available. Because of this we
+ # check that the publication is complete enough to compile a Java sample app
+ if mvn -V -B -pl ai.vespa.example:albums -Dmaven.repo.local=$TMP_MVN_REPO -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -DskipTests clean package; then
+ return 0
else
- echo "$FALSE"
+ return 1
fi
}
function wait_until_published {
cnt=0
- until [[ $(is_published) = "$TRUE" ]]; do
+ until is_published; do
((cnt+=1))
# Wait max 60 minutes
if (( $cnt > 60 )); then
@@ -38,8 +40,6 @@ function wait_until_published {
done
}
-wait_until_published
-
ssh-add -D
set +x
ssh-add <(echo $SAMPLE_APPS_DEPLOY_KEY | base64 -d)
@@ -49,7 +49,9 @@ git clone git@github.com:vespa-engine/sample-apps.git
cd sample-apps
# Update Vespa version property in pom.xml
-mvn versions:set-property -Dproperty=vespa_version -DnewVersion=${VESPA_RELEASE}
+mvn -V -B versions:set-property -Dproperty=vespa_version -DnewVersion=${VESPA_RELEASE}
+
+wait_until_published
changes=$(git status --porcelain | wc -l)
diff --git a/searchcore/src/apps/proton/proton.cpp b/searchcore/src/apps/proton/proton.cpp
index ab273fd2660..27ecbd08917 100644
--- a/searchcore/src/apps/proton/proton.cpp
+++ b/searchcore/src/apps/proton/proton.cpp
@@ -12,9 +12,11 @@
#include <vespa/config/common/configcontext.h>
#include <vespa/fnet/transport.h>
#include <vespa/fastos/thread.h>
+#include <vespa/fastos/file.h>
#include <vespa/fastos/app.h>
#include <iostream>
#include <thread>
+#include <fcntl.h>
#include <vespa/log/log.h>
LOG_SETUP("proton");
@@ -42,6 +44,7 @@ class App : public FastOS_Application
{
private:
static void setupSignals();
+ static void setup_fadvise();
Params parseParams();
void startAndRun(FastOS_ThreadPool & threadPool, FNET_Transport & transport);
public:
@@ -56,6 +59,23 @@ App::setupSignals()
SIG::TERM.hook();
}
+void
+App::setup_fadvise()
+{
+#ifdef __linux__
+ char * fadvise = getenv("VESPA_FADVISE_OPTIONS");
+ if (fadvise != nullptr) {
+ int fadviseOptions(0);
+ if (strstr(fadvise, "SEQUENTIAL")) { fadviseOptions |= POSIX_FADV_SEQUENTIAL; }
+ if (strstr(fadvise, "RANDOM")) { fadviseOptions |= POSIX_FADV_RANDOM; }
+ if (strstr(fadvise, "WILLNEED")) { fadviseOptions |= POSIX_FADV_WILLNEED; }
+ if (strstr(fadvise, "DONTNEED")) { fadviseOptions |= POSIX_FADV_DONTNEED; }
+ if (strstr(fadvise, "NOREUSE")) { fadviseOptions |= POSIX_FADV_NOREUSE; }
+ FastOS_FileInterface::setDefaultFAdviseOptions(fadviseOptions);
+ }
+#endif
+}
+
Params
App::parseParams()
{
@@ -247,6 +267,7 @@ App::Main()
{
try {
setupSignals();
+ setup_fadvise();
FastOS_ThreadPool threadPool(128_Ki);
FNET_Transport transport(buildTransportConfig());
transport.Start(&threadPool);
diff --git a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
index 1feb46cfdb7..7ff5eb823ca 100644
--- a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
+++ b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp
@@ -220,7 +220,11 @@ struct Setup {
}
bool verify() {
generate();
- return vespalib::Process::run(fmt("%s dir:%s", prog, gen_dir.c_str()));
+ vespalib::Process process(fmt("%s dir:%s", prog, gen_dir.c_str()), true);
+ for (auto line = process.read_line(); !line.empty(); line = process.read_line()) {
+ fprintf(stderr, "> %s\n", line.c_str());
+ }
+ return (process.join() == 0);
}
void verify_valid(std::initializer_list<std::string> features) {
for (const std::string &f: features) {
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
index 54561ec6304..4e272e1af4c 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp
@@ -24,6 +24,7 @@
#include <vespa/searchsummary/config/config-juniperrc.h>
#include <vespa/vespalib/time/time_box.h>
#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/time.h>
#include <vespa/config/retriever/configsnapshot.hpp>
#include <thread>
#include <cassert>
@@ -318,6 +319,7 @@ DocumentDBConfigManager::update(FNET_Transport & transport, const ConfigSnapshot
MaintenanceConfigSP newMaintenanceConfig;
std::shared_ptr<const ThreadingServiceConfig> old_threading_service_config;
std::shared_ptr<const AllocConfig> old_alloc_config;
+ constexpr vespalib::duration file_resolve_timeout = 60s * 10;
if (!_ignoreForwardedConfig) {
if (!(_bootstrapConfig->getDocumenttypesConfigSP() &&
@@ -357,7 +359,7 @@ DocumentDBConfigManager::update(FNET_Transport & transport, const ConfigSnapshot
RankingConstants::Vector constants;
if (spec != "") {
config::RpcFileAcquirer fileAcquirer(transport, spec);
- vespalib::TimeBox timeBox(5*60, 5);
+ vespalib::TimeBox timeBox(vespalib::to_s(file_resolve_timeout), 5);
for (const RankingConstantsConfig::Constant &rc : newRankingConstantsConfig->constant) {
auto desc = fmt("name='%s', type='%s'", rc.name.c_str(), rc.type.c_str());
vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref);
@@ -373,7 +375,7 @@ DocumentDBConfigManager::update(FNET_Transport & transport, const ConfigSnapshot
RankingExpressions expressions;
if (spec != "") {
config::RpcFileAcquirer fileAcquirer(transport, spec);
- vespalib::TimeBox timeBox(5*60, 5);
+ vespalib::TimeBox timeBox(vespalib::to_s(file_resolve_timeout), 5);
for (const RankingExpressionsConfig::Expression &rc : newRankingExpressionsConfig->expression) {
auto desc = fmt("name='%s'", rc.name.c_str());
vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref);
@@ -389,7 +391,7 @@ DocumentDBConfigManager::update(FNET_Transport & transport, const ConfigSnapshot
OnnxModels::Vector models;
if (spec != "") {
config::RpcFileAcquirer fileAcquirer(transport, spec);
- vespalib::TimeBox timeBox(5*60, 5);
+ vespalib::TimeBox timeBox(vespalib::to_s(file_resolve_timeout), 5);
for (const OnnxModelsConfig::Model &rc : newOnnxModelsConfig->model) {
auto desc = fmt("name='%s'", rc.name.c_str());
vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref);
diff --git a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.cpp b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.cpp
index c6f8ca923e0..79827ce81c0 100644
--- a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.cpp
@@ -2,6 +2,7 @@
#include "shared_threading_service.h"
#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
+#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/vespalib/util/cpu_usage.h>
#include <vespa/vespalib/util/sequencedtaskexecutor.h>
#include <vespa/vespalib/util/size_literals.h>
@@ -21,9 +22,11 @@ SharedThreadingService::SharedThreadingService(const SharedThreadingServiceConfi
FNET_Transport& transport,
storage::spi::BucketExecutor& bucket_executor)
: _transport(transport),
- _warmup(cfg.warmup_threads(), 128_Ki, CpuUsage::wrap(proton_warmup_executor, CpuUsage::Category::COMPACT)),
+ _warmup(std::make_unique<vespalib::ThreadStackExecutor>(cfg.warmup_threads(), 128_Ki,
+ CpuUsage::wrap(proton_warmup_executor, CpuUsage::Category::COMPACT),
+ cfg.shared_task_limit())),
_shared(std::make_shared<vespalib::BlockingThreadStackExecutor>(cfg.shared_threads(), 128_Ki,
- cfg.shared_task_limit(), proton_shared_executor)),
+ cfg.shared_task_limit(), proton_shared_executor)),
_field_writer(),
_invokeService(std::max(vespalib::adjustTimeoutByDetectedHz(1ms),
cfg.field_writer_config().reactionTime())),
@@ -51,7 +54,7 @@ SharedThreadingService::~SharedThreadingService() = default;
void
SharedThreadingService::sync_all_executors() {
- _warmup.sync();
+ _warmup->sync();
_shared->sync();
if (_field_writer) {
_field_writer->sync_all();
diff --git a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.h b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.h
index 04e30b0f9b3..ead16441da0 100644
--- a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.h
+++ b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service.h
@@ -3,7 +3,7 @@
#include "i_shared_threading_service.h"
#include "shared_threading_service_config.h"
-#include <vespa/vespalib/util/threadstackexecutor.h>
+#include <vespa/vespalib/util/threadexecutor.h>
#include <vespa/vespalib/util/syncable.h>
#include <vespa/vespalib/util/clock.h>
#include <vespa/vespalib/util/invokeserviceimpl.h>
@@ -18,7 +18,7 @@ class SharedThreadingService : public ISharedThreadingService {
private:
using Registration = std::unique_ptr<vespalib::IDestructorCallback>;
FNET_Transport & _transport;
- vespalib::ThreadStackExecutor _warmup;
+ std::unique_ptr<vespalib::SyncableThreadExecutor> _warmup;
std::shared_ptr<vespalib::SyncableThreadExecutor> _shared;
std::unique_ptr<vespalib::ISequencedTaskExecutor> _field_writer;
vespalib::InvokeServiceImpl _invokeService;
@@ -34,7 +34,7 @@ public:
std::shared_ptr<vespalib::Executor> shared_raw() { return _shared; }
void sync_all_executors();
- vespalib::ThreadExecutor& warmup() override { return _warmup; }
+ vespalib::ThreadExecutor& warmup() override { return *_warmup; }
vespalib::ThreadExecutor& shared() override { return *_shared; }
vespalib::ISequencedTaskExecutor* field_writer() override { return _field_writer.get(); }
vespalib::InvokeService & invokeService() override { return _invokeService; }
diff --git a/searchlib/src/tests/fef/resolver/resolver_test.cpp b/searchlib/src/tests/fef/resolver/resolver_test.cpp
index 77aba1664ad..536507f3776 100644
--- a/searchlib/src/tests/fef/resolver/resolver_test.cpp
+++ b/searchlib/src/tests/fef/resolver/resolver_test.cpp
@@ -86,4 +86,10 @@ TEST_F("require_that_bad_input_is_handled", Fixture) {
EXPECT_TRUE(dynamic_cast<RankingExpressionBlueprint *>(spec[1].blueprint.get()) != nullptr);
}
+TEST("require that features can be described") {
+ EXPECT_EQUAL(BlueprintResolver::describe_feature("featureName"), vespalib::string("rank feature 'featureName'"));
+ EXPECT_EQUAL(BlueprintResolver::describe_feature("rankingExpression(foo)"), vespalib::string("function 'foo'"));
+ EXPECT_EQUAL(BlueprintResolver::describe_feature("rankingExpression(foo@1234.5678)"), vespalib::string("function 'foo'"));
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
index b3aa3bd958b..2e562b4a54f 100644
--- a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
@@ -65,6 +65,7 @@ vespa_add_library(searchlib_attribute OBJECT
loadednumericvalue.cpp
loadedvalue.cpp
multi_numeric_enum_search_context.cpp
+ multi_numeric_flag_search_context.cpp
multi_numeric_search_context.cpp
multi_string_enum_search_context.cpp
multi_string_enum_hint_search_context.cpp
diff --git a/searchlib/src/vespa/searchlib/attribute/attributeiterators.h b/searchlib/src/vespa/searchlib/attribute/attributeiterators.h
index 8765e52d38a..d12c5a7eadc 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributeiterators.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributeiterators.h
@@ -333,7 +333,6 @@ template <typename SC>
class FlagAttributeIteratorT : public FlagAttributeIterator
{
private:
- using Attribute = typename SC::Attribute;
void doSeek(uint32_t docId) override;
protected:
@@ -366,7 +365,6 @@ private:
using FlagAttributeIteratorT<SC>::setDocId;
using FlagAttributeIteratorT<SC>::setAtEnd;
using FlagAttributeIteratorT<SC>::isAtEnd;
- using Attribute = typename SC::Attribute;
using Trinary=vespalib::Trinary;
void doSeek(uint32_t docId) override;
Trinary is_strict() const override { return Trinary::True; }
diff --git a/searchlib/src/vespa/searchlib/attribute/attributeiterators.hpp b/searchlib/src/vespa/searchlib/attribute/attributeiterators.hpp
index 8cde2862645..16b0c0da143 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributeiterators.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributeiterators.hpp
@@ -307,9 +307,8 @@ void
FlagAttributeIteratorStrict<SC>::doSeek(uint32_t docId)
{
const SC & sc(_concreteSearchCtx);
- const Attribute &attr = static_cast<const Attribute &>(sc.attribute());
for (int i = sc._low; (i <= sc._high); ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if ((bv != nullptr) && !isAtEnd(docId) && bv->testBit(docId)) {
setDocId(docId);
return;
@@ -318,7 +317,7 @@ FlagAttributeIteratorStrict<SC>::doSeek(uint32_t docId)
uint32_t minNextBit(search::endDocId);
for (int i = sc._low; (i <= sc._high); ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if (bv != nullptr && !isAtEnd(docId)) {
uint32_t nextBit = bv->getNextTrueBit(docId);
minNextBit = std::min(nextBit, minNextBit);
@@ -336,9 +335,8 @@ void
FlagAttributeIteratorT<SC>::doSeek(uint32_t docId)
{
const SC & sc(_concreteSearchCtx);
- const Attribute &attr = static_cast<const Attribute &>(sc.attribute());
for (int i = sc._low; (i <= sc._high); ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if ((bv != nullptr) && !isAtEnd(docId) && bv->testBit(docId)) {
setDocId(docId);
return;
@@ -351,9 +349,8 @@ void
FlagAttributeIteratorT<SC>::or_hits_into(BitVector &result, uint32_t begin_id) {
(void) begin_id;
const SC & sc(_concreteSearchCtx);
- const Attribute &attr = static_cast<const Attribute &>(sc.attribute());
for (int i = sc._low; (i <= sc._high); ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if (bv != nullptr) {
result.orWith(*bv);
}
@@ -364,9 +361,8 @@ template <typename SC>
void
FlagAttributeIteratorT<SC>::and_hits_into(BitVector &result, uint32_t begin_id) {
const SC & sc(_concreteSearchCtx);
- const Attribute &attr = static_cast<const Attribute &>(sc.attribute());
if (sc._low == sc._high) {
- const BitVector * bv = attr.getBitVector(sc._low);
+ const BitVector * bv = sc.get_bit_vector(sc._low);
if (bv != nullptr) {
result.andWith(*bv);
} else {
@@ -383,18 +379,17 @@ template <typename SC>
std::unique_ptr<BitVector>
FlagAttributeIteratorT<SC>::get_hits(uint32_t begin_id) {
const SC & sc(_concreteSearchCtx);
- const Attribute &attr = static_cast<const Attribute &>(sc.attribute());
int i = sc._low;
BitVector::UP result;
for (;!result && i < sc._high; ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if (bv != nullptr) {
result = BitVector::create(*bv, begin_id, getEndId());
}
}
for (; i <= sc._high; ++i) {
- const BitVector * bv = attr.getBitVector(i);
+ const BitVector * bv = sc.get_bit_vector(i);
if (bv != nullptr) {
result->orWith(*bv);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
index f6139c28d65..346c238f0cc 100644
--- a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
@@ -2,11 +2,8 @@
#include "flagattribute.h"
#include "load_utils.hpp"
-#include "attributeiterators.h"
#include "multinumericattribute.hpp"
-
-#include <vespa/searchlib/queryeval/emptysearch.h>
-#include <vespa/searchlib/common/bitvectoriterator.h>
+#include "multi_numeric_flag_search_context.h"
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.attribute.flag_attribute");
@@ -56,7 +53,7 @@ template <typename B>
std::unique_ptr<attribute::SearchContext>
FlagAttributeT<B>::getSearch(QueryTermSimple::UP qTerm, const attribute::SearchContextParams &) const
{
- return std::make_unique<SearchContext>(std::move(qTerm), *this, this->_mvMapping);
+ return std::make_unique<attribute::MultiNumericFlagSearchContext<typename B::BaseType, typename B::WType>>(std::move(qTerm), *this, this->_mvMapping.make_read_view(this->getCommittedDocIdLimit()), _bitVectors);
}
template <typename B>
@@ -232,38 +229,6 @@ FlagAttributeT<B>::removeOldGenerations(vespalib::GenerationHandler::generation_
_bitVectorHolder.trimHoldLists(firstUsed);
}
-template <typename B>
-FlagAttributeT<B>::SearchContext::SearchContext(QueryTermSimple::UP qTerm, const FlagAttributeT<B> & toBeSearched, const MvMapping& mv_mapping)
- : BaseSC(std::move(qTerm), toBeSearched, mv_mapping),
- _zeroHits(false)
-{
-}
-
-template <typename B>
-SearchIterator::UP
-FlagAttributeT<B>::SearchContext::createIterator(fef::TermFieldMatchData * matchData, bool strict)
-{
- if (this->valid()) {
- if (this->_low == this->_high) {
- const Attribute & attr(static_cast<const Attribute &>(this->attribute()));
- const BitVector * bv(attr.getBitVector(this->_low));
- if (bv != nullptr) {
- return BitVectorIterator::create(bv, attr.getCommittedDocIdLimit(), *matchData, strict);
- } else {
- return std::make_unique<queryeval::EmptySearch>();
- }
- } else {
- SearchIterator::UP flagIterator(
- strict
- ? new FlagAttributeIteratorStrict<typename FlagAttributeT<B>::SearchContext>(*this, matchData)
- : new FlagAttributeIteratorT<typename FlagAttributeT<B>::SearchContext>(*this, matchData));
- return flagIterator;
- }
- } else {
- return std::make_unique<queryeval::EmptySearch>();
- }
-}
-
template class FlagAttributeT<FlagBaseImpl>;
}
diff --git a/searchlib/src/vespa/searchlib/attribute/flagattribute.h b/searchlib/src/vespa/searchlib/attribute/flagattribute.h
index c1ac4e007bf..24bec517eb0 100644
--- a/searchlib/src/vespa/searchlib/attribute/flagattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/flagattribute.h
@@ -7,7 +7,6 @@
namespace search {
typedef MultiValueNumericAttribute< IntegerAttributeTemplate<int8_t>, multivalue::Value<int8_t> > FlagBaseImpl;
-typedef MultiValueNumericAttribute< IntegerAttributeTemplate<int8_t>, multivalue::Value<int8_t> > HugeFlagBaseImpl;
template <typename B>
class FlagAttributeT : public B {
@@ -15,22 +14,6 @@ public:
FlagAttributeT(const vespalib::string & baseFileName, const AttributeVector::Config & cfg);
private:
typedef AttributeVector::DocId DocId;
- using BaseSC = attribute::MultiNumericSearchContext<typename B::BaseType, typename B::WType>;
- class SearchContext : public BaseSC {
- public:
- typedef FlagAttributeT<B> Attribute;
- using MvMapping = attribute::MultiValueMapping<typename B::WType>;
- SearchContext(std::unique_ptr<QueryTermSimple> qTerm, const FlagAttributeT<B> & toBeSearched, const MvMapping& mv_mapping);
-
- std::unique_ptr<queryeval::SearchIterator>
- createIterator(fef::TermFieldMatchData * matchData, bool strict) override;
-
- private:
- bool _zeroHits;
-
- template <class SC> friend class FlagAttributeIteratorT;
- template <class SC> friend class FlagAttributeIteratorStrict;
- };
bool onLoad(vespalib::Executor *executor) override;
bool onLoadEnumerated(ReaderBase &attrReader) override;
std::unique_ptr<attribute::SearchContext>
@@ -50,20 +33,14 @@ private:
void resizeBitVectors(uint32_t neededSize);
void removeOldGenerations(vespalib::GenerationHandler::generation_t firstUsed) override;
uint32_t getOffset(int8_t value) const { return value + 128; }
- BitVector * getBitVector(typename B::BaseType value) const {
- return _bitVectors[value + 128];
- }
vespalib::GenerationHolder _bitVectorHolder;
std::vector<std::shared_ptr<BitVector> > _bitVectorStore;
std::vector<BitVector *> _bitVectors;
uint32_t _bitVectorSize;
- template <class SC> friend class FlagAttributeIteratorT;
- template <class SC> friend class FlagAttributeIteratorStrict;
};
typedef FlagAttributeT<FlagBaseImpl> FlagAttribute;
-typedef FlagAttributeT<HugeFlagBaseImpl> HugeFlagAttribute;
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
index 44e7fe9491f..b88a10652a7 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
@@ -4,7 +4,7 @@
#include "numeric_search_context.h"
#include "enumstore.h"
-#include "multi_value_mapping.h"
+#include "multi_value_mapping_read_view.h"
namespace search::attribute {
@@ -18,8 +18,8 @@ class MultiEnumSearchContext : public BaseSC
{
protected:
using DocId = ISearchContext::DocId;
- const MultiValueMapping<M>& _mv_mapping;
- const EnumStoreT<T>& _enum_store;
+ MultiValueMappingReadView<M> _mv_mapping_read_view;
+ const EnumStoreT<T>& _enum_store;
int32_t onFind(DocId docId, int32_t elemId, int32_t & weight) const override {
return find(docId, elemId, weight);
@@ -29,11 +29,11 @@ protected:
return find(docId, elemId);
}
- MultiEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<T>& enum_store);
+ MultiEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<T>& enum_store);
public:
int32_t find(DocId doc, int32_t elemId, int32_t & weight) const {
- auto indices(_mv_mapping.get(doc));
+ auto indices(_mv_mapping_read_view.get(doc));
for (uint32_t i(elemId); i < indices.size(); i++) {
T v = _enum_store.get_value(indices[i].value_ref().load_acquire());
if (this->match(v)) {
@@ -46,7 +46,7 @@ public:
}
int32_t find(DocId doc, int32_t elemId) const {
- auto indices(_mv_mapping.get(doc));
+ auto indices(_mv_mapping_read_view.get(doc));
for (uint32_t i(elemId); i < indices.size(); i++) {
T v = _enum_store.get_value(indices[i].value_ref().load_acquire());
if (this->match(v)) {
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
index cc1640a08b9..e7901199e50 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
@@ -9,9 +9,9 @@
namespace search::attribute {
template <typename T, typename BaseSC, typename M>
-MultiEnumSearchContext<T, BaseSC, M>::MultiEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<T>& enum_store)
+MultiEnumSearchContext<T, BaseSC, M>::MultiEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<T>& enum_store)
: BaseSC(toBeSearched, std::move(matcher)),
- _mv_mapping(mv_mapping),
+ _mv_mapping_read_view(mv_mapping_read_view),
_enum_store(enum_store)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.h
index b70ce2459ee..fe05afc606f 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.h
@@ -16,7 +16,7 @@ template <typename T, typename M>
class MultiNumericEnumSearchContext : public MultiEnumSearchContext<T, NumericSearchContext<NumericRangeMatcher<T>>, M>
{
public:
- MultiNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<T>& enum_store);
+ MultiNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<T>& enum_store);
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.hpp
index 9780aa7a507..f4f2c2407fc 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_enum_search_context.hpp
@@ -8,8 +8,8 @@
namespace search::attribute {
template <typename T, typename M>
-MultiNumericEnumSearchContext<T, M>::MultiNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<T>& enum_store)
- : MultiEnumSearchContext<T, NumericSearchContext<NumericRangeMatcher<T>>, M>(NumericRangeMatcher<T>(*qTerm), toBeSearched, mv_mapping, enum_store)
+MultiNumericEnumSearchContext<T, M>::MultiNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<T>& enum_store)
+ : MultiEnumSearchContext<T, NumericSearchContext<NumericRangeMatcher<T>>, M>(NumericRangeMatcher<T>(*qTerm), toBeSearched, mv_mapping_read_view, enum_store)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.cpp
new file mode 100644
index 00000000000..1c187c5dbd6
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.cpp
@@ -0,0 +1,50 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "multi_numeric_flag_search_context.h"
+#include "attributeiterators.hpp"
+#include "attributevector.h"
+#include <vespa/searchcommon/attribute/multivalue.h>
+#include <vespa/searchlib/common/bitvectoriterator.h>
+#include <vespa/searchlib/queryeval/emptysearch.h>
+#include <vespa/searchlib/query/query_term_simple.h>
+
+namespace search::attribute {
+
+using queryeval::SearchIterator;
+
+template <typename T, typename M>
+MultiNumericFlagSearchContext<T, M>::MultiNumericFlagSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, vespalib::ConstArrayRef<BitVector *> bit_vectors)
+ : MultiNumericSearchContext<T, M>(std::move(qTerm), toBeSearched, mv_mapping_read_view),
+ _bit_vectors(bit_vectors),
+ _zeroHits(false)
+{
+}
+
+template <typename T, typename M>
+std::unique_ptr<SearchIterator>
+MultiNumericFlagSearchContext<T, M>::createIterator(fef::TermFieldMatchData* matchData, bool strict)
+{
+ if (this->valid()) {
+ if (this->_low == this->_high) {
+ const AttributeVector & attr = this->attribute();
+ const BitVector * bv(get_bit_vector(this->_low));
+ if (bv != nullptr) {
+ return BitVectorIterator::create(bv, attr.getCommittedDocIdLimit(), *matchData, strict);
+ } else {
+ return std::make_unique<queryeval::EmptySearch>();
+ }
+ } else {
+ SearchIterator::UP flagIterator(
+ strict
+ ? new FlagAttributeIteratorStrict<MultiNumericFlagSearchContext>(*this, matchData)
+ : new FlagAttributeIteratorT<MultiNumericFlagSearchContext>(*this, matchData));
+ return flagIterator;
+ }
+ } else {
+ return std::make_unique<queryeval::EmptySearch>();
+ }
+}
+
+template class MultiNumericFlagSearchContext<int8_t, multivalue::Value<int8_t>>;
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.h
new file mode 100644
index 00000000000..00f7077b2d0
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_flag_search_context.h
@@ -0,0 +1,39 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "multi_numeric_search_context.h"
+
+namespace search {
+class BitVector;
+template <class SC> class FlagAttributeIteratorT;
+template <class SC> class FlagAttributeIteratorStrict;
+}
+
+namespace search::attribute {
+
+/*
+ * MultiNumericFlagSearchContext handles the creation of search iterators for
+ * a query term on a multi value numeric flag attribute vector.
+ */
+template <typename T, typename M>
+class MultiNumericFlagSearchContext : public MultiNumericSearchContext<T, M>
+{
+public:
+ MultiNumericFlagSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, vespalib::ConstArrayRef<BitVector *> bit_vectors);
+
+ std::unique_ptr<queryeval::SearchIterator>
+ createIterator(fef::TermFieldMatchData * matchData, bool strict) override;
+private:
+ vespalib::ConstArrayRef<BitVector *> _bit_vectors;
+ bool _zeroHits;
+ const BitVector* get_bit_vector(T value) const {
+ static_assert(std::is_same_v<T, int8_t>, "Flag attribute search context is only supported for int8_t data type");
+ return _bit_vectors[value + 128];
+ }
+
+ template <class SC> friend class ::search::FlagAttributeIteratorT;
+ template <class SC> friend class ::search::FlagAttributeIteratorStrict;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
index 7d43e195d00..3649b542e87 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
@@ -3,7 +3,7 @@
#pragma once
#include "numeric_search_context.h"
-#include "multi_value_mapping.h"
+#include "multi_value_mapping_read_view.h"
#include "numeric_range_matcher.h"
namespace search::attribute {
@@ -17,7 +17,7 @@ class MultiNumericSearchContext : public NumericSearchContext<NumericRangeMatche
{
private:
using DocId = ISearchContext::DocId;
- const MultiValueMapping<M>& _mv_mapping;
+ MultiValueMappingReadView<M> _mv_mapping_read_view;
int32_t onFind(DocId docId, int32_t elemId, int32_t& weight) const override final {
return find(docId, elemId, weight);
@@ -28,9 +28,9 @@ private:
}
public:
- MultiNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping);
+ MultiNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view);
int32_t find(DocId doc, int32_t elemId, int32_t & weight) const {
- auto values(_mv_mapping.get(doc));
+ auto values(_mv_mapping_read_view.get(doc));
for (uint32_t i(elemId); i < values.size(); i++) {
if (this->match(values[i].value())) {
weight = values[i].weight();
@@ -42,7 +42,7 @@ public:
}
int32_t find(DocId doc, int32_t elemId) const {
- auto values(_mv_mapping.get(doc));
+ auto values(_mv_mapping_read_view.get(doc));
for (uint32_t i(elemId); i < values.size(); i++) {
if (this->match(values[i].value())) {
return i;
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
index 8398c921ec6..15b851215f8 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
@@ -10,9 +10,9 @@
namespace search::attribute {
template <typename T, typename M>
-MultiNumericSearchContext<T, M>::MultiNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping)
+MultiNumericSearchContext<T, M>::MultiNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view)
: NumericSearchContext<NumericRangeMatcher<T>>(toBeSearched, *qTerm, false),
- _mv_mapping(mv_mapping)
+ _mv_mapping_read_view(mv_mapping_read_view)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.h
index 92650851116..3ae342be61b 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.h
@@ -17,7 +17,7 @@ class MultiStringEnumHintSearchContext : public MultiStringEnumSearchContext<M>,
public EnumHintSearchContext
{
public:
- MultiStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values);
+ MultiStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values);
~MultiStringEnumHintSearchContext() override;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.hpp
index a6b0f3f5eb9..fc1f72c940f 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_hint_search_context.hpp
@@ -6,8 +6,8 @@
namespace search::attribute {
template <typename M>
-MultiStringEnumHintSearchContext<M>::MultiStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values)
- : MultiStringEnumSearchContext<M>(std::move(qTerm), cased, toBeSearched, mv_mapping, enum_store),
+MultiStringEnumHintSearchContext<M>::MultiStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values)
+ : MultiStringEnumSearchContext<M>(std::move(qTerm), cased, toBeSearched, mv_mapping_read_view, enum_store),
EnumHintSearchContext(enum_store.get_dictionary(),
doc_id_limit, num_values)
{
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.h
index a4f05a5c9cc..1787ea0086d 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.h
@@ -15,7 +15,7 @@ template <typename M>
class MultiStringEnumSearchContext : public MultiEnumSearchContext<const char*, StringSearchContext, M>
{
public:
- MultiStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<const char*>& enum_store);
+ MultiStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<const char*>& enum_store);
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.hpp
index 02a740b06dc..1d74db04373 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_string_enum_search_context.hpp
@@ -9,8 +9,8 @@
namespace search::attribute {
template <typename M>
-MultiStringEnumSearchContext<M>::MultiStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const MultiValueMapping<M>& mv_mapping, const EnumStoreT<const char*>& enum_store)
- : MultiEnumSearchContext<const char*, StringSearchContext, M>(StringMatcher(std::move(qTerm), cased), toBeSearched, mv_mapping, enum_store)
+MultiStringEnumSearchContext<M>::MultiStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, MultiValueMappingReadView<M> mv_mapping_read_view, const EnumStoreT<const char*>& enum_store)
+ : MultiEnumSearchContext<const char*, StringSearchContext, M>(StringMatcher(std::move(qTerm), cased), toBeSearched, mv_mapping_read_view, enum_store)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
index 382f5b02642..b57269d04c6 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
@@ -3,13 +3,14 @@
#pragma once
#include "multi_value_mapping_base.h"
+#include "multi_value_mapping_read_view.h"
#include <vespa/vespalib/datastore/array_store.h>
#include <vespa/vespalib/util/address_space.h>
namespace search::attribute {
/**
- * Class for mapping from from document id to an array of values.
+ * Class for mapping from document id to an array of values.
*/
template <typename EntryT, typename RefT = vespalib::datastore::EntryRefT<19> >
class MultiValueMapping : public MultiValueMappingBase
@@ -17,6 +18,7 @@ class MultiValueMapping : public MultiValueMappingBase
public:
using MultiValueType = EntryT;
using RefType = RefT;
+ using ReadView = MultiValueMappingReadView<EntryT, RefT>;
private:
using ArrayRef = vespalib::ArrayRef<EntryT>;
using ArrayStore = vespalib::datastore::ArrayStore<EntryT, RefT>;
@@ -39,6 +41,12 @@ public:
// compacting enum store (replacing old enum index with updated enum index)
ArrayRef get_writable(uint32_t docId) { return _store.get_writable(_indices[docId].load_relaxed()); }
+ /*
+ * Readers holding a generation guard can call make_read_view() to
+ * get a read view to the multi value mapping. Array bound (read_size) must
+ * be specified by reader, cf. committed docid limit in attribute vectors.
+ */
+ ReadView make_read_view(size_t read_size) const { return ReadView(_indices.make_read_view(read_size), &_store); }
// Pass on hold list management to underlying store
void transferHoldLists(generation_t generation) { _store.transferHoldLists(generation); }
void trimHoldLists(generation_t firstUsed) { _store.trimHoldLists(firstUsed); }
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
new file mode 100644
index 00000000000..116e069e8b4
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/datastore/atomic_entry_ref.h>
+#include <vespa/vespalib/datastore/array_store.h>
+#include <vespa/vespalib/util/address_space.h>
+
+namespace search::attribute {
+
+/**
+ * Class for mapping from document id to an array of values as reader.
+ */
+template <typename EntryT, typename RefT = vespalib::datastore::EntryRefT<19> >
+class MultiValueMappingReadView
+{
+ using AtomicEntryRef = vespalib::datastore::AtomicEntryRef;
+ using Indices = vespalib::ConstArrayRef<AtomicEntryRef>;
+ using ArrayStore = vespalib::datastore::ArrayStore<EntryT, RefT>;
+
+ Indices _indices;
+ const ArrayStore* _store;
+public:
+ constexpr MultiValueMappingReadView()
+ : _indices(),
+ _store(nullptr)
+ {
+ }
+ MultiValueMappingReadView(Indices indices, const ArrayStore* store)
+ : _indices(indices),
+ _store(store)
+ {
+ }
+ vespalib::ConstArrayRef<EntryT> get(uint32_t doc_id) const { return _store->get(_indices[doc_id].load_acquire()); }
+ bool valid() const noexcept { return _store != nullptr; }
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
index 57a7c6a3b14..99963094366 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
@@ -171,7 +171,7 @@ MultiValueNumericAttribute<B, M>::getSearch(QueryTermSimple::UP qTerm,
const attribute::SearchContextParams & params) const
{
(void) params;
- return std::make_unique<attribute::MultiNumericSearchContext<T, M>>(std::move(qTerm), *this, this->_mvMapping);
+ return std::make_unique<attribute::MultiNumericSearchContext<T, M>>(std::move(qTerm), *this, this->_mvMapping.make_read_view(this->getCommittedDocIdLimit()));
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
index 201bff48be7..c35a2e55ec3 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
@@ -121,7 +121,8 @@ MultiValueNumericEnumAttribute<B, M>::getSearch(QueryTermSimple::UP qTerm,
const attribute::SearchContextParams & params) const
{
(void) params;
- return std::make_unique<attribute::MultiNumericEnumSearchContext<T, M>>(std::move(qTerm), *this, this->_mvMapping, this->_enumStore);
+ auto doc_id_limit = this->getCommittedDocIdLimit();
+ return std::make_unique<attribute::MultiNumericEnumSearchContext<T, M>>(std::move(qTerm), *this, this->_mvMapping.make_read_view(doc_id_limit), this->_enumStore);
}
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
index 688bcaf1825..4854728ca37 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
@@ -78,7 +78,8 @@ MultiValueNumericPostingAttribute<B, M>::getSearch(QueryTermSimpleUP qTerm,
{
using BaseSC = attribute::MultiNumericEnumSearchContext<typename B::BaseClass::BaseType, M>;
using SC = attribute::NumericPostingSearchContext<BaseSC, SelfType, int32_t>;
- BaseSC base_sc(std::move(qTerm), *this, this->_mvMapping, this->_enumStore);
+ auto doc_id_limit = this->getCommittedDocIdLimit();
+ BaseSC base_sc(std::move(qTerm), *this, this->_mvMapping.make_read_view(doc_id_limit), this->_enumStore);
return std::make_unique<SC>(std::move(base_sc), params, *this);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
index 212a71dad74..2d60887c23b 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
@@ -43,7 +43,8 @@ MultiValueStringAttributeT<B, M>::getSearch(QueryTermSimpleUP qTerm,
const attribute::SearchContextParams &) const
{
bool cased = this->get_match_is_cased();
- return std::make_unique<attribute::MultiStringEnumHintSearchContext<M>>(std::move(qTerm), cased, *this, this->_mvMapping, this->_enumStore, this->getCommittedDocIdLimit(), this->getStatus().getNumValues());
+ auto doc_id_limit = this->getCommittedDocIdLimit();
+ return std::make_unique<attribute::MultiStringEnumHintSearchContext<M>>(std::move(qTerm), cased, *this, this->_mvMapping.make_read_view(doc_id_limit), this->_enumStore, doc_id_limit, this->getStatus().getNumValues());
}
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
index 2c2ac48979d..d3cd338cacb 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
@@ -93,7 +93,8 @@ MultiValueStringPostingAttributeT<B, T>::getSearch(QueryTermSimpleUP qTerm,
using BaseSC = attribute::MultiStringEnumSearchContext<T>;
using SC = attribute::StringPostingSearchContext<BaseSC, SelfType, int32_t>;
bool cased = this->get_match_is_cased();
- BaseSC base_sc(std::move(qTerm), cased, *this, this->_mvMapping, this->_enumStore);
+ auto doc_id_limit = this->getCommittedDocIdLimit();
+ BaseSC base_sc(std::move(qTerm), cased, *this, this->_mvMapping.make_read_view(doc_id_limit), this->_enumStore);
return std::make_unique<SC>(std::move(base_sc), params.useBitVector(), *this);
}
diff --git a/searchlib/src/vespa/searchlib/expression/documentfieldnode.cpp b/searchlib/src/vespa/searchlib/expression/documentfieldnode.cpp
index 91e5a36a7cf..f48be061d15 100644
--- a/searchlib/src/vespa/searchlib/expression/documentfieldnode.cpp
+++ b/searchlib/src/vespa/searchlib/expression/documentfieldnode.cpp
@@ -26,7 +26,7 @@ DocumentFieldNode::~DocumentFieldNode() = default;
DocumentFieldNode::DocumentFieldNode(const DocumentFieldNode & rhs) :
DocumentAccessorNode(rhs),
- _fieldPath(rhs._fieldPath),
+ _fieldPath(),
_value(rhs._value),
_fieldName(rhs._fieldName),
_doc(nullptr)
@@ -38,7 +38,7 @@ DocumentFieldNode::operator = (const DocumentFieldNode & rhs)
{
if (this != &rhs) {
DocumentAccessorNode::operator=(rhs);
- _fieldPath = rhs._fieldPath;
+ _fieldPath.clear();
_value = rhs._value;
_fieldName = rhs._fieldName;
_doc = nullptr;
@@ -146,7 +146,7 @@ void DocumentFieldNode::onDocType(const DocumentType & docType)
_fieldPath.clear();
docType.buildFieldPath(_fieldPath, _fieldName);
if (_fieldPath.empty()) {
- throw std::runtime_error(make_string("Field %s could not be loacated in documenttype %s", _fieldName.c_str(), docType.getName().c_str()));
+ throw std::runtime_error(make_string("Field %s could not be located in documenttype %s", _fieldName.c_str(), docType.getName().c_str()));
}
}
diff --git a/searchlib/src/vespa/searchlib/expression/documentfieldnode.h b/searchlib/src/vespa/searchlib/expression/documentfieldnode.h
index fd3923bd4a0..e1038f73fa3 100644
--- a/searchlib/src/vespa/searchlib/expression/documentfieldnode.h
+++ b/searchlib/src/vespa/searchlib/expression/documentfieldnode.h
@@ -32,11 +32,13 @@ public:
DECLARE_NBO_SERIALIZE;
void visitMembers(vespalib::ObjectVisitor &visitor) const override;
DECLARE_EXPRESSIONNODE(DocumentFieldNode);
- DocumentFieldNode() : _fieldPath(), _value(), _fieldName(), _doc(NULL) { }
- ~DocumentFieldNode();
- DocumentFieldNode(vespalib::stringref name) : _fieldPath(), _value(), _fieldName(name), _doc(NULL) { }
+ DocumentFieldNode() : _fieldPath(), _value(), _fieldName(), _doc(nullptr) { }
+ ~DocumentFieldNode() override;
+ DocumentFieldNode(vespalib::stringref name) : _fieldPath(), _value(), _fieldName(name), _doc(nullptr) { }
DocumentFieldNode(const DocumentFieldNode & rhs);
DocumentFieldNode & operator = (const DocumentFieldNode & rhs);
+ DocumentFieldNode(DocumentFieldNode && rhs) noexcept = default;
+ DocumentFieldNode & operator = (DocumentFieldNode && rhs) noexcept = default;
const vespalib::string & getFieldName() const override { return _fieldName; }
private:
class Handler : public document::fieldvalue::IteratorHandler {
diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
index 2a12867dd33..731306d1bea 100644
--- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
+++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
@@ -28,6 +28,10 @@ constexpr int TRACE_SKIP_POS = 10;
using Accept = Blueprint::AcceptInput;
+vespalib::string describe(const vespalib::string &feature_name) {
+ return BlueprintResolver::describe_feature(feature_name);
+}
+
bool is_compatible(bool is_object, Accept accept_type) {
return ((accept_type == Accept::ANY) ||
((accept_type == Accept::OBJECT) == (is_object)));
@@ -122,7 +126,7 @@ struct Compiler : public Blueprint::DependencyHandler {
should_trace |= (i < TRACE_SKIP_POS);
should_trace |= ((end - pos) < (MAX_TRACE_SIZE - TRACE_SKIP_POS));
if (should_trace) {
- trace += fmt(" ... needed by rank feature '%s'\n", pos->parser.featureName().c_str());
+ trace += fmt(" ... needed by %s\n", describe(pos->parser.featureName()).c_str());
} else if (i == TRACE_SKIP_POS) {
trace += fmt(" (skipped %zu entries)\n", (n - MAX_TRACE_SIZE) + 1);
}
@@ -135,9 +139,9 @@ struct Compiler : public Blueprint::DependencyHandler {
failed_set.insert(feature_name);
auto trace = make_trace(skip_self);
if (trace.empty()) {
- LOG(warning, "invalid rank feature '%s': %s", feature_name.c_str(), reason.c_str());
+ LOG(warning, "invalid %s: %s", describe(feature_name).c_str(), reason.c_str());
} else {
- LOG(warning, "invalid rank feature '%s': %s\n%s", feature_name.c_str(), reason.c_str(), trace.c_str());
+ LOG(warning, "invalid %s: %s\n%s", describe(feature_name).c_str(), reason.c_str(), trace.c_str());
}
}
probe_stack();
@@ -264,6 +268,22 @@ BlueprintResolver::BlueprintResolver(const BlueprintFactory &factory,
{
}
+vespalib::string
+BlueprintResolver::describe_feature(const vespalib::string &name)
+{
+ auto parser = std::make_unique<FeatureNameParser>(name);
+ if (parser->valid() &&
+ (parser->baseName() == "rankingExpression") &&
+ (parser->parameters().size() == 1) &&
+ parser->output().empty())
+ {
+ auto param = parser->parameters()[0];
+ param = param.substr(0, param.find("@"));
+ return fmt("function '%s'", param.c_str());
+ }
+ return fmt("rank feature '%s'", name.c_str());
+}
+
void
BlueprintResolver::addSeed(vespalib::stringref feature)
{
diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.h b/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
index 80320ae780a..3e3b5879518 100644
--- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
+++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
@@ -100,6 +100,12 @@ public:
BlueprintResolver(const BlueprintFactory &factory,
const IIndexEnvironment &indexEnv);
+ // Describe a feature based on its name (intended for log messages)
+ //
+ // rankingExpression(foo@hash) -> function 'foo'
+ // feature -> rank feature 'feature'
+ static vespalib::string describe_feature(const vespalib::string &name);
+
/**
* Add a feature name to the list of seeds. During compilation,
* blueprints for all seeds and dependencies will be instantiated
diff --git a/searchlib/src/vespa/searchlib/fef/verify_feature.cpp b/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
index d61d7faef0f..85d1daffd01 100644
--- a/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
+++ b/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
@@ -19,8 +19,8 @@ bool verifyFeature(const BlueprintFactory &factory,
resolver.addSeed(featureName);
bool result = resolver.compile();
if (!result) {
- LOG(error, "rank feature verification failed: %s (%s)",
- featureName.c_str(), desc.c_str());
+ LOG(error, "verification failed: %s (%s)",
+ BlueprintResolver::describe_feature(featureName).c_str(), desc.c_str());
}
return result;
}
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
index 54f95e1c543..3f2a1bb1969 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
@@ -21,9 +21,10 @@ void
DirectTensorStore::TensorBufferType::cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx)
{
TensorSP* elem = static_cast<TensorSP*>(buffer) + offset;
+ const auto& empty = empty_entry();
for (size_t i = 0; i < num_elems; ++i) {
clean_ctx.extraBytesCleaned((*elem)->get_memory_usage().allocatedBytes());
- *elem = _emptyEntry;
+ *elem = empty;
++elem;
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
index 776920ab930..1f112f1ea28 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
@@ -24,7 +24,7 @@ private:
class TensorBufferType : public vespalib::datastore::BufferType<TensorSP> {
private:
using ParentType = BufferType<TensorSP>;
- using ParentType::_emptyEntry;
+ using ParentType::empty_entry;
using CleanContext = typename ParentType::CleanContext;
public:
TensorBufferType();
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp
index 2e6d771a870..a668387e5bd 100644
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp
@@ -166,9 +166,10 @@ void
StreamedValueStore::TensorBufferType::cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx)
{
TensorEntry::SP* elem = static_cast<TensorEntry::SP*>(buffer) + offset;
+ const auto& empty = empty_entry();
for (size_t i = 0; i < num_elems; ++i) {
clean_ctx.extraBytesCleaned((*elem)->get_memory_usage().allocatedBytes());
- *elem = _emptyEntry;
+ *elem = empty;
++elem;
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h
index 7d83e9f3335..29201dc0e61 100644
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h
@@ -51,7 +51,7 @@ private:
class TensorBufferType : public vespalib::datastore::BufferType<TensorEntry::SP> {
private:
using ParentType = BufferType<TensorEntry::SP>;
- using ParentType::_emptyEntry;
+ using ParentType::empty_entry;
using CleanContext = typename ParentType::CleanContext;
public:
TensorBufferType() noexcept;
diff --git a/storage/src/tests/storageserver/mergethrottlertest.cpp b/storage/src/tests/storageserver/mergethrottlertest.cpp
index 77c674ff1e6..ee18384598e 100644
--- a/storage/src/tests/storageserver/mergethrottlertest.cpp
+++ b/storage/src/tests/storageserver/mergethrottlertest.cpp
@@ -16,6 +16,7 @@
#include <iterator>
#include <vector>
#include <chrono>
+#include <climits>
#include <thread>
using namespace document;
diff --git a/storage/src/vespa/storage/storageserver/documentapiconverter.cpp b/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
index c465f974a9b..9a5fb2bdd13 100644
--- a/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
+++ b/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
@@ -16,6 +16,7 @@
#include <vespa/storageapi/message/stat.h>
#include <vespa/storageapi/message/visitor.h>
#include <vespa/messagebus/error.h>
+#include <climits>
#include <vespa/log/log.h>
LOG_SETUP(".documentapiconverter");
diff --git a/testutil/src/main/java/com/yahoo/test/OrderTester.java b/testutil/src/main/java/com/yahoo/test/OrderTester.java
index 4acba4ee7fe..cc28ca9f469 100644
--- a/testutil/src/main/java/com/yahoo/test/OrderTester.java
+++ b/testutil/src/main/java/com/yahoo/test/OrderTester.java
@@ -15,8 +15,8 @@ import java.util.List;
*
*/
-public abstract class OrderTester<T extends Comparable<T>> {
- private ArrayList<List<T>> groups = new ArrayList<>();
+public abstract class OrderTester<T extends Comparable<? super T>> {
+ private final ArrayList<List<T>> groups = new ArrayList<>();
abstract protected void lessTest(T a, T b);
abstract protected void greaterTest(T a, T b);
@@ -24,7 +24,7 @@ public abstract class OrderTester<T extends Comparable<T>> {
@SafeVarargs
@SuppressWarnings("varargs")
- private final OrderTester<T> addGroup(T... group) {
+ private OrderTester<T> addGroup(T... group) {
groups.add(Arrays.asList(group));
return this;
}
diff --git a/testutil/src/main/java/com/yahoo/test/TotalOrderTester.java b/testutil/src/main/java/com/yahoo/test/TotalOrderTester.java
index 850369fbc2e..e95bc056ba8 100644
--- a/testutil/src/main/java/com/yahoo/test/TotalOrderTester.java
+++ b/testutil/src/main/java/com/yahoo/test/TotalOrderTester.java
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertTrue;
* @author Vegard Sjonfjell
*/
-public class TotalOrderTester<T extends Comparable<T>> extends OrderTester<T> {
+public class TotalOrderTester<T extends Comparable<? super T>> extends OrderTester<T> {
protected void lessTest(T a, T b) throws AssertionError {
assertTrue(a + " must be less than " + b, a.compareTo(b) <= -1);
}
diff --git a/vespa-hadoop/pom.xml b/vespa-hadoop/pom.xml
index 2530e461354..6fe87541448 100644
--- a/vespa-hadoop/pom.xml
+++ b/vespa-hadoop/pom.xml
@@ -126,7 +126,21 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>vespa-feed-client</artifactId>
<version>${project.version}</version>
- <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespa-feed-client-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <!-- Jackson dependencies used in this module -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
index 7b3e488a5a5..29c572422c3 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
@@ -236,12 +236,12 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
+ handlerTimeout.toMillis(),
MILLISECONDS);
- Path requestPath = new Path(request.getUri());
+ Path requestPath = new Path(request.getUri(), __ -> { }); // No segment validation here, as document IDs can be anything.
for (String path : handlers.keySet())
if (requestPath.matches(path)) {
Map<Method, Handler> methods = handlers.get(path);
if (methods.containsKey(request.getMethod()))
- return methods.get(request.getMethod()).handle(request, new DocumentPath(requestPath), responseHandler);
+ return methods.get(request.getMethod()).handle(request, new DocumentPath(requestPath, request.getUri().getRawPath()), responseHandler);
if (request.getMethod() == OPTIONS)
options(methods.keySet(), responseHandler);
@@ -1458,10 +1458,12 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private static class DocumentPath {
private final Path path;
+ private final String rawPath;
private final Optional<Group> group;
- DocumentPath(Path path) {
+ DocumentPath(Path path, String rawPath) {
this.path = requireNonNull(path);
+ this.rawPath = requireNonNull(rawPath);
this.group = Optional.ofNullable(path.get("number")).map(unsignedLongParser::parse).map(Group::of)
.or(() -> Optional.ofNullable(path.get("group")).map(Group::of));
}
@@ -1470,10 +1472,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
return new DocumentId("id:" + requireNonNull(path.get("namespace")) +
":" + requireNonNull(path.get("documentType")) +
":" + group.map(Group::docIdPart).orElse("") +
- ":" + requireNonNull(path.getRest()));
+ ":" + String.join("/", requireNonNull(path.getRest()).segments())); // :'(
}
- String rawPath() { return path.asString(); }
+ String rawPath() { return rawPath; }
Optional<String> documentType() { return Optional.ofNullable(path.get("documentType")); }
Optional<String> namespace() { return Optional.ofNullable(path.get("namespace")); }
Optional<Group> group() { return group; }
diff --git a/vespaclient-core/src/main/java/com/yahoo/feedapi/MessageBusSessionFactory.java b/vespaclient-core/src/main/java/com/yahoo/feedapi/MessageBusSessionFactory.java
index 514669fe0ac..d9b1190aaed 100755
--- a/vespaclient-core/src/main/java/com/yahoo/feedapi/MessageBusSessionFactory.java
+++ b/vespaclient-core/src/main/java/com/yahoo/feedapi/MessageBusSessionFactory.java
@@ -18,7 +18,8 @@ public class MessageBusSessionFactory implements SessionFactory {
public MessageBusSessionFactory(MessagePropertyProcessor processor) {
this(processor, null, null);
}
-
+
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
private MessageBusSessionFactory(MessagePropertyProcessor processor,
DocumentmanagerConfig documentmanagerConfig,
SlobroksConfig slobroksConfig) {
diff --git a/vespaclient-core/src/main/java/com/yahoo/feedapi/MessagePropertyProcessor.java b/vespaclient-core/src/main/java/com/yahoo/feedapi/MessagePropertyProcessor.java
index e5da51f0918..84fbe63a576 100644
--- a/vespaclient-core/src/main/java/com/yahoo/feedapi/MessagePropertyProcessor.java
+++ b/vespaclient-core/src/main/java/com/yahoo/feedapi/MessagePropertyProcessor.java
@@ -33,10 +33,18 @@ public class MessagePropertyProcessor implements ConfigSubscriber.SingleSubscrib
private String defaultDocprocChain = null;
private boolean defaultAbortOnDocumentError = true;
private boolean defaultAbortOnSendError = true;
- private final LoadTypeSet loadTypes;
+ private final LoadTypeSet loadTypes; // TODO remove on Vespa 8
private boolean configChanged = false;
+ public MessagePropertyProcessor(FeederConfig config) {
+ loadTypes = new LoadTypeSet();
+ configure(config);
+ }
+ /**
+ * @deprecated load types are deprecated. Use constructor without LoadTypeConfig instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public MessagePropertyProcessor(FeederConfig config, LoadTypeConfig loadTypeCfg) {
loadTypes = new LoadTypeSet();
configure(config, loadTypeCfg);
@@ -127,11 +135,19 @@ public class MessagePropertyProcessor implements ConfigSubscriber.SingleSubscrib
return feederOptions;
}
+ /**
+ * @deprecated load types are deprecated. configure without LoadTypeConfig instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
public synchronized void configure(FeederConfig config, LoadTypeConfig loadTypeConfig) {
loadTypes.configure(loadTypeConfig);
configure(config);
}
+ /**
+ * @deprecated load types are deprecated
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
LoadTypeSet getLoadTypes() {
return loadTypes;
}
@@ -175,7 +191,7 @@ public class MessagePropertyProcessor implements ConfigSubscriber.SingleSubscrib
private boolean abortOnDocumentError;
private boolean abortOnFeedError;
private boolean createIfNonExistent;
- private LoadType loadType;
+ private LoadType loadType; // TODO remove on Vespa 8
private int traceLevel;
PropertySetter(Route route, long timeout, long totalTimeout, DocumentProtocol.Priority priority, LoadType loadType,
diff --git a/vespaclient-java/src/main/java/com/yahoo/dummyreceiver/DummyReceiver.java b/vespaclient-java/src/main/java/com/yahoo/dummyreceiver/DummyReceiver.java
index cd524c07f73..c2985996bd0 100755
--- a/vespaclient-java/src/main/java/com/yahoo/dummyreceiver/DummyReceiver.java
+++ b/vespaclient-java/src/main/java/com/yahoo/dummyreceiver/DummyReceiver.java
@@ -4,7 +4,6 @@ package com.yahoo.dummyreceiver;
import com.yahoo.concurrent.DaemonThreadFactory;
import com.yahoo.documentapi.messagebus.MessageBusDocumentAccess;
import com.yahoo.documentapi.messagebus.MessageBusParams;
-import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
import com.yahoo.documentapi.messagebus.protocol.PutDocumentMessage;
import com.yahoo.documentapi.messagebus.protocol.RemoveDocumentMessage;
import com.yahoo.documentapi.messagebus.protocol.UpdateDocumentMessage;
@@ -68,7 +67,7 @@ public class DummyReceiver implements MessageHandler {
}
private void init() {
- MessageBusParams params = new MessageBusParams(new LoadTypeSet());
+ MessageBusParams params = new MessageBusParams();
params.setRPCNetworkParams(new RPCNetworkParams().setIdentity(new Identity(name)));
params.setDocumentManagerConfigId("client");
params.getMessageBusParams().setMaxPendingCount(0);
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespafeeder/Arguments.java b/vespaclient-java/src/main/java/com/yahoo/vespafeeder/Arguments.java
index 8f5db4adf97..52f2857c7e5 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespafeeder/Arguments.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespafeeder/Arguments.java
@@ -153,7 +153,7 @@ public class Arguments {
}
}
- propertyProcessor = new MessagePropertyProcessor(getFeederConfig(), new LoadTypeConfig(new LoadTypeConfig.Builder()));
+ propertyProcessor = new MessagePropertyProcessor(getFeederConfig());
}
private String getParam(List<String> args, String arg) throws IllegalArgumentException {
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespaget/DocumentRetriever.java b/vespaclient-java/src/main/java/com/yahoo/vespaget/DocumentRetriever.java
index b9917533a62..2454f5c8627 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespaget/DocumentRetriever.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespaget/DocumentRetriever.java
@@ -27,18 +27,32 @@ import java.util.Map;
*
* @author bjorncs
*/
+@SuppressWarnings("removal") // TODO: Remove on Vespa 8
public class DocumentRetriever {
private final ClusterList clusterList;
private final DocumentAccessFactory documentAccessFactory;
private final ClientParameters params;
- private final LoadTypeSet loadTypeSet;
+ private final LoadTypeSet loadTypeSet; // TODO remove on Vespa 8
private MessageBusSyncSession session;
private MessageBusDocumentAccess documentAccess;
public DocumentRetriever(ClusterList clusterList,
DocumentAccessFactory documentAccessFactory,
+ ClientParameters params) {
+ this.clusterList = clusterList;
+ this.documentAccessFactory = documentAccessFactory;
+ this.loadTypeSet = new LoadTypeSet(); // TODO remove on Vespa 8
+ this.params = params;
+ }
+
+ /**
+ * @deprecated load types are deprecated. Use constructor without LoadTypeSet instead.
+ */
+ @Deprecated(forRemoval = true) // TODO: Remove on Vespa 8
+ public DocumentRetriever(ClusterList clusterList,
+ DocumentAccessFactory documentAccessFactory,
LoadTypeSet loadTypeSet,
ClientParameters params) {
this.clusterList = clusterList;
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespaget/Main.java b/vespaclient-java/src/main/java/com/yahoo/vespaget/Main.java
index fd2c9e964f7..7596246d16e 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespaget/Main.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespaget/Main.java
@@ -38,11 +38,12 @@ public class Main {
Runtime.getRuntime().addShutdownHook(new Thread(documentRetriever::shutdown));
}
+ @SuppressWarnings("removal") // TODO: Remove on Vespa 8
private static DocumentRetriever createDocumentRetriever(ClientParameters params) {
return new DocumentRetriever(
new ClusterList("client"),
new DocumentAccessFactory(),
- new LoadTypeSet(params.configId),
+ new LoadTypeSet(params.configId), // TODO: Remove on Vespa 8
params
);
}
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
index 4d79f2f2e1d..0e64f824b63 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
@@ -10,7 +10,6 @@ import com.yahoo.documentapi.VisitorParameters;
import com.yahoo.documentapi.VisitorSession;
import com.yahoo.documentapi.messagebus.MessageBusDocumentAccess;
import com.yahoo.documentapi.messagebus.MessageBusParams;
-import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
import com.yahoo.log.LogSetup;
import com.yahoo.messagebus.StaticThrottlePolicy;
@@ -36,7 +35,7 @@ import java.util.stream.Collectors;
public class VdsVisit {
private VdsVisitParameters params;
- private MessageBusParams mbparams = new MessageBusParams(new LoadTypeSet());
+ private MessageBusParams mbparams = new MessageBusParams();
private VisitorSession session;
private final VisitorSessionAccessorFactory sessionAccessorFactory;
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisitTarget.java b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisitTarget.java
index d1fbde7dd42..7dfa3a2cf2e 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisitTarget.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisitTarget.java
@@ -8,7 +8,6 @@ import com.yahoo.documentapi.VisitorDestinationParameters;
import com.yahoo.documentapi.VisitorDestinationSession;
import com.yahoo.documentapi.messagebus.MessageBusDocumentAccess;
import com.yahoo.documentapi.messagebus.MessageBusParams;
-import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
import java.util.logging.Level;
import com.yahoo.log.LogSetup;
import com.yahoo.messagebus.network.Identity;
@@ -209,7 +208,7 @@ public class VdsVisitTarget {
public void run() throws Exception {
initShutdownHook();
log.log(Level.FINE, "Starting VdsVisitTarget");
- MessageBusParams mbusParams = new MessageBusParams(new LoadTypeSet());
+ MessageBusParams mbusParams = new MessageBusParams();
mbusParams.getRPCNetworkParams().setIdentity(new Identity(slobrokAddress));
if (port > 0) {
diff --git a/vespaclient-java/src/test/java/com/yahoo/vespaget/DocumentRetrieverTest.java b/vespaclient-java/src/test/java/com/yahoo/vespaget/DocumentRetrieverTest.java
index 8d7483c2196..30d117ab105 100644
--- a/vespaclient-java/src/test/java/com/yahoo/vespaget/DocumentRetrieverTest.java
+++ b/vespaclient-java/src/test/java/com/yahoo/vespaget/DocumentRetrieverTest.java
@@ -129,7 +129,6 @@ public class DocumentRetrieverTest {
return new DocumentRetriever(
clusterList,
mockedFactory,
- new LoadTypeSet(),
params);
}
@@ -145,7 +144,7 @@ public class DocumentRetrieverTest {
when(mockedSession.syncSend(any())).thenReturn(createDocumentReply(DOC_ID_1));
- LoadTypeSet loadTypeSet = new LoadTypeSet();
+ LoadTypeSet loadTypeSet = new LoadTypeSet(); // TODO remove on Vespa 8
loadTypeSet.addLoadType(1, "loadtype", DocumentProtocol.Priority.HIGH_1);
DocumentRetriever documentRetriever = new DocumentRetriever(
new ClusterList(),
diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json
index 20c7d435964..642fdd5c16f 100644
--- a/vespajlib/abi-spec.json
+++ b/vespajlib/abi-spec.json
@@ -583,6 +583,7 @@
"final"
],
"methods": [
+ "public static com.yahoo.path.Path from(java.util.List)",
"public boolean isChildOf(com.yahoo.path.Path)",
"public com.yahoo.path.Path append(java.lang.String)",
"public com.yahoo.path.Path append(com.yahoo.path.Path)",
@@ -3232,6 +3233,7 @@
"methods": [
"public static boolean isTextCharacter(int)",
"public static java.util.OptionalInt validateTextString(java.lang.String)",
+ "public static boolean isValidTextString(java.lang.String)",
"public static boolean isDisplayable(int)",
"public static java.lang.String stripInvalidCharacters(java.lang.String)",
"public static java.lang.String truncate(java.lang.String, int)",
diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml
index b4898b14b86..ed6ae3678f4 100644
--- a/vespajlib/pom.xml
+++ b/vespajlib/pom.xml
@@ -36,6 +36,11 @@
<artifactId>aircompressor</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <scope>compile</scope>
+ </dependency>
<!-- provided scope -->
<dependency>
diff --git a/vespajlib/src/main/java/ai/vespa/validation/Name.java b/vespajlib/src/main/java/ai/vespa/validation/Name.java
index b275917dcff..a6ab456c285 100644
--- a/vespajlib/src/main/java/ai/vespa/validation/Name.java
+++ b/vespajlib/src/main/java/ai/vespa/validation/Name.java
@@ -3,8 +3,6 @@ package ai.vespa.validation;
import java.util.regex.Pattern;
-import static ai.vespa.validation.Validation.requireMatch;
-
/**
* A name has from 1 to 64 {@link String} characters which may be letters, numbers,
* dashes or underscores, and must start with a letter.
diff --git a/vespajlib/src/main/java/ai/vespa/validation/Validation.java b/vespajlib/src/main/java/ai/vespa/validation/Validation.java
index 816ca931c80..292cb2f0aa5 100644
--- a/vespajlib/src/main/java/ai/vespa/validation/Validation.java
+++ b/vespajlib/src/main/java/ai/vespa/validation/Validation.java
@@ -37,36 +37,36 @@ public class Validation {
/** Requires the value to match the given pattern. */
public static String requireMatch(String value, String description, Pattern pattern) {
- return require(pattern.matcher(value).matches(), value, description, "must match '" + pattern + "'");
+ return require(pattern.matcher(value).matches(), value, description + " must match '" + pattern + "'");
}
/** Requires the value to be non-blank. */
public static String requireNonBlank(String value, String description) {
- return require( ! value.isBlank(), value, description, "cannot be blank");
+ return require( ! value.isBlank(), value, description + " cannot be blank");
}
/** Requires the value to be at least the lower bound. */
public static <T extends Comparable<? super T>> T requireAtLeast(T value, String description, T lower) {
- return require(lower.compareTo(value) <= 0, value, description, "must be at least '" + lower + "'");
+ return require(lower.compareTo(value) <= 0, value, description + " must be at least '" + lower + "'");
}
/** Requires the value to be at most the upper bound. */
public static <T extends Comparable<? super T>> T requireAtMost(T value, String description, T upper) {
- return require(upper.compareTo(value) >= 0, value, description, "must be at most '" + upper + "'");
+ return require(upper.compareTo(value) >= 0, value, description + " must be at most '" + upper + "'");
}
/** Requires the value to be at least the lower bound, and at most the upper bound. */
public static <T extends Comparable<? super T>> T requireInRange(T value, String description, T lower, T upper) {
if (lower.compareTo(upper) > 0) throw new IllegalArgumentException("lower bound cannot be greater than upper bound, " +
"but got '" + lower + "' > '" + upper + "'");
- return require(lower.compareTo(value) <= 0 && upper.compareTo(value) >= 0, value, description,
- "must be at least '" + lower + "' and at most '" + upper + "'");
+ return require(lower.compareTo(value) <= 0 && upper.compareTo(value) >= 0, value,
+ description + " must be at least '" + lower + "' and at most '" + upper + "'");
}
/** Returns the argument if the condition is true, otherwise throws. */
- public static <T> T require(boolean condition, T value, String description, String requirement) {
+ public static <T> T require(boolean condition, T value, String description) {
if (condition) return value;
- throw new IllegalArgumentException(description + " " + requirement + ", but got: '" + value + "'");
+ throw new IllegalArgumentException(description + ", but got: '" + value + "'");
}
} \ No newline at end of file
diff --git a/vespajlib/src/main/java/ai/vespa/validation/package-info.java b/vespajlib/src/main/java/ai/vespa/validation/package-info.java
new file mode 100644
index 00000000000..edbab3a6fd1
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/validation/package-info.java
@@ -0,0 +1,5 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package ai.vespa.validation;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/vespajlib/src/main/java/com/yahoo/compress/ArchiveStreamReader.java b/vespajlib/src/main/java/com/yahoo/compress/ArchiveStreamReader.java
new file mode 100644
index 00000000000..e65a645f5be
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/compress/ArchiveStreamReader.java
@@ -0,0 +1,216 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.compress;
+
+import com.yahoo.path.Path;
+import com.yahoo.yolean.Exceptions;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.Objects;
+import java.util.OptionalLong;
+import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Helper class for safely reading files from a compressed archive.
+ *
+ * @author mpolden
+ */
+public class ArchiveStreamReader implements AutoCloseable {
+
+ private final ArchiveInputStream archiveInputStream;
+ private final Options options;
+
+ private long totalRead = 0;
+ private long entriesRead = 0;
+
+ private ArchiveStreamReader(ArchiveInputStream archiveInputStream, Options options) {
+ this.archiveInputStream = Objects.requireNonNull(archiveInputStream);
+ this.options = Objects.requireNonNull(options);
+ }
+
+ /** Create reader for an inputStream containing a tar.gz file */
+ public static ArchiveStreamReader ofTarGzip(InputStream inputStream, Options options) {
+ return new ArchiveStreamReader(new TarArchiveInputStream(Exceptions.uncheck(() -> new GZIPInputStream(inputStream))), options);
+ }
+
+ /** Create reader for an inputStream containing a ZIP file */
+ public static ArchiveStreamReader ofZip(InputStream inputStream, Options options) {
+ return new ArchiveStreamReader(new ZipArchiveInputStream(inputStream), options);
+ }
+
+ /**
+ * Read the next file in this archive and write it to given outputStream. Returns information about the read archive
+ * file, or null if there are no more files to read.
+ */
+ public ArchiveFile readNextTo(OutputStream outputStream) {
+ ArchiveEntry entry;
+ try {
+ while ((entry = archiveInputStream.getNextEntry()) != null) {
+ Path path = Path.fromString(requireNormalized(entry.getName(), options.allowDotSegment));
+ if (isSymlink(entry)) throw new IllegalArgumentException("Archive entry " + path + " is a symbolic link, which is unsupported");
+ if (entry.isDirectory()) continue;
+ if (!options.pathPredicate.test(path.toString())) continue;
+ if (++entriesRead > options.maxEntries) throw new IllegalArgumentException("Attempted to read more entries than entry limit of " + options.maxEntries);
+
+ long size = 0;
+ byte[] buffer = new byte[2048];
+ int read;
+ while ((read = archiveInputStream.read(buffer)) != -1) {
+ totalRead += read;
+ size += read;
+ if (totalRead > options.maxSize) throw new IllegalArgumentException("Total size of archive exceeds size limit of " + options.maxSize + " bytes");
+ if (read > options.maxEntrySize) {
+ if (!options.truncateEntry) throw new IllegalArgumentException("Size of entry " + path + " exceeded entry size limit of " + options.maxEntrySize + " bytes");
+ } else {
+ outputStream.write(buffer, 0, read);
+ }
+ }
+ return new ArchiveFile(path, crc32(entry), size);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return null;
+ }
+
+ @Override
+ public void close() {
+ Exceptions.uncheck(archiveInputStream::close);
+ }
+
+ /** Information about a file extracted from a compressed archive */
+ public static class ArchiveFile {
+
+ private final Path path;
+ private final OptionalLong crc32;
+ private final long size;
+
+ public ArchiveFile(Path name, OptionalLong crc32, long size) {
+ this.path = Objects.requireNonNull(name);
+ this.crc32 = Objects.requireNonNull(crc32);
+ if (crc32.isPresent()) {
+ requireNonNegative("crc32", crc32.getAsLong());
+ }
+ this.size = requireNonNegative("size", size);
+ }
+
+ /** The path of this file inside its containing archive */
+ public Path path() {
+ return path;
+ }
+
+ /** The CRC-32 checksum of this file, if any */
+ public OptionalLong crc32() {
+ return crc32;
+ }
+
+ /** The decompressed size of this file */
+ public long size() {
+ return size;
+ }
+
+ }
+
+ /** Get the CRC-32 checksum of given archive entry, if any */
+ private static OptionalLong crc32(ArchiveEntry entry) {
+ long crc32 = -1;
+ if (entry instanceof ZipArchiveEntry) {
+ crc32 = ((ZipArchiveEntry) entry).getCrc();
+ }
+ return crc32 > -1 ? OptionalLong.of(crc32) : OptionalLong.empty();
+ }
+
+ private static boolean isSymlink(ArchiveEntry entry) {
+ // Symlinks inside ZIP files are not part of the ZIP spec and are only supported by some implementations, such
+ // as Info-ZIP.
+ //
+ // Commons Compress only has limited support for symlinks as they are only detected when the ZIP file is read
+ // through org.apache.commons.compress.archivers.zip.ZipFile. This is not the case in this class, because it must
+ // support reading ZIP files from generic input streams. The check below thus always returns false.
+ if (entry instanceof ZipArchiveEntry) return ((ZipArchiveEntry) entry).isUnixSymlink();
+ if (entry instanceof TarArchiveEntry) return ((TarArchiveEntry) entry).isSymbolicLink();
+ throw new IllegalArgumentException("Unsupported archive entry " + entry.getClass().getSimpleName() + ", cannot check for symbolic link");
+ }
+
+ private static String requireNormalized(String name, boolean allowDotSegment) {
+ for (var part : name.split("/")) {
+ if (part.isEmpty() || (!allowDotSegment && part.equals(".")) || part.equals("..")) {
+ throw new IllegalArgumentException("Unexpected non-normalized path found in zip content: '" + name + "'");
+ }
+ }
+ return name;
+ }
+
+ private static long requireNonNegative(String field, long n) {
+ if (n < 0) throw new IllegalArgumentException(field + " cannot be negative, got " + n);
+ return n;
+ }
+
+ /** Options for reading entries of an archive */
+ public static class Options {
+
+ private long maxSize = 8 * (long) Math.pow(1024, 3); // 8 GB
+ private long maxEntrySize = Long.MAX_VALUE;
+ private long maxEntries = Long.MAX_VALUE;
+ private boolean truncateEntry = false;
+ private boolean allowDotSegment = false;
+ private Predicate<String> pathPredicate = (path) -> true;
+
+ private Options() {}
+
+ /** Returns the standard set of read options */
+ public static Options standard() {
+ return new Options();
+ }
+
+ /** Set the maximum total size of decompressed entries. Default is 8 GB */
+ public Options maxSize(long size) {
+ this.maxSize = requireNonNegative("size", size);
+ return this;
+ }
+
+ /** Set the maximum size a decompressed entry. Default is no limit */
+ public Options maxEntrySize(long size) {
+ this.maxEntrySize = requireNonNegative("size", size);
+ return this;
+ }
+
+ /** Set the maximum number of entries to decompress. Default is no limit */
+ public Options maxEntries(long count) {
+ this.maxEntries = requireNonNegative("count", count);
+ return this;
+ }
+
+ /**
+ * Set whether to truncate the content of an entry exceeding the configured size limit, instead of throwing.
+ * Default is to throw.
+ */
+ public Options truncateEntry(boolean truncate) {
+ this.truncateEntry = truncate;
+ return this;
+ }
+
+ /** Set a predicate that an entry path must match in order to be extracted. Default is to extract all entries */
+ public Options pathPredicate(Predicate<String> predicate) {
+ this.pathPredicate = predicate;
+ return this;
+ }
+
+ /** Set whether to allow single-dot segments in entry paths. Default is false */
+ public Options allowDotSegment(boolean allow) {
+ this.allowDotSegment = allow;
+ return this;
+ }
+
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/compress/ZstdOuputStream.java b/vespajlib/src/main/java/com/yahoo/compress/ZstdOutputStream.java
index c423f4c76bf..f439ee03ea6 100644
--- a/vespajlib/src/main/java/com/yahoo/compress/ZstdOuputStream.java
+++ b/vespajlib/src/main/java/com/yahoo/compress/ZstdOutputStream.java
@@ -7,7 +7,7 @@ import java.io.OutputStream;
/**
* @author bjorncs
*/
-public class ZstdOuputStream extends OutputStream {
+public class ZstdOutputStream extends OutputStream {
private final ZstdCompressor compressor = new ZstdCompressor();
@@ -19,13 +19,13 @@ public class ZstdOuputStream extends OutputStream {
private int inputPosition = 0;
private boolean isClosed = false;
- public ZstdOuputStream(OutputStream out, int inputBufferSize) {
+ public ZstdOutputStream(OutputStream out, int inputBufferSize) {
this.out = out;
this.inputBuffer = new byte[inputBufferSize];
this.outputBuffer = new byte[ZstdCompressor.getMaxCompressedLength(inputBufferSize)];
}
- public ZstdOuputStream(OutputStream out) {
+ public ZstdOutputStream(OutputStream out) {
this(out, DEFAULT_INPUT_BUFFER_SIZE);
}
diff --git a/vespajlib/src/main/java/com/yahoo/io/NativeIO.java b/vespajlib/src/main/java/com/yahoo/io/NativeIO.java
index 3cb1cdf5242..28c3f21b24c 100644
--- a/vespajlib/src/main/java/com/yahoo/io/NativeIO.java
+++ b/vespajlib/src/main/java/com/yahoo/io/NativeIO.java
@@ -18,49 +18,70 @@ import com.sun.jna.Platform;
* Provides functionality only possible through native C library.
*/
public class NativeIO {
- private final static Logger logger = Logger.getLogger(NativeIO.class.getName());
- private final static String DISABLE_NATIVE_IO = "DISABLE_NATIVE_IO";
+ private static final Logger logger = Logger.getLogger(NativeIO.class.getName());
+ private static final String DISABLE_NATIVE_IO = "DISABLE_NATIVE_IO";
private static final int POSIX_FADV_DONTNEED = 4; // See /usr/include/linux/fadvise.h
- private static boolean initialized = false;
- private static boolean disabled = false;
- private static Throwable initError = null;
- static {
- try {
- if (Platform.isLinux()) {
- disabled = System.getenv().containsKey(DISABLE_NATIVE_IO);
- if (!disabled) {
- Native.register(Platform.C_LIBRARY_NAME);
- initialized = true;
+ private static final InitResult fdField = new InitResult();
+ private static class InitResult {
+ private final boolean initialized;
+ private final boolean enabled;
+ private final Field fdField;
+ private final Throwable initError;
+ InitResult() {
+ boolean initComplete = false;
+ boolean disabled = true;
+ Field field = null;
+ Throwable exception = null;
+ try {
+ if (Platform.isLinux()) {
+ disabled = System.getenv().containsKey(DISABLE_NATIVE_IO);
+ if (!disabled) {
+ Native.register(Platform.C_LIBRARY_NAME);
+ field = getField(FileDescriptor.class, "fd");
+ initComplete = true;
+ }
+ } else {
+ exception = new RuntimeException("Platform is unsúpported. Only supported on linux.");
}
+ } catch (Throwable throwable) {
+ exception = throwable;
+ }
+ initialized = initComplete;
+ enabled = ! disabled;
+ initError = exception;
+ fdField = field;
+ }
+ private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field;
+ }
+ boolean isInitialized() { return initialized; }
+ boolean isEnabled() { return enabled; }
+ Throwable getError() { return initError; }
+ int getNativeFD(FileDescriptor fd) {
+ try {
+ return fdField.getInt(fd);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
}
- } catch (Throwable throwable) {
- initError = throwable;
}
}
- private static final Field fieldFD = getField(FileDescriptor.class, "fd");
-
-
private static native int posix_fadvise(int fd, long offset, long len, int flag) throws LastErrorException;
public NativeIO() {
- if (!initialized) {
- if (disabled) {
- logger.info("Native IO has been disable explicit via system property " + DISABLE_NATIVE_IO);
- } else {
+ if ( ! fdField.isInitialized()) {
+ if (fdField.isEnabled()) {
logger.warning("Native IO not possible due to " + getError().getMessage());
+ } else {
+ logger.info("Native IO has been disable explicit via system property " + DISABLE_NATIVE_IO);
}
}
}
- public boolean valid() { return initialized; }
- public Throwable getError() {
- if (initError != null) {
- return initError;
- } else {
- return new RuntimeException("Platform is unsúpported. Only supported on linux.");
- }
- }
+ public boolean valid() { return fdField.isInitialized(); }
+ public Throwable getError() { return fdField.getError(); }
/**
* Will hint the OS that data read so far will not be accessed again and should hence be dropped from the buffer cache.
@@ -74,8 +95,8 @@ public class NativeIO {
logger.warning("Sync failed while dropping cache: " + e.getMessage());
}
}
- if (initialized) {
- posix_fadvise(getNativeFD(fd), offset, len, POSIX_FADV_DONTNEED);
+ if (valid()) {
+ posix_fadvise(fdField.getNativeFD(fd), offset, len, POSIX_FADV_DONTNEED);
}
}
/**
@@ -100,24 +121,4 @@ public class NativeIO {
}
}
-
- private static Field getField(Class<?> clazz, String fieldName) {
- Field field;
- try {
- field = clazz.getDeclaredField(fieldName);
- } catch (NoSuchFieldException e) {
- throw new RuntimeException(e);
- }
- field.setAccessible(true);
- return field;
- }
-
- private static int getNativeFD(FileDescriptor fd) {
- try {
- return fieldFD.getInt(fd);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
-
}
diff --git a/vespajlib/src/main/java/com/yahoo/path/Path.java b/vespajlib/src/main/java/com/yahoo/path/Path.java
index 16370059c8c..737a27c57d8 100644
--- a/vespajlib/src/main/java/com/yahoo/path/Path.java
+++ b/vespajlib/src/main/java/com/yahoo/path/Path.java
@@ -41,6 +41,11 @@ public final class Path {
this.delimiter = delimiter;
}
+ /** Creates a new path with the given segments. */
+ public static Path from(List<String> segments) {
+ return new Path(segments, "/");
+ }
+
/** Returns whether this path is an immediate child of the given path */
public boolean isChildOf(Path parent) {
return toString().startsWith(parent.toString()) && this.elements.size() -1 == parent.elements.size();
diff --git a/vespajlib/src/main/java/com/yahoo/text/Text.java b/vespajlib/src/main/java/com/yahoo/text/Text.java
index 662100aa8ea..adf91a9b21e 100644
--- a/vespajlib/src/main/java/com/yahoo/text/Text.java
+++ b/vespajlib/src/main/java/com/yahoo/text/Text.java
@@ -48,43 +48,17 @@ public final class Text {
// The link above notes that 0x7F-0x84 and 0x86-0x9F are discouraged, but they are still allowed -
// see http://www.w3.org/International/questions/qa-controls
- if (codepoint < 0x80) return allowedAsciiChars[codepoint];
+ return (codepoint < 0x80)
+ ? allowedAsciiChars[codepoint]
+ : (codepoint < Character.MIN_SURROGATE) || isTextCharAboveMinSurrogate(codepoint);
+ }
+ private static boolean isTextCharAboveMinSurrogate(int codepoint) {
+ if (codepoint <= Character.MAX_HIGH_SURROGATE) return false;
if (codepoint < 0xFDD0) return true;
if (codepoint <= 0xFDDF) return false;
- if (codepoint < 0x1FFFE) return true;
- if (codepoint <= 0x1FFFF) return false;
- if (codepoint < 0x2FFFE) return true;
- if (codepoint <= 0x2FFFF) return false;
- if (codepoint < 0x3FFFE) return true;
- if (codepoint <= 0x3FFFF) return false;
- if (codepoint < 0x4FFFE) return true;
- if (codepoint <= 0x4FFFF) return false;
- if (codepoint < 0x5FFFE) return true;
- if (codepoint <= 0x5FFFF) return false;
- if (codepoint < 0x6FFFE) return true;
- if (codepoint <= 0x6FFFF) return false;
- if (codepoint < 0x7FFFE) return true;
- if (codepoint <= 0x7FFFF) return false;
- if (codepoint < 0x8FFFE) return true;
- if (codepoint <= 0x8FFFF) return false;
- if (codepoint < 0x9FFFE) return true;
- if (codepoint <= 0x9FFFF) return false;
- if (codepoint < 0xAFFFE) return true;
- if (codepoint <= 0xAFFFF) return false;
- if (codepoint < 0xBFFFE) return true;
- if (codepoint <= 0xBFFFF) return false;
- if (codepoint < 0xCFFFE) return true;
- if (codepoint <= 0xCFFFF) return false;
- if (codepoint < 0xDFFFE) return true;
- if (codepoint <= 0xDFFFF) return false;
- if (codepoint < 0xEFFFE) return true;
- if (codepoint <= 0xEFFFF) return false;
- if (codepoint < 0xFFFFE) return true;
- if (codepoint <= 0xFFFFF) return false;
- if (codepoint < 0x10FFFE) return true;
- if (codepoint <= 0x10FFFF) return false;
-
- return true;
+ if (codepoint < 0x10000) return true;
+ if (codepoint >= 0x10FFFE) return false;
+ return (codepoint & 0xffff) < 0xFFFE;
}
/**
@@ -110,6 +84,27 @@ public final class Text {
return OptionalInt.empty();
}
+ /**
+ * Validates that the given string value only contains text characters.
+ */
+ public static boolean isValidTextString(String string) {
+ int length = string.length();
+ for (int i = 0; i < length; ) {
+ int codePoint = string.codePointAt(i);
+ if (codePoint < 0x80) {
+ if ( ! allowedAsciiChars[codePoint]) return false;
+ } else if (codePoint >= Character.MIN_SURROGATE) {
+ if ( ! isTextCharAboveMinSurrogate(codePoint)) return false;
+ if ( ! Character.isBmpCodePoint(codePoint)) {
+ i++;
+ }
+ }
+ i++;
+ }
+ return true;
+ }
+
+
/** Returns whether the given code point is displayable. */
public static boolean isDisplayable(int codePoint) {
switch (Character.getType(codePoint)) {
diff --git a/vespajlib/src/test/java/com/yahoo/compress/ArchiveStreamReaderTest.java b/vespajlib/src/test/java/com/yahoo/compress/ArchiveStreamReaderTest.java
new file mode 100644
index 00000000000..b7f019282b7
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/compress/ArchiveStreamReaderTest.java
@@ -0,0 +1,131 @@
+package com.yahoo.compress;
+
+import com.yahoo.compress.ArchiveStreamReader.Options;
+import com.yahoo.yolean.Exceptions;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * @author mpolden
+ */
+class ArchiveStreamReaderTest {
+
+ @Test
+ void reading() {
+ Map<String, String> zipFiles = Map.of("foo", "contents of foo",
+ "bar", "contents of bar",
+ "baz", "0".repeat(2049));
+ Map<String, String> zipContents = new HashMap<>(zipFiles);
+ zipContents.put("dir/", ""); // Directories are always ignored
+ Map<String, String> extracted = readAll(zip(zipContents), Options.standard());
+ assertEquals(zipFiles, extracted);
+ }
+
+ @Test
+ void entry_size_limit() {
+ Map<String, String> entries = Map.of("foo.xml", "foobar");
+ Options options = Options.standard().pathPredicate("foo.xml"::equals).maxEntrySize(1);
+ try {
+ readAll(zip(entries), options);
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {}
+
+ entries = Map.of("foo.xml", "foobar",
+ "foo.jar", "0".repeat(100) // File not extracted and thus not subject to size limit
+ );
+ Map<String, String> extracted = readAll(zip(entries), options.maxEntrySize(10));
+ assertEquals(Map.of("foo.xml", "foobar"), extracted);
+ }
+
+ @Test
+ void size_limit() {
+ Map<String, String> entries = Map.of("foo.xml", "foo", "bar.xml", "bar");
+ try {
+ readAll(zip(entries), Options.standard().maxSize(4));
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {}
+ }
+
+ @Test
+ void entry_limit() {
+ Map<String, String> entries = Map.of("foo.xml", "foo", "bar.xml", "bar");
+ try {
+ readAll(zip(entries), Options.standard().maxEntries(1));
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {}
+ }
+
+ @Test
+ void paths() {
+ Map<String, Boolean> tests = Map.of(
+ "../../services.xml", true,
+ "/../.././services.xml", true,
+ "./application/././services.xml", true,
+ "application//services.xml", true,
+ "artifacts/", false, // empty dir
+ "services..xml", false,
+ "application/services.xml", false,
+ "components/foo-bar-deploy.jar", false,
+ "services.xml", false
+ );
+
+ Options options = Options.standard().maxEntrySize(1024);
+ tests.forEach((name, expectException) -> {
+ try {
+ readAll(zip(Map.of(name, "foo")), options.pathPredicate(name::equals));
+ assertFalse(expectException, "Expected exception for '" + name + "'");
+ } catch (IllegalArgumentException ignored) {
+ assertTrue(expectException, "Unexpected exception for '" + name + "'");
+ }
+ });
+ }
+
+ private static Map<String, String> readAll(InputStream inputStream, Options options) {
+ ArchiveStreamReader reader = ArchiveStreamReader.ofZip(inputStream, options);
+ ArchiveStreamReader.ArchiveFile file;
+ Map<String, String> entries = new HashMap<>();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ while ((file = reader.readNextTo(baos)) != null) {
+ entries.put(file.path().toString(), baos.toString(StandardCharsets.UTF_8));
+ baos.reset();
+ }
+ return entries;
+ }
+
+ private static InputStream zip(Map<String, String> entries) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ZipArchiveOutputStream archiveOutputStream = null;
+ try {
+ archiveOutputStream = new ZipArchiveOutputStream(baos);
+ for (var kv : entries.entrySet()) {
+ String entryName = kv.getKey();
+ String contents = kv.getValue();
+ ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
+ archiveOutputStream.putArchiveEntry(entry);
+ archiveOutputStream.write(contents.getBytes(StandardCharsets.UTF_8));
+ archiveOutputStream.closeArchiveEntry();
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } finally {
+ if (archiveOutputStream != null) Exceptions.uncheck(archiveOutputStream::close);
+ }
+ return new ByteArrayInputStream(baos.toByteArray());
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/compress/ZstdOuputStreamTest.java b/vespajlib/src/test/java/com/yahoo/compress/ZstdOutputStreamTest.java
index 5f6140271ad..c766c6e0c19 100644
--- a/vespajlib/src/test/java/com/yahoo/compress/ZstdOuputStreamTest.java
+++ b/vespajlib/src/test/java/com/yahoo/compress/ZstdOutputStreamTest.java
@@ -12,13 +12,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author bjorncs
*/
-class ZstdOuputStreamTest {
+class ZstdOutputStreamTest {
@Test
void output_stream_compresses_input() throws IOException {
byte[] inputData = "The quick brown fox jumps over the lazy dog".getBytes();
ByteArrayOutputStream arrayOut = new ByteArrayOutputStream();
- try (ZstdOuputStream zstdOut = new ZstdOuputStream(arrayOut, 12)) {
+ try (ZstdOutputStream zstdOut = new ZstdOutputStream(arrayOut, 12)) {
zstdOut.write(inputData[0]);
zstdOut.write(inputData, 1, inputData.length - 1);
}
@@ -37,7 +37,7 @@ class ZstdOuputStreamTest {
}
byte[] inputData = builder.toString().getBytes();
ByteArrayOutputStream arrayOut = new ByteArrayOutputStream();
- try (ZstdOuputStream zstdOut = new ZstdOuputStream(arrayOut)) {
+ try (ZstdOutputStream zstdOut = new ZstdOutputStream(arrayOut)) {
zstdOut.write(inputData);
}
int compressedSize = arrayOut.toByteArray().length;
diff --git a/vespajlib/src/test/java/com/yahoo/text/TextTestCase.java b/vespajlib/src/test/java/com/yahoo/text/TextTestCase.java
index a2cb2158278..033918f0bad 100644
--- a/vespajlib/src/test/java/com/yahoo/text/TextTestCase.java
+++ b/vespajlib/src/test/java/com/yahoo/text/TextTestCase.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.text;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.OptionalInt;
@@ -11,18 +12,18 @@ import static org.junit.Assert.assertTrue;
public class TextTestCase {
+ private static void validateText(OptionalInt expect, String text) {
+ assertEquals(expect, Text.validateTextString(text));
+ assertEquals(expect.isEmpty(), Text.isValidTextString(text));
+ }
@Test
public void testValidateTextString() {
- assertFalse(Text.validateTextString("valid").isPresent());
- assertEquals(OptionalInt.of(1), Text.validateTextString("text\u0001text\u0003"));
- assertEquals(OptionalInt.of(0xDFFFF),
- Text.validateTextString(new StringBuilder().appendCodePoint(0xDFFFF).toString()));
- assertEquals(OptionalInt.of(0xDFFFF),
- Text.validateTextString(new StringBuilder("foo").appendCodePoint(0xDFFFF).toString()));
- assertEquals(OptionalInt.of(0xDFFFF),
- Text.validateTextString(new StringBuilder().appendCodePoint(0xDFFFF).append("foo").toString()));
- assertEquals(OptionalInt.of(0xDFFFF),
- Text.validateTextString(new StringBuilder("foo").appendCodePoint(0xDFFFF).append("foo").toString()));
+ validateText(OptionalInt.empty(), "valid");
+ validateText(OptionalInt.of(1), "text\u0001text\u0003");
+ validateText(OptionalInt.of(0xDFFFF), new StringBuilder().appendCodePoint(0xDFFFF).toString());
+ validateText(OptionalInt.of(0xDFFFF), new StringBuilder("foo").appendCodePoint(0xDFFFF).toString());
+ validateText(OptionalInt.of(0xDFFFF), new StringBuilder().appendCodePoint(0xDFFFF).append("foo").toString());
+ validateText(OptionalInt.of(0xDFFFF), new StringBuilder("foo").appendCodePoint(0xDFFFF).append("foo").toString());
}
@Test
@@ -44,8 +45,9 @@ public class TextTestCase {
@Test
public void testThatHighSurrogateRequireLowSurrogate() {
- assertEquals(OptionalInt.of(0xD800), Text.validateTextString(new StringBuilder().appendCodePoint(0xD800).toString()));
- assertEquals(OptionalInt.of(0xD800), Text.validateTextString(new StringBuilder().appendCodePoint(0xD800).append(0x0000).toString()));
+ validateText(OptionalInt.of(0xD800), new StringBuilder().appendCodePoint(0xD800).toString());
+ validateText(OptionalInt.of(0xD800), new StringBuilder().appendCodePoint(0xD800).append(0x0000).toString());
+ validateText(OptionalInt.empty(), new StringBuilder().appendCodePoint(0xD800).appendCodePoint(0xDC00).toString());
}
@Test
@@ -76,4 +78,48 @@ public class TextTestCase {
public void testFormat() {
assertEquals("foo 3.14", Text.format("%s %.2f", "foo", 3.1415926536));
}
+
+ private static long benchmarkIsValid(String [] strings, int num) {
+ long sum = 0;
+ for (int i=0; i < num; i++) {
+ if (Text.isValidTextString(strings[i%strings.length])) {
+ sum++;
+ }
+ }
+ return sum;
+ }
+
+ private static long benchmarkValidate(String [] strings, int num) {
+ long sum = 0;
+ for (int i=0; i < num; i++) {
+ if (Text.validateTextString(strings[i%strings.length]).isEmpty()) {
+ sum++;
+ }
+ }
+ return sum;
+ }
+
+ @Ignore
+ @Test
+ public void benchmarkTextValidation() {
+ String [] strings = new String[100];
+ for (int i=0; i < strings.length; i++) {
+ strings[i] = new StringBuilder("some text ").append(i).append("of mine.").appendCodePoint(0xDFFFC).append("foo").toString();
+ }
+ long sum = benchmarkValidate(strings, 1000000);
+ System.out.println("Warmup num validate = " + sum);
+ sum = benchmarkIsValid(strings, 1000000);
+ System.out.println("Warmup num isValid = " + sum);
+
+ long start = System.nanoTime();
+ sum = benchmarkValidate(strings, 100000000);
+ long diff = System.nanoTime() - start;
+ System.out.println("Validation num validate = " + sum + ". Took " + diff + "ns");
+
+ start = System.nanoTime();
+ sum = benchmarkIsValid(strings, 100000000);
+ diff = System.nanoTime() - start;
+ System.out.println("Validation num isValid = " + sum + ". Took " + diff + "ns");
+
+ }
}
diff --git a/vespalib/src/tests/slime/slime_binary_format_test.cpp b/vespalib/src/tests/slime/slime_binary_format_test.cpp
index 459826d691e..ba2aacb88b9 100644
--- a/vespalib/src/tests/slime/slime_binary_format_test.cpp
+++ b/vespalib/src/tests/slime/slime_binary_format_test.cpp
@@ -248,9 +248,9 @@ TEST("testCmprUlong") {
for (uint32_t n = 1; n <= MAX_CMPR_SIZE; ++n) {
TEST_STATE(vespalib::make_string("n = %d", n).c_str());
uint64_t min = (n == 1) ? 0x00
- : (1L << ((n - 1) * 7));
+ : (1ULL << ((n - 1) * 7));
uint64_t max = (n == MAX_CMPR_SIZE) ? 0xffffffffffffffff
- : (1L << (n * 7)) - 1;
+ : (1ULL << (n * 7)) - 1;
SimpleBuffer expect_min;
SimpleBuffer expect_max;
for (uint32_t i = 0; i < n; ++i) {
diff --git a/vespalib/src/tests/wakeup/wakeup_bench.cpp b/vespalib/src/tests/wakeup/wakeup_bench.cpp
index c39b8899159..dc6ca70a4d1 100644
--- a/vespalib/src/tests/wakeup/wakeup_bench.cpp
+++ b/vespalib/src/tests/wakeup/wakeup_bench.cpp
@@ -137,6 +137,7 @@ struct UsePipe : State {
}
};
+#if __cpp_lib_atomic_wait
struct UseAtomic : State {
void wakeup() {
set_wakeup();
@@ -151,6 +152,7 @@ struct UseAtomic : State {
// assert(!is_ready());
}
};
+#endif
#ifdef __linux__
struct UseFutex : State {
@@ -284,7 +286,9 @@ TEST(WakeupBench, using_spin_yield) { benchmark<Wakeup<UseSpinYield>>(); }
TEST(WakeupBench, using_cond) { benchmark<Wakeup<UseCond>>(); }
TEST(WakeupBench, using_cond_nolock) { benchmark<Wakeup<UseCondNolock>>(); }
TEST(WakeupBench, using_pipe) { benchmark<Wakeup<UsePipe>>(); }
+#if __cpp_lib_atomic_wait
TEST(WakeupBench, using_atomic) { benchmark<Wakeup<UseAtomic>>(); }
+#endif
#ifdef __linux__
TEST(WakeupBench, using_futex) { benchmark<Wakeup<UseFutex>>(); }
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.h b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
index 444bf641899..d4a5ae42ef8 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
@@ -25,7 +25,7 @@ template <typename EntryType>
class BTreeNodeBufferType : public datastore::BufferType<EntryType, FrozenBtreeNode<EntryType>>
{
using ParentType = datastore::BufferType<EntryType, FrozenBtreeNode<EntryType>>;
- using ParentType::_emptyEntry;
+ using ParentType::empty_entry;
using ParentType::_arraySize;
using ElemCount = typename ParentType::ElemCount;
using CleanContext = typename ParentType::CleanContext;
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_type.h b/vespalib/src/vespa/vespalib/datastore/buffer_type.h
index 7d041646ca5..3394af18b04 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_type.h
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_type.h
@@ -133,7 +133,7 @@ template <typename EntryType, typename EmptyType = EntryType>
class BufferType : public BufferTypeBase
{
protected:
- static EntryType _emptyEntry;
+ static const EntryType& empty_entry() noexcept;
public:
BufferType() noexcept : BufferType(1,1,1) {}
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp b/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
index ae2659ef65b..72c8f574a70 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
@@ -51,8 +51,9 @@ void
BufferType<EntryType, EmptyType>::initializeReservedElements(void *buffer, ElemCount reservedElems)
{
EntryType *e = static_cast<EntryType *>(buffer);
+ const auto& empty = empty_entry();
for (size_t j = reservedElems; j != 0; --j) {
- new (static_cast<void *>(e)) EntryType(_emptyEntry);
+ new (static_cast<void *>(e)) EntryType(empty);
++e;
}
}
@@ -62,13 +63,22 @@ void
BufferType<EntryType, EmptyType>::cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext)
{
EntryType *e = static_cast<EntryType *>(buffer) + offset;
+ const auto& empty = empty_entry();
for (size_t j = numElems; j != 0; --j) {
- *e = _emptyEntry;
+ *e = empty;
++e;
}
}
template <typename EntryType, typename EmptyType>
-EntryType BufferType<EntryType, EmptyType>::_emptyEntry = EmptyType();
+const EntryType&
+BufferType<EntryType, EmptyType>::empty_entry() noexcept
+{
+ // It's possible for EntryType to wrap e.g. an Alloc instance, which has a transitive
+ // dependency on globally constructed allocator object(s). To avoid issues with global
+ // construction order, initialize the sentinel on the first access.
+ static EntryType empty = EmptyType();
+ return empty;
+}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
index 50d15d4a27c..ccaedaa9b64 100644
--- a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
+++ b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
@@ -20,7 +20,7 @@ class LargeArrayBufferType : public BufferType<Array<EntryT>>
using AllocSpec = ArrayStoreConfig::AllocSpec;
using ArrayType = Array<EntryT>;
using ParentType = BufferType<ArrayType>;
- using ParentType::_emptyEntry;
+ using ParentType::empty_entry;
using CleanContext = typename ParentType::CleanContext;
std::shared_ptr<alloc::MemoryAllocator> _memory_allocator;
public:
diff --git a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
index aeeef8166c6..3042bbff73f 100644
--- a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
@@ -22,9 +22,10 @@ void
LargeArrayBufferType<EntryT>::cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx)
{
ArrayType* elem = static_cast<ArrayType*>(buffer) + offset;
+ const auto& empty = empty_entry();
for (size_t i = 0; i < numElems; ++i) {
cleanCtx.extraBytesCleaned(sizeof(EntryT) * elem->size());
- *elem = _emptyEntry;
+ *elem = empty;
++elem;
}
}
diff --git a/vespalib/src/vespa/vespalib/util/array.hpp b/vespalib/src/vespa/vespalib/util/array.hpp
index 72178f0391b..24136e544b8 100644
--- a/vespalib/src/vespa/vespalib/util/array.hpp
+++ b/vespalib/src/vespa/vespalib/util/array.hpp
@@ -60,7 +60,9 @@ Array<T>::Array(const Array & rhs)
: _array(rhs._array.create(rhs.size() * sizeof(T))),
_sz(rhs.size())
{
- construct(array(0), rhs.array(0), _sz, std::is_trivially_copyable<T>());
+ if (_sz > 0) [[likely]] {
+ construct(array(0), rhs.array(0), _sz, std::is_trivially_copyable<T>());
+ }
}
template <typename T>
diff --git a/vespalib/src/vespa/vespalib/util/arrayref.h b/vespalib/src/vespa/vespalib/util/arrayref.h
index db3b39f400d..bc1fc540a6c 100644
--- a/vespalib/src/vespa/vespalib/util/arrayref.h
+++ b/vespalib/src/vespa/vespalib/util/arrayref.h
@@ -42,7 +42,7 @@ public:
ConstArrayRef(const SmallVector<T, N> &v) noexcept : _v(&v[0]), _sz(v.size()) { }
ConstArrayRef(const ArrayRef<T> & v) noexcept : _v(&v[0]), _sz(v.size()) { }
ConstArrayRef(const Array<T> &v) noexcept : _v(&v[0]), _sz(v.size()) { }
- ConstArrayRef() noexcept : _v(nullptr), _sz(0) {}
+ constexpr ConstArrayRef() noexcept : _v(nullptr), _sz(0) {}
const T & operator [] (size_t i) const { return _v[i]; }
size_t size() const { return _sz; }
bool empty() const { return _sz == 0; }
diff --git a/vespalib/src/vespa/vespalib/util/fiddle.h b/vespalib/src/vespa/vespalib/util/fiddle.h
index 20a13ff4654..f4d2ac33695 100644
--- a/vespalib/src/vespa/vespalib/util/fiddle.h
+++ b/vespalib/src/vespa/vespalib/util/fiddle.h
@@ -24,8 +24,8 @@ uint32_t mix(uint32_t prefix, uint32_t suffix, uint32_t prefix_bits) {
if (prefix_bits >= 32) {
return prefix;
}
- uint32_t suffix_mask = (1 << (32 - prefix_bits)) - 1;
- uint32_t prefix_mask = (0 - 1) - suffix_mask;
+ uint32_t suffix_mask = (1u << (32u - prefix_bits)) - 1u;
+ uint32_t prefix_mask = (0u - 1u) - suffix_mask;
return (prefix & prefix_mask) | (suffix & suffix_mask);
}
diff --git a/vespalib/src/vespa/vespalib/util/rcuvector.h b/vespalib/src/vespa/vespalib/util/rcuvector.h
index 09957d14aaf..ce48082099c 100644
--- a/vespalib/src/vespa/vespalib/util/rcuvector.h
+++ b/vespalib/src/vespa/vespalib/util/rcuvector.h
@@ -4,6 +4,7 @@
#include "alloc.h"
#include "array.h"
+#include "arrayref.h"
#include "generationholder.h"
#include "growstrategy.h"
#include "memoryusage.h"
@@ -148,6 +149,15 @@ public:
const T& get_elem_ref(size_t i) const noexcept { return _data[i]; } // Called from writer only
+ /*
+ * Readers holding a generation guard can call make_read_view() to
+ * get a read view to the rcu vector. Array bound (read_size) must
+ * be specified by reader, cf. committed docid limit in attribute vectors.
+ */
+ ConstArrayRef<T> make_read_view(size_t read_size) const noexcept {
+ return ConstArrayRef<T>(&acquire_elem_ref(0), read_size);
+ }
+
void reset();
void shrink(size_t newSize) __attribute__((noinline));
void replaceVector(ArrayType replacement);
diff --git a/vespamalloc/src/vespamalloc/malloc/common.h b/vespamalloc/src/vespamalloc/malloc/common.h
index a8bc8b102ec..58e05878f64 100644
--- a/vespamalloc/src/vespamalloc/malloc/common.h
+++ b/vespamalloc/src/vespamalloc/malloc/common.h
@@ -4,7 +4,9 @@
#include <new>
#include <atomic>
#include <cassert>
+#include <cstdio>
#include <vespamalloc/util/osmem.h>
+#include <thread>
extern "C" void MallocRecurseOnSuspend(bool recurse) __attribute__ ((noinline));
diff --git a/vespamalloc/src/vespamalloc/malloc/datasegment.cpp b/vespamalloc/src/vespamalloc/malloc/datasegment.cpp
index 28b69717fb5..4bb36eade43 100644
--- a/vespamalloc/src/vespamalloc/malloc/datasegment.cpp
+++ b/vespamalloc/src/vespamalloc/malloc/datasegment.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "datasegment.h"
+#include <unistd.h>
namespace vespamalloc::segment {
diff --git a/vespamalloc/src/vespamalloc/malloc/freelist.hpp b/vespamalloc/src/vespamalloc/malloc/freelist.hpp
index 0bc812b8ad3..9b18c375804 100644
--- a/vespamalloc/src/vespamalloc/malloc/freelist.hpp
+++ b/vespamalloc/src/vespamalloc/malloc/freelist.hpp
@@ -2,6 +2,7 @@
#pragma once
#include "freelist.h"
+#include <climits>
namespace vespamalloc::segment {
diff --git a/vespamalloc/src/vespamalloc/malloc/memorywatcher.h b/vespamalloc/src/vespamalloc/malloc/memorywatcher.h
index 88229aca77a..de72f0f3f2e 100644
--- a/vespamalloc/src/vespamalloc/malloc/memorywatcher.h
+++ b/vespamalloc/src/vespamalloc/malloc/memorywatcher.h
@@ -7,6 +7,7 @@
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
+#include <unistd.h>
#include <vespamalloc/malloc/malloc.h>
#include <vespamalloc/util/callstack.h>
diff --git a/vespamalloc/src/vespamalloc/malloc/mmappool.cpp b/vespamalloc/src/vespamalloc/malloc/mmappool.cpp
index 30e9985dae3..1d4cb5a6b5e 100644
--- a/vespamalloc/src/vespamalloc/malloc/mmappool.cpp
+++ b/vespamalloc/src/vespamalloc/malloc/mmappool.cpp
@@ -2,6 +2,7 @@
#include "mmappool.h"
#include "common.h"
#include <sys/mman.h>
+#include <unistd.h>
namespace vespamalloc {
diff --git a/vespamalloc/src/vespamalloc/malloc/mmappool.h b/vespamalloc/src/vespamalloc/malloc/mmappool.h
index aa8679c8171..6c06b840b5b 100644
--- a/vespamalloc/src/vespamalloc/malloc/mmappool.h
+++ b/vespamalloc/src/vespamalloc/malloc/mmappool.h
@@ -2,6 +2,7 @@
#pragma once
#include <atomic>
+#include <mutex>
#include <unordered_map>
namespace vespamalloc {
diff --git a/vespamalloc/src/vespamalloc/util/osmem.cpp b/vespamalloc/src/vespamalloc/util/osmem.cpp
index c61926c084b..e0cfbc36938 100644
--- a/vespamalloc/src/vespamalloc/util/osmem.cpp
+++ b/vespamalloc/src/vespamalloc/util/osmem.cpp
@@ -2,6 +2,7 @@
#include "osmem.h"
#include <vespamalloc/malloc/common.h>
#include <cstdio>
+#include <cctype>
#include <cassert>
#include <cerrno>
#include <cstdlib>
@@ -10,6 +11,7 @@
#include <sys/statfs.h>
#include <sys/mman.h>
#include <linux/mman.h>
+#include <functional>
namespace vespamalloc {
diff --git a/vsm/src/tests/docsum/docsum.cpp b/vsm/src/tests/docsum/docsum.cpp
index 35553c6f98c..475489d2f5a 100644
--- a/vsm/src/tests/docsum/docsum.cpp
+++ b/vsm/src/tests/docsum/docsum.cpp
@@ -204,12 +204,12 @@ DocsumTest::testSlimeFieldWriter()
{
FieldPath path;
type.buildFieldPath(path, "a");
- fields.push_back(DocsumFieldSpec::FieldIdentifier(0, path));
+ fields.push_back(DocsumFieldSpec::FieldIdentifier(0, std::move(path)));
}
{
FieldPath path;
type.buildFieldPath(path, "c.e");
- fields.push_back(DocsumFieldSpec::FieldIdentifier(0, path));
+ fields.push_back(DocsumFieldSpec::FieldIdentifier(0, std::move(path)));
}
sfw.setInputFields(fields);
TEST_DO(assertSlimeFieldWriter(sfw, value, "{\"a\":\"foo\",\"c\":{\"e\":\"qux\"}}"));
@@ -257,14 +257,14 @@ DocsumTest::requireThatSlimeFieldWriterHandlesMap()
{
FieldPath path;
mapType.buildFieldPath(path, "value.b");
- fields.push_back(DocsumFieldSpec::FieldIdentifier(0, path));
+ fields.push_back(DocsumFieldSpec::FieldIdentifier(0, std::move(path)));
}
sfw.setInputFields(fields);
TEST_DO(assertSlimeFieldWriter(sfw, mapfv, "[{\"key\":\"k1\",\"value\":{\"b\":\"bar\"}}]"));
{
FieldPath path;
mapType.buildFieldPath(path, "{k1}.a");
- fields[0] = DocsumFieldSpec::FieldIdentifier(0, path);
+ fields[0] = DocsumFieldSpec::FieldIdentifier(0, std::move(path));
}
sfw.clear();
sfw.setInputFields(fields);
diff --git a/vsm/src/vespa/vsm/common/documenttypemapping.cpp b/vsm/src/vespa/vsm/common/documenttypemapping.cpp
index 2379103653e..7886c44b2e0 100644
--- a/vsm/src/vespa/vsm/common/documenttypemapping.cpp
+++ b/vsm/src/vespa/vsm/common/documenttypemapping.cpp
@@ -45,13 +45,13 @@ bool DocumentTypeMapping::prepareBaseDoc(SharedFieldPathMap & map) const
{
FieldPathMapMapT::const_iterator found = _fieldMap.find(_defaultDocumentTypeName);
if (found != _fieldMap.end()) {
- map.reset(new FieldPathMapT(found->second));
+ map = std::make_shared<FieldPathMapT>(found->second);
LOG(debug, "Found FieldPathMap for default document type '%s' with %zd elements",
_defaultDocumentTypeName.c_str(), map->size());
} else {
LOG(warning, "No FieldPathMap found for default document type '%s'. Using empty one",
_defaultDocumentTypeName.c_str());
- map.reset(new FieldPathMapT());
+ map = std::make_shared<FieldPathMapT>();
}
return true;
}
@@ -70,7 +70,7 @@ void DocumentTypeMapping::buildFieldMap(
highestFNo++;
FieldPathMapT & fieldMap = _fieldMap[typeId];
- fieldMap.assign(highestFNo, FieldPath());
+ fieldMap.resize(highestFNo);
size_t validCount(0);
for (StringFieldIdTMapT::const_iterator it = fieldList.begin(), mt = fieldList.end(); it != mt; it++) {
diff --git a/vsm/src/vespa/vsm/common/storagedocument.h b/vsm/src/vespa/vsm/common/storagedocument.h
index 46a3e2f3251..a7f21cb052f 100644
--- a/vsm/src/vespa/vsm/common/storagedocument.h
+++ b/vsm/src/vespa/vsm/common/storagedocument.h
@@ -17,7 +17,7 @@ public:
class SubDocument {
public:
- SubDocument() : _fieldValue(NULL) {}
+ SubDocument() : _fieldValue(nullptr) {}
SubDocument(document::FieldValue *fv, document::FieldValue::PathRange nested) :
_fieldValue(fv),
_range(nested)
@@ -43,7 +43,7 @@ public:
~StorageDocument();
const document::Document &docDoc() const { return *_doc; }
- bool valid() const { return _doc.get() != NULL; }
+ bool valid() const { return _doc.get() != nullptr; }
const SubDocument &getComplexField(FieldIdT fId) const;
const document::FieldValue *getField(FieldIdT fId) const override;
bool setField(FieldIdT fId, document::FieldValue::UP fv) override ;
diff --git a/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp b/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp
index ba8310a9879..936aaaa2091 100644
--- a/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp
+++ b/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp
@@ -10,9 +10,12 @@ DocsumFieldSpec::FieldIdentifier::FieldIdentifier() :
DocsumFieldSpec::FieldIdentifier::FieldIdentifier(FieldIdT id, FieldPath path) :
_id(id),
- _path(path)
+ _path(std::move(path))
{ }
+DocsumFieldSpec::FieldIdentifier::FieldIdentifier(FieldIdentifier &&) noexcept = default;
+DocsumFieldSpec::FieldIdentifier & DocsumFieldSpec::FieldIdentifier::operator=(FieldIdentifier &&) noexcept = default;
+DocsumFieldSpec::FieldIdentifier::~FieldIdentifier() = default;
DocsumFieldSpec::DocsumFieldSpec() :
_resultType(search::docsummary::RES_INT),
diff --git a/vsm/src/vespa/vsm/vsm/docsumfieldspec.h b/vsm/src/vespa/vsm/vsm/docsumfieldspec.h
index 5acef140468..db6ee9fa223 100644
--- a/vsm/src/vespa/vsm/vsm/docsumfieldspec.h
+++ b/vsm/src/vespa/vsm/vsm/docsumfieldspec.h
@@ -24,6 +24,11 @@ public:
public:
FieldIdentifier();
FieldIdentifier(FieldIdT id, FieldPath path);
+ FieldIdentifier(FieldIdentifier &&) noexcept;
+ FieldIdentifier & operator=(FieldIdentifier &&) noexcept;
+ FieldIdentifier(const FieldIdentifier &) = delete;
+ FieldIdentifier & operator=(const FieldIdentifier &) = delete;
+ ~FieldIdentifier();
FieldIdT getId() const { return _id; }
const FieldPath & getPath() const { return _path; }
};
@@ -58,7 +63,7 @@ public:
}
const FieldIdentifier & getOutputField() const { return _outputField; }
- void setOutputField(const FieldIdentifier & outputField) { _outputField = outputField; }
+ void setOutputField(FieldIdentifier outputField) { _outputField = std::move(outputField); }
const FieldIdentifierVector & getInputFields() const { return _inputFields; }
FieldIdentifierVector & getInputFields() { return _inputFields; }
};
diff --git a/vsm/src/vespa/vsm/vsm/docsumfilter.cpp b/vsm/src/vespa/vsm/vsm/docsumfilter.cpp
index bd16c687fc7..70759feb41c 100644
--- a/vsm/src/vespa/vsm/vsm/docsumfilter.cpp
+++ b/vsm/src/vespa/vsm/vsm/docsumfilter.cpp
@@ -118,6 +118,18 @@ public:
namespace vsm {
+FieldPath
+copyPathButFirst(const FieldPath & rhs) {
+ // skip the element that correspond to the start field value
+ FieldPath path;
+ if ( ! rhs.empty()) {
+ for (auto it = rhs.begin() + 1; it != rhs.end(); ++it) {
+ path.push_back(std::make_unique<document::FieldPathEntry>(**it));
+ }
+ }
+ return path;
+}
+
void
DocsumFilter::prepareFieldSpec(DocsumFieldSpec & spec, const DocsumTools::FieldSpec & toolsSpec,
const FieldMap & fieldMap, const FieldPathMapT & fieldPathMap)
@@ -128,14 +140,7 @@ DocsumFilter::prepareFieldSpec(DocsumFieldSpec & spec, const DocsumTools::FieldS
FieldIdT field = fieldMap.fieldNo(name);
if (field != FieldMap::npos) {
if (field < fieldPathMap.size()) {
- if (!fieldPathMap[field].empty()) {
- // skip the element that correspond to the start field value
- spec.setOutputField(DocsumFieldSpec::FieldIdentifier
- (field, FieldPath(fieldPathMap[field].begin() + 1,
- fieldPathMap[field].end())));
- } else {
- spec.setOutputField(DocsumFieldSpec::FieldIdentifier(field, FieldPath()));
- }
+ spec.setOutputField(DocsumFieldSpec::FieldIdentifier(field, copyPathButFirst(fieldPathMap[field])));
} else {
LOG(warning, "Could not find a field path for field '%s' with id '%d'", name.c_str(), field);
spec.setOutputField(DocsumFieldSpec::FieldIdentifier(field, FieldPath()));
@@ -152,18 +157,7 @@ DocsumFilter::prepareFieldSpec(DocsumFieldSpec & spec, const DocsumTools::FieldS
if (field != FieldMap::npos) {
if (field < fieldPathMap.size()) {
LOG(debug, "field %u < map size %zu", field, fieldPathMap.size());
- if (!fieldPathMap[field].empty()) {
- FieldPath relPath(fieldPathMap[field].begin() + 1,
- fieldPathMap[field].end());
- LOG(debug, "map[%u] -> %zu elements", field, fieldPathMap[field].end() - fieldPathMap[field].begin());
- // skip the element that correspond to the start field value
- spec.getInputFields().push_back(DocsumFieldSpec::FieldIdentifier
- (field, FieldPath(fieldPathMap[field].begin() + 1,
- fieldPathMap[field].end())));
- } else {
- LOG(debug, "map[%u] empty", field);
- spec.getInputFields().push_back(DocsumFieldSpec::FieldIdentifier(field, FieldPath()));
- }
+ spec.getInputFields().push_back(DocsumFieldSpec::FieldIdentifier(field, copyPathButFirst(fieldPathMap[field])));
} else {
LOG(warning, "Could not find a field path for field '%s' with id '%d'", name.c_str(), field);
spec.getInputFields().push_back(DocsumFieldSpec::FieldIdentifier(field, FieldPath()));
diff --git a/zkfacade/pom.xml b/zkfacade/pom.xml
index fd02410d03f..86a60702c26 100644
--- a/zkfacade/pom.xml
+++ b/zkfacade/pom.xml
@@ -86,13 +86,12 @@
</exclusions>
</dependency>
<!-- snappy-java and metrics-core are included here
- to be able to work with ZooKeeper 3.6.2 due to
+ to be able to work with ZooKeeper >= 3.6.2 due to
class loading issues -->
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<scope>compile</scope>
- <version>3.2.5</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -104,7 +103,6 @@
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
<scope>compile</scope>
- <version>1.1.7</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
index bd3389b8d4d..7c91e54dd4b 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCurator.java
@@ -36,7 +36,7 @@ public class MockCurator extends Curator {
/**
* Creates a mock curator
*
- * @param stableOrdering if true children of a node are returned in the same order each time they are queries.
+ * @param stableOrdering if true children of a node are returned in the same order each time they are queried.
* This is not what ZooKeeper does.
*/
public MockCurator(boolean stableOrdering) {
diff --git a/zookeeper-command-line-client/pom.xml b/zookeeper-command-line-client/pom.xml
index a8105c78881..236bd5245a9 100644
--- a/zookeeper-command-line-client/pom.xml
+++ b/zookeeper-command-line-client/pom.xml
@@ -61,6 +61,11 @@
<artifactId>log4j-over-slf4j</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.xerial.snappy</groupId>
+ <artifactId>snappy-java</artifactId>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/zookeeper-server/zookeeper-server-3.7.0/pom.xml b/zookeeper-server/zookeeper-server-3.7.0/pom.xml
index 01fd83a496b..8daa6003f1e 100644
--- a/zookeeper-server/zookeeper-server-3.7.0/pom.xml
+++ b/zookeeper-server/zookeeper-server-3.7.0/pom.xml
@@ -62,7 +62,6 @@
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<scope>compile</scope>
- <version>3.2.5</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -74,7 +73,6 @@
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
<scope>compile</scope>
- <version>1.1.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>