aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt37
-rw-r--r--client/CMakeLists.txt14
-rw-r--r--client/go/.gitignore1
-rw-r--r--client/go/Makefile3
-rw-r--r--client/go/cmd/logfmt/runlogfmt.go31
-rw-r--r--client/go/cmd/logfmt/tail.go162
-rw-r--r--client/go/cmd/logfmt/tail_not_unix.go15
-rw-r--r--client/go/cmd/logfmt/tail_unix.go170
-rw-r--r--client/go/cmd/testdata/applications/withSource/src/main/application/services.xml3
-rw-r--r--client/go/go.mod2
-rw-r--r--client/go/vespa-logfmt/cmd.go2
-rw-r--r--client/js/app/README.md70
-rw-r--r--client/js/app/img/querybuilder.pngbin0 -> 95609 bytes
-rw-r--r--client/js/app/src/app/pages/querybuilder/TransformVespaTrace.jsx4
-rw-r--r--client/js/app/src/app/pages/querybuilder/query-response/download-jaeger.jsx (renamed from client/js/app/src/app/pages/querybuilder/query-response/download-jeager.jsx)2
-rw-r--r--client/js/app/src/app/pages/querybuilder/query-response/query-response.jsx4
-rw-r--r--client/js/app/src/app/pages/querytracer/query-tracer.jsx4
-rw-r--r--cloud-tenant-base-dependencies-enforcer/pom.xml2
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/RpcServerTest.java8
-rw-r--r--component/abi-spec.json1
-rw-r--r--component/src/main/java/com/yahoo/component/Version.java6
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java26
-rw-r--r--config-model/src/main/resources/schema/content.rnc2
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java31
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/DispatchTuningTest.java29
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/cluster/DomDispatchTuningBuilderTest.java47
-rw-r--r--configdefinitions/src/vespa/dispatch.def2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java80
-rwxr-xr-xconfigserver/src/main/sh/start-configserver10
-rw-r--r--configserver/src/test/apps/illegalApp2/hosts.xml7
-rw-r--r--configserver/src/test/apps/illegalApp2/schemas/music.sd50
-rw-r--r--configserver/src/test/apps/illegalApp2/services.xml39
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java49
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java97
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java78
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java90
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java22
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java19
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/ParseException.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/ProgrammaticParser.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java33
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java50
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/Token.java44
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/TokenPosition.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/TokenizeParser.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/UnicodePropertyDump.java7
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/WebParser.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java9
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java18
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java83
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/test/QueryTestCase.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java89
-rw-r--r--container-search/src/test/java/com/yahoo/search/test/QueryWithFilterTestCase.java80
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SimplePrincipal.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java44
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java206
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java8
-rw-r--r--default_build_settings.cmake28
-rw-r--r--dist/vespa.spec45
-rw-r--r--document/src/vespa/document/bucket/bucketid.cpp7
-rw-r--r--document/src/vespa/document/bucket/bucketid.h4
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java4
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java35
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java21
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java28
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java1
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cors/CorsLogic.java2
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cors/CorsPreflightRequestFilterTest.java8
-rw-r--r--jrt/src/com/yahoo/jrt/Acceptor.java2
-rw-r--r--linguistics/src/main/java/com/yahoo/language/opennlp/OpenNlpTokenizer.java28
-rw-r--r--linguistics/src/main/java/com/yahoo/language/simple/SimpleToken.java49
-rw-r--r--linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenType.java94
-rw-r--r--linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenizer.java26
-rw-r--r--linguistics/src/test/java/com/yahoo/language/opennlp/OpenNlpTokenizationTestCase.java32
-rw-r--r--linguistics/src/test/java/com/yahoo/language/process/GramSplitterTestCase.java23
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java11
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/RestartTest.java5
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java27
-rw-r--r--parent/pom.xml2
-rw-r--r--screwdriver.yaml11
-rw-r--r--searchcore/src/apps/verify_ranksetup/CMakeLists.txt16
-rw-r--r--searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp137
-rw-r--r--searchcore/src/apps/verify_ranksetup/verify_ranksetup.h7
-rw-r--r--searchcore/src/apps/verify_ranksetup/verify_ranksetup_app.cpp56
-rw-r--r--searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.cpp8
-rw-r--r--searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.h4
-rw-r--r--searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot_vector.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h15
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/handlermap.hpp43
-rw-r--r--searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/matcher.cpp5
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.cpp19
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.h35
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp40
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.h7
-rw-r--r--searchlib/src/tests/fef/object_passing/object_passing_test.cpp4
-rw-r--r--searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp99
-rw-r--r--searchlib/src/vespa/searchlib/features/distance_calculator_bundle.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/features/distance_calculator_bundle.h4
-rw-r--r--searchlib/src/vespa/searchlib/fef/blueprint.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp55
-rw-r--r--searchlib/src/vespa/searchlib/fef/blueprintresolver.h20
-rw-r--r--searchlib/src/vespa/searchlib/fef/ranksetup.cpp56
-rw-r--r--searchlib/src/vespa/searchlib/fef/ranksetup.h10
-rw-r--r--searchlib/src/vespa/searchlib/fef/verify_feature.cpp21
-rw-r--r--searchlib/src/vespa/searchlib/fef/verify_feature.h14
-rw-r--r--searchsummary/src/tests/juniper/auxTest.cpp83
-rw-r--r--searchsummary/src/tests/juniper/matchobjectTest.cpp60
-rw-r--r--searchsummary/src/tests/juniper/mcandTest.cpp170
-rw-r--r--searchsummary/src/vespa/juniper/Matcher.cpp10
-rw-r--r--searchsummary/src/vespa/juniper/foreach_utils.h44
-rw-r--r--searchsummary/src/vespa/juniper/juniperdebug.h8
-rw-r--r--searchsummary/src/vespa/juniper/juniperparams.cpp6
-rw-r--r--searchsummary/src/vespa/juniper/juniperparams.h10
-rw-r--r--searchsummary/src/vespa/juniper/keyocc.h4
-rw-r--r--searchsummary/src/vespa/juniper/querymodifier.cpp1
-rw-r--r--searchsummary/src/vespa/juniper/result.cpp44
-rw-r--r--searchsummary/src/vespa/juniper/result.h6
-rw-r--r--searchsummary/src/vespa/juniper/rpinterface.cpp36
-rw-r--r--searchsummary/src/vespa/juniper/rpinterface.h31
-rw-r--r--searchsummary/src/vespa/juniper/tokenizer.cpp2
-rw-r--r--searchsummary/src/vespa/juniper/tokenizer.h4
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.cpp6
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.h2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsum_store_field_value.h38
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp17
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h7
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp29
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/general_result.cpp4
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/general_result.h7
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/i_docsum_store_document.h6
-rw-r--r--storage/src/vespa/storage/bucketdb/bucketmanager.h59
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp7
-rw-r--r--streamingvisitors/src/vespa/vsm/vsm/docsumfilter.cpp8
-rw-r--r--vespalog/CMakeLists.txt1
164 files changed, 2419 insertions, 1594 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4fbc756236d..8972fada5bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,15 +51,21 @@ add_subdirectory(ann_benchmark)
add_subdirectory(application-model)
add_subdirectory(application-preprocessor)
add_subdirectory(athenz-identity-provider-service)
+add_subdirectory(client)
+add_subdirectory(cloud-tenant-cd)
+add_subdirectory(clustercontroller-apps)
+add_subdirectory(clustercontroller-core)
+add_subdirectory(clustercontroller-reindexer)
+add_subdirectory(clustercontroller-utils)
+add_subdirectory(config)
add_subdirectory(config-bundle)
+add_subdirectory(configd)
+add_subdirectory(configdefinitions)
add_subdirectory(config-model)
add_subdirectory(config-model-api)
+add_subdirectory(config-model-fat)
add_subdirectory(config-provisioning)
add_subdirectory(config-proxy)
-add_subdirectory(config)
-add_subdirectory(config-model-fat)
-add_subdirectory(configd)
-add_subdirectory(configdefinitions)
add_subdirectory(configserver)
add_subdirectory(configserver-flags)
add_subdirectory(configutil)
@@ -68,14 +74,9 @@ add_subdirectory(container-core)
add_subdirectory(container-disc)
add_subdirectory(container-messagebus)
add_subdirectory(container-search)
-add_subdirectory(container-search-gui)
add_subdirectory(container-search-and-docproc)
+add_subdirectory(container-search-gui)
add_subdirectory(container-spifly)
-add_subdirectory(cloud-tenant-cd)
-add_subdirectory(clustercontroller-apps)
-add_subdirectory(clustercontroller-core)
-add_subdirectory(clustercontroller-reindexer)
-add_subdirectory(clustercontroller-utils)
add_subdirectory(defaults)
add_subdirectory(docproc)
add_subdirectory(docprocs)
@@ -98,8 +99,8 @@ add_subdirectory(jrt_test)
add_subdirectory(linguistics)
add_subdirectory(linguistics-components)
add_subdirectory(logd)
-add_subdirectory(logserver)
add_subdirectory(logforwarder)
+add_subdirectory(logserver)
add_subdirectory(lowercasing_test)
add_subdirectory(messagebus)
add_subdirectory(messagebus_test)
@@ -127,22 +128,22 @@ add_subdirectory(tenant-cd-api)
add_subdirectory(vbench)
add_subdirectory(vdslib)
add_subdirectory(vdstestlib)
-add_subdirectory(vespa-athenz)
-add_subdirectory(vespa-feed-client)
-add_subdirectory(vespa-feed-client-cli)
-add_subdirectory(vespa-osgi-testrunner)
-add_subdirectory(vespa-testrunner-components)
-add_subdirectory(vespa_feed_perf)
add_subdirectory(vespa-3party-bundles)
+add_subdirectory(vespa-athenz)
add_subdirectory(vespabase)
add_subdirectory(vespaclient)
-add_subdirectory(vespaclient-core)
add_subdirectory(vespaclient-container-plugin)
+add_subdirectory(vespaclient-core)
add_subdirectory(vespaclient-java)
+add_subdirectory(vespa-feed-client)
+add_subdirectory(vespa-feed-client-cli)
+add_subdirectory(vespa_feed_perf)
add_subdirectory(vespajlib)
add_subdirectory(vespalib)
add_subdirectory(vespalog)
add_subdirectory(vespamalloc)
+add_subdirectory(vespa-osgi-testrunner)
+add_subdirectory(vespa-testrunner-components)
add_subdirectory(zkfacade)
add_subdirectory(zookeeper-command-line-client)
add_subdirectory(zookeeper-server)
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
new file mode 100644
index 00000000000..1ca6d2b4e60
--- /dev/null
+++ b/client/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+set(GODIR ${CMAKE_CURRENT_SOURCE_DIR}/go)
+
+file(GLOB_RECURSE GOSRCFILES ${GODIR}/*.go)
+
+add_custom_command(OUTPUT ${GODIR}/bin/vespa-logfmt
+ COMMAND make
+ DEPENDS ${GODIR}/Makefile ${GOSRCFILES}
+ WORKING_DIRECTORY ${GODIR})
+
+add_custom_target(vespalog_logfmt ALL DEPENDS ${GODIR}/bin/vespa-logfmt)
+
+install(PROGRAMS ${GODIR}/bin/vespa-logfmt DESTINATION bin)
diff --git a/client/go/.gitignore b/client/go/.gitignore
index 8933bc220cb..baab7c638c6 100644
--- a/client/go/.gitignore
+++ b/client/go/.gitignore
@@ -6,3 +6,4 @@ share/
!target/
mytestapp/
mytestapp-cache/
+mytmp/
diff --git a/client/go/Makefile b/client/go/Makefile
index 78adf299f0e..8a07f880c24 100644
--- a/client/go/Makefile
+++ b/client/go/Makefile
@@ -131,7 +131,8 @@ clean:
rmdir -p $(BIN) $(SHARE)/man/man1 > /dev/null 2>&1 || true
test: ci
- go test ./...
+ mkdir -p mytmp
+ TMPDIR=`pwd`/mytmp go test ./...
checkfmt:
@bash -c "diff --line-format='%L' <(echo -n) <(gofmt -l .)" || { echo "one or more files need to be formatted: try make fmt to fix this automatically"; exit 1; }
diff --git a/client/go/cmd/logfmt/runlogfmt.go b/client/go/cmd/logfmt/runlogfmt.go
index 9d782692f50..5abc4cc1cb8 100644
--- a/client/go/cmd/logfmt/runlogfmt.go
+++ b/client/go/cmd/logfmt/runlogfmt.go
@@ -8,12 +8,18 @@ import (
"bufio"
"fmt"
"os"
-
- "github.com/mattn/go-isatty"
)
-func inputIsTty() bool {
- return isatty.IsTerminal(os.Stdin.Fd())
+func inputIsPipe() bool {
+ fi, err := os.Stdin.Stat()
+ if err != nil {
+ return false
+ }
+ if fi.Mode()&os.ModeNamedPipe == 0 {
+ return false
+ } else {
+ return true
+ }
}
func vespaHome() string {
@@ -28,7 +34,7 @@ func vespaHome() string {
func RunLogfmt(opts *Options, args []string) {
if len(args) == 0 {
- if inputIsTty() {
+ if !inputIsPipe() {
args = append(args, vespaHome()+"/logs/vespa/vespa.log")
} else {
formatFile(opts, os.Stdin)
@@ -39,7 +45,10 @@ func RunLogfmt(opts *Options, args []string) {
fmt.Fprintf(os.Stderr, "Must have exact 1 file for 'follow' option, got %d\n", len(args))
return
}
- tailFile(opts, args[0])
+ if err := tailFile(opts, args[0]); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return
+ }
return
}
for _, arg := range args {
@@ -62,11 +71,15 @@ func formatLine(opts *Options, line string) {
}
}
-func tailFile(opts *Options, fn string) {
- tailed := FollowFile(fn)
- for line := range tailed.Lines {
+func tailFile(opts *Options, fn string) error {
+ tailed, err := FollowFile(fn)
+ if err != nil {
+ return err
+ }
+ for line := range tailed.Lines() {
formatLine(opts, line.Text)
}
+ return nil
}
func formatFile(opts *Options, arg *os.File) {
diff --git a/client/go/cmd/logfmt/tail.go b/client/go/cmd/logfmt/tail.go
index 44674012548..75e7cbb0693 100644
--- a/client/go/cmd/logfmt/tail.go
+++ b/client/go/cmd/logfmt/tail.go
@@ -1,169 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// vespa logfmt command
-// Author: arnej
+// Author: mpolden
package logfmt
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "time"
-
- "golang.org/x/sys/unix"
-)
-
-const lastLinesSize = 4 * 1024
-
type Line struct {
Text string
}
-// an active "tail -f" like object
-
-type Tail struct {
- Lines chan Line
- lineBuf []byte
- curFile *os.File
- fn string
- reader *bufio.Reader
- curStat unix.Stat_t
-}
-
-// API for starting to follow a log file
-
-func FollowFile(fn string) (res Tail) {
- res.fn = fn
- res.lineBuf = make([]byte, lastLinesSize)
- res.openTail()
- res.Lines = make(chan Line, 20)
- res.lineBuf = res.lineBuf[:0]
- go runTailWith(&res)
- return res
-}
-
-func (t *Tail) setFile(f *os.File) {
- if t.curFile != nil {
- t.curFile.Close()
- }
- t.curFile = f
- if f != nil {
- err := unix.Fstat(int(f.Fd()), &t.curStat)
- if err != nil {
- f.Close()
- fmt.Fprintf(os.Stderr, "unexpected failure: %v\n", err)
- return
- }
- t.reader = bufio.NewReaderSize(f, 1024*1024)
- } else {
- t.reader = nil
- }
-}
-
-// open log file and seek to the start of a line near the end, if possible.
-func (t *Tail) openTail() {
- file, err := os.Open(t.fn)
- if err != nil {
- return
- }
- sz, err := file.Seek(0, os.SEEK_END)
- if err != nil {
- return
- }
- if sz < lastLinesSize {
- sz, err = file.Seek(0, os.SEEK_SET)
- if err == nil {
- // just read from start of file, all OK
- t.setFile(file)
- }
- return
- }
- sz, _ = file.Seek(-lastLinesSize, os.SEEK_END)
- n, err := file.Read(t.lineBuf)
- if err != nil {
- return
- }
- for i := 0; i < n; i++ {
- if t.lineBuf[i] == '\n' {
- sz, err = file.Seek(sz+int64(i+1), os.SEEK_SET)
- if err == nil {
- t.setFile(file)
- }
- return
- }
- }
-}
-
-func (t *Tail) reopen(cur *unix.Stat_t) {
- for cnt := 0; cnt < 100; cnt++ {
- file, err := os.Open(t.fn)
- if err != nil {
- t.setFile(nil)
- if cnt == 0 {
- fmt.Fprintf(os.Stderr, "%v (waiting for log file to appear)\n", err)
- }
- time.Sleep(1000 * time.Millisecond)
- continue
- }
- var stat unix.Stat_t
- err = unix.Fstat(int(file.Fd()), &stat)
- if err != nil {
- file.Close()
- fmt.Fprintf(os.Stderr, "unexpected failure: %v\n", err)
- time.Sleep(5000 * time.Millisecond)
- continue
- }
- if cur != nil && cur.Dev == stat.Dev && cur.Ino == stat.Ino {
- // same file, continue following it
- file.Close()
- return
- }
- // new file, start following it
- t.setFile(file)
- return
- }
-}
-
-// runs as a goroutine
-func runTailWith(t *Tail) {
- defer t.setFile(nil)
-loop:
- for {
- for t.curFile == nil {
- t.reopen(nil)
- }
- bytes, err := t.reader.ReadSlice('\n')
- t.lineBuf = append(t.lineBuf, bytes...)
- if err == bufio.ErrBufferFull {
- continue
- }
- if err == nil {
- ll := len(t.lineBuf) - 1
- t.Lines <- Line{Text: string(t.lineBuf[:ll])}
- t.lineBuf = t.lineBuf[:0]
- continue
- }
- if err == io.EOF {
- pos, _ := t.curFile.Seek(0, os.SEEK_CUR)
- for cnt := 0; cnt < 100; cnt++ {
- time.Sleep(10 * time.Millisecond)
- sz, _ := t.curFile.Seek(0, os.SEEK_END)
- if sz != pos {
- if sz < pos {
- // truncation case
- pos = 0
- }
- t.curFile.Seek(pos, os.SEEK_SET)
- continue loop
- }
- }
- // no change in file size, try reopening
- t.reopen(&t.curStat)
- } else {
- fmt.Fprintf(os.Stderr, "error tailing '%s': %v\n", t.fn, err)
- close(t.Lines)
- return
- }
- }
+type Tail interface {
+ Lines() chan Line
}
diff --git a/client/go/cmd/logfmt/tail_not_unix.go b/client/go/cmd/logfmt/tail_not_unix.go
new file mode 100644
index 00000000000..7030572575d
--- /dev/null
+++ b/client/go/cmd/logfmt/tail_not_unix.go
@@ -0,0 +1,15 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// vespa logfmt command
+// Author: mpolden
+
+//go:build windows
+
+package logfmt
+
+import (
+ "fmt"
+)
+
+func FollowFile(fn string) (Tail, error) {
+ return nil, fmt.Errorf("tail is not supported on this platform")
+}
diff --git a/client/go/cmd/logfmt/tail_unix.go b/client/go/cmd/logfmt/tail_unix.go
new file mode 100644
index 00000000000..7703844da48
--- /dev/null
+++ b/client/go/cmd/logfmt/tail_unix.go
@@ -0,0 +1,170 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// vespa logfmt command
+// Author: arnej
+
+//go:build !windows
+
+package logfmt
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "time"
+
+ "golang.org/x/sys/unix"
+)
+
+const lastLinesSize = 4 * 1024
+
+// an active "tail -f" like object
+
+type unixTail struct {
+ lines chan Line
+ lineBuf []byte
+ curFile *os.File
+ fn string
+ reader *bufio.Reader
+ curStat unix.Stat_t
+}
+
+func (t *unixTail) Lines() chan Line { return t.lines }
+
+// API for starting to follow a log file
+
+func FollowFile(fn string) (Tail, error) {
+ res := unixTail{}
+ res.fn = fn
+ res.lineBuf = make([]byte, lastLinesSize)
+ res.openTail()
+ res.lines = make(chan Line, 20)
+ res.lineBuf = res.lineBuf[:0]
+ go runTailWith(&res)
+ return &res, nil
+}
+
+func (t *unixTail) setFile(f *os.File) {
+ if t.curFile != nil {
+ t.curFile.Close()
+ }
+ t.curFile = f
+ if f != nil {
+ err := unix.Fstat(int(f.Fd()), &t.curStat)
+ if err != nil {
+ f.Close()
+ fmt.Fprintf(os.Stderr, "unexpected failure: %v\n", err)
+ return
+ }
+ t.reader = bufio.NewReaderSize(f, 1024*1024)
+ } else {
+ t.reader = nil
+ }
+}
+
+// open log file and seek to the start of a line near the end, if possible.
+func (t *unixTail) openTail() {
+ file, err := os.Open(t.fn)
+ if err != nil {
+ return
+ }
+ sz, err := file.Seek(0, os.SEEK_END)
+ if err != nil {
+ return
+ }
+ if sz < lastLinesSize {
+ sz, err = file.Seek(0, os.SEEK_SET)
+ if err == nil {
+ // just read from start of file, all OK
+ t.setFile(file)
+ }
+ return
+ }
+ sz, _ = file.Seek(-lastLinesSize, os.SEEK_END)
+ n, err := file.Read(t.lineBuf)
+ if err != nil {
+ return
+ }
+ for i := 0; i < n; i++ {
+ if t.lineBuf[i] == '\n' {
+ sz, err = file.Seek(sz+int64(i+1), os.SEEK_SET)
+ if err == nil {
+ t.setFile(file)
+ }
+ return
+ }
+ }
+}
+
+func (t *unixTail) reopen(cur *unix.Stat_t) {
+ for cnt := 0; cnt < 100; cnt++ {
+ file, err := os.Open(t.fn)
+ if err != nil {
+ t.setFile(nil)
+ if cnt == 0 {
+ fmt.Fprintf(os.Stderr, "%v (waiting for log file to appear)\n", err)
+ }
+ time.Sleep(1000 * time.Millisecond)
+ continue
+ }
+ var stat unix.Stat_t
+ err = unix.Fstat(int(file.Fd()), &stat)
+ if err != nil {
+ file.Close()
+ fmt.Fprintf(os.Stderr, "unexpected failure: %v\n", err)
+ time.Sleep(5000 * time.Millisecond)
+ continue
+ }
+ if cur != nil && cur.Dev == stat.Dev && cur.Ino == stat.Ino {
+ // same file, continue following it
+ file.Close()
+ return
+ }
+ // new file, start following it
+ t.setFile(file)
+ return
+ }
+}
+
+// runs as a goroutine
+func runTailWith(t *unixTail) {
+ defer t.setFile(nil)
+loop:
+ for {
+ for t.curFile == nil {
+ t.reopen(nil)
+ }
+ bytes, err := t.reader.ReadSlice('\n')
+ t.lineBuf = append(t.lineBuf, bytes...)
+ if err == bufio.ErrBufferFull {
+ continue
+ }
+ if err == nil {
+ ll := len(t.lineBuf) - 1
+ t.lines <- Line{Text: string(t.lineBuf[:ll])}
+ t.lineBuf = t.lineBuf[:0]
+ continue
+ }
+ if err == io.EOF {
+ pos, _ := t.curFile.Seek(0, os.SEEK_CUR)
+ for cnt := 0; cnt < 100; cnt++ {
+ time.Sleep(10 * time.Millisecond)
+ sz, _ := t.curFile.Seek(0, os.SEEK_END)
+ if sz != pos {
+ if sz < pos {
+ // truncation case
+ pos = 0
+ }
+ t.curFile.Seek(pos, os.SEEK_SET)
+ continue loop
+ }
+ }
+ // no change in file size, try reopening
+ t.reopen(&t.curStat)
+ } else {
+ fmt.Fprintf(os.Stderr, "error tailing '%s': %v\n", t.fn, err)
+ close(t.lines)
+ return
+ }
+ }
+}
diff --git a/client/go/cmd/testdata/applications/withSource/src/main/application/services.xml b/client/go/cmd/testdata/applications/withSource/src/main/application/services.xml
index ceaaa0f5e3c..60267517c33 100644
--- a/client/go/cmd/testdata/applications/withSource/src/main/application/services.xml
+++ b/client/go/cmd/testdata/applications/withSource/src/main/application/services.xml
@@ -32,8 +32,9 @@
</config>
</handler>
- <nodes jvmargs="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8998">
+ <nodes>
<node hostalias="node1" />
+ <jvm options="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8998"/>
</nodes>
</container>
diff --git a/client/go/go.mod b/client/go/go.mod
index 15b45a291a6..4181d4ee2f2 100644
--- a/client/go/go.mod
+++ b/client/go/go.mod
@@ -17,7 +17,7 @@ require (
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.7.0
github.com/zalando/go-keyring v0.1.1
- golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
+ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
diff --git a/client/go/vespa-logfmt/cmd.go b/client/go/vespa-logfmt/cmd.go
index 749cac5fa56..c1b8261c0c1 100644
--- a/client/go/vespa-logfmt/cmd.go
+++ b/client/go/vespa-logfmt/cmd.go
@@ -34,6 +34,7 @@ and converts it to something human-readable`,
cmd.Flags().BoolVar(&curOptions.TruncateService, "ts", false, "")
cmd.Flags().BoolVarP(&curOptions.FollowTail, "follow", "f", false, "follow logfile with tail -f")
cmd.Flags().BoolVarP(&curOptions.DequoteNewlines, "nldequote", "N", false, "dequote newlines embedded in message")
+ cmd.Flags().BoolVarP(&curOptions.DequoteNewlines, "dequotenewlines", "n", false, "dequote newlines embedded in message")
cmd.Flags().BoolVarP(&curOptions.TruncateComponent, "truncatecomponent", "t", false, "truncate component name")
cmd.Flags().BoolVar(&curOptions.TruncateComponent, "tc", false, "")
cmd.Flags().StringVarP(&curOptions.OnlyHostname, "host", "H", "", "select only one host")
@@ -41,5 +42,6 @@ and converts it to something human-readable`,
cmd.Flags().StringVarP(&curOptions.OnlyService, "service", "S", "", "select only one service")
cmd.Flags().MarkHidden("tc")
cmd.Flags().MarkHidden("ts")
+ cmd.Flags().MarkHidden("dequotenewlines")
return cmd
}
diff --git a/client/js/app/README.md b/client/js/app/README.md
index ae6a8d1cc25..9dbce216224 100644
--- a/client/js/app/README.md
+++ b/client/js/app/README.md
@@ -3,17 +3,71 @@
![Vespa logo](https://vespa.ai/assets/vespa-logo-color.png)
# Vespa client
+This app contains the **Query Builder** and the **Trace Visualizer**.
-This app is work in progress.
-It currently contains the **Query Builder** and the **Trace Visualizer**.
+Install and start:
+
+ $ nvm install --lts node # in case the installed node.js is too old
+ $ yarn install
+ $ yarn dev # then open link, like http://127.0.0.1:3000/
+
+Alternatively, use Docker to start it without installing node:
+
+ $ docker run -v `pwd`:/w -w /w --publish 3000:3000 node:16 sh -c 'yarn install && yarn dev --host'
+
+When started, open [http://127.0.0.1:3000/](http://127.0.0.1:3000/).
+
+*Troubleshooting:* Remove the generated `node_modules` directory and try again.
+This is also relevant when switching between running local and in the container.
## Query Builder
-The Query Builder is a tool for creating Vespa queries to send to a local backend.
+The Query Builder is a tool for creating Vespa queries to send to a local Vespa application.
The tool provides all of the options for query parameters from dropdowns.
The input fields provide hints to what is the expected type of value.
+![Query Builder](img/querybuilder.png)
+
+To access a Vespa endpoint from the Query Builder,
+deploy with [CORS filters](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
+To the query-serving container, add an `http` element in _services.xml_ like:
+```
+ <container id="default" version="1.0">
+
+ <http>
+ <filtering strict-mode="false">
+ <request-chain id="request-chain">
+ <filter id="com.yahoo.jdisc.http.filter.security.cors.CorsPreflightRequestFilter"
+ bundle="jdisc-security-filters">
+ <config name="jdisc.http.filter.security.cors.cors-filter">
+ <allowedUrls>
+ <item>*</item>
+ </allowedUrls>
+ </config>
+ </filter>
+ <binding>http://*/search/</binding>
+ </request-chain>
+
+ <response-chain id="response-chain">
+ <filter id="com.yahoo.jdisc.http.filter.security.cors.CorsResponseFilter"
+ bundle="jdisc-security-filters">
+ <config name="jdisc.http.filter.security.cors.cors-filter">
+ <allowedUrls>
+ <item>*</item>
+ </allowedUrls>
+ </config>
+ </filter>
+ <binding>http://*/search/</binding>
+ </response-chain>
+ </filtering>
+
+ <server port="8080" id="default"/>
+ </http>
+```
+
+Deploy again, and (possibly) restart Vespa (internal port changes can be triggered by this).
+
## Trace Visualizer
@@ -45,13 +99,3 @@ Press the _JSON File_ button as shown in the image, and drag and drop the trace
Then click on the newly added trace and see it displayed as a flame graph:
![Example Image](img/result.png)
-
-
-
-## Client install and start
-
- nvm install --lts node # in case your current node.js is too old
- yarn install
- yarn dev # then open link, like http://127.0.0.1:3000/
-
-<!-- ToDo: publish a Docker image with all the clients ... -->
diff --git a/client/js/app/img/querybuilder.png b/client/js/app/img/querybuilder.png
new file mode 100644
index 00000000000..fa68f5bb82f
--- /dev/null
+++ b/client/js/app/img/querybuilder.png
Binary files differ
diff --git a/client/js/app/src/app/pages/querybuilder/TransformVespaTrace.jsx b/client/js/app/src/app/pages/querybuilder/TransformVespaTrace.jsx
index 7299f1cb777..7a630a017cb 100644
--- a/client/js/app/src/app/pages/querybuilder/TransformVespaTrace.jsx
+++ b/client/js/app/src/app/pages/querybuilder/TransformVespaTrace.jsx
@@ -128,6 +128,10 @@ function createProtonSpans(children, parentID) {
[{ refType: 'CHILD_OF', traceID: traceID, spanID: parentID }]
);
data.push(newSpan);
+ // eslint-disable-next-line no-prototype-builtins
+ if (!child.hasOwnProperty('traces')) {
+ return;
+ }
let traces = child['traces'];
for (let k = 0; k < traces.length; k++) {
let trace = traces[k];
diff --git a/client/js/app/src/app/pages/querybuilder/query-response/download-jeager.jsx b/client/js/app/src/app/pages/querybuilder/query-response/download-jaeger.jsx
index 4130172a329..795c53f8c21 100644
--- a/client/js/app/src/app/pages/querybuilder/query-response/download-jeager.jsx
+++ b/client/js/app/src/app/pages/querybuilder/query-response/download-jaeger.jsx
@@ -20,7 +20,7 @@ function downloadFile(filename, blob) {
URL.revokeObjectURL(href);
}
-export function DownloadJeager({ response, ...props }) {
+export function DownloadJaeger({ response, ...props }) {
const handleClick = () => {
try {
const json = JSON.parse(response);
diff --git a/client/js/app/src/app/pages/querybuilder/query-response/query-response.jsx b/client/js/app/src/app/pages/querybuilder/query-response/query-response.jsx
index 56562ae1717..794e163ef04 100644
--- a/client/js/app/src/app/pages/querybuilder/query-response/query-response.jsx
+++ b/client/js/app/src/app/pages/querybuilder/query-response/query-response.jsx
@@ -7,7 +7,7 @@ import {
CopyButton,
Textarea,
} from '@mantine/core';
-import { DownloadJeager } from 'app/pages/querybuilder/query-response/download-jeager';
+import { DownloadJaeger } from 'app/pages/querybuilder/query-response/download-jaeger';
import { useQueryBuilderContext } from 'app/pages/querybuilder/context/query-builder-provider';
import { Icon } from 'app/components';
@@ -33,7 +33,7 @@ export function QueryResponse() {
</Button>
)}
</CopyButton>
- <DownloadJeager
+ <DownloadJaeger
variant="outline"
size="xs"
compact
diff --git a/client/js/app/src/app/pages/querytracer/query-tracer.jsx b/client/js/app/src/app/pages/querytracer/query-tracer.jsx
index 758182d4f3f..ca1a4e99c8f 100644
--- a/client/js/app/src/app/pages/querytracer/query-tracer.jsx
+++ b/client/js/app/src/app/pages/querytracer/query-tracer.jsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Stack, Textarea } from '@mantine/core';
-import { DownloadJeager } from 'app/pages/querybuilder/query-response/download-jeager';
+import { DownloadJaeger } from 'app/pages/querybuilder/query-response/download-jaeger';
export function QueryTracer() {
const [response, setResponse] = useState('');
@@ -19,7 +19,7 @@ export function QueryTracer() {
value={response}
onChange={({ target }) => setResponse(target.value)}
/>
- <DownloadJeager fullWidth response={response} />
+ <DownloadJaeger fullWidth response={response} />
</Stack>
);
}
diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml
index a30cbb86299..5862731ced7 100644
--- a/cloud-tenant-base-dependencies-enforcer/pom.xml
+++ b/cloud-tenant-base-dependencies-enforcer/pom.xml
@@ -27,7 +27,7 @@
<httpclient.version>4.5.13</httpclient.version>
<httpcore.version>4.4.13</httpcore.version>
<junit5.version>5.8.1</junit5.version> <!-- TODO: in parent this is named 'junit.version' -->
- <onnxruntime.version>1.11.0</onnxruntime.version>
+ <onnxruntime.version>1.12.1</onnxruntime.version>
<!-- END parent/pom.xml -->
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/RpcServerTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/RpcServerTest.java
index 6a7c75c629e..789c86bd6cf 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/RpcServerTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/RpcServerTest.java
@@ -22,7 +22,6 @@ import com.yahoo.vespa.clustercontroller.core.rpc.RpcServer;
import com.yahoo.vespa.clustercontroller.core.testutils.LogFormatter;
import com.yahoo.vespa.clustercontroller.core.testutils.WaitCondition;
import org.junit.jupiter.api.Test;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -31,7 +30,10 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author humbe
@@ -55,7 +57,7 @@ public class RpcServerTest extends FleetControllerTest {
Slobrok slobrok = new Slobrok();
String[] slobrokConnectionSpecs = getSlobrokConnectionSpecs(slobrok);
RpcServer server = new RpcServer(timer, new Object(), "mycluster", 0, new BackOff());
- server.setSlobrokConnectionSpecs(slobrokConnectionSpecs, 18347);
+ server.setSlobrokConnectionSpecs(slobrokConnectionSpecs, 0);
int portUsed = server.getPort();
server.setSlobrokConnectionSpecs(slobrokConnectionSpecs, portUsed);
server.disconnect();
diff --git a/component/abi-spec.json b/component/abi-spec.json
index d990a9077b4..cfbbdf4b306 100644
--- a/component/abi-spec.json
+++ b/component/abi-spec.json
@@ -138,6 +138,7 @@
"public void <init>(java.lang.String)",
"public void <init>(com.yahoo.text.Utf8Array)",
"public static com.yahoo.component.Version fromString(java.lang.String)",
+ "public com.yahoo.component.Version withQualifier(java.lang.String)",
"public java.lang.String toFullString()",
"public int getMajor()",
"public int getMinor()",
diff --git a/component/src/main/java/com/yahoo/component/Version.java b/component/src/main/java/com/yahoo/component/Version.java
index 1d4546c0c58..db8606d31fa 100644
--- a/component/src/main/java/com/yahoo/component/Version.java
+++ b/component/src/main/java/com/yahoo/component/Version.java
@@ -202,6 +202,12 @@ public final class Version implements Comparable<Version> {
return (versionString == null) ? emptyVersion :new Version(versionString);
}
+ public Version withQualifier(String qualifier) {
+ if (qualifier.indexOf('.') != -1)
+ throw new IllegalArgumentException("Qualifier cannot contain '.'");
+ return new Version(major, minor, micro, qualifier);
+ }
+
/**
* Must be called on construction after the component values are set
*
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 4536257f8a3..e45ab5de253 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
@@ -76,6 +76,7 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"vekterli"}) default boolean useThreePhaseUpdates() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"baldersheim"}, comment = "Select sequencer type use while feeding") default String feedSequencerType() { return "THROUGHPUT"; }
@ModelFeatureFlag(owners = {"baldersheim"}) default String responseSequencerType() { throw new UnsupportedOperationException("TODO specify default value"); }
+ @ModelFeatureFlag(owners = {"baldersheim"}) default String queryDispatchPolicy() { return "adaptive"; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int defaultNumResponseThreads() { return 2; }
@ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipCommunicationManagerThread() { return true; }
@ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipMbusRequestThread() { return true; }
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 400aea86834..8e2f3feb010 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
@@ -41,6 +41,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private boolean useThreePhaseUpdates = false;
private double defaultTermwiseLimit = 1.0;
private String jvmGCOptions = null;
+ private String queryDispatchPolicy = "adaptive";
private String sequencerType = "THROUGHPUT";
private boolean firstTimeDeployment = false;
private String responseSequencerType = "ADAPTIVE";
@@ -147,6 +148,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
@Override public int mbusCppEventsBeforeWakeup() { return mbus_cpp_events_before_wakeup; }
@Override public int rpcNumTargets() { return rpc_num_targets; }
@Override public int rpcEventsBeforeWakeup() { return rpc_events_before_wakeup; }
+ @Override public String queryDispatchPolicy() { return queryDispatchPolicy; }
public TestProperties sharedStringRepoNoReclaim(boolean sharedStringRepoNoReclaim) {
@@ -192,6 +194,10 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
jvmGCOptions = gcOptions;
return this;
}
+ public TestProperties setQueryDispatchPolicy(String policy) {
+ queryDispatchPolicy = policy;
+ return this;
+ }
public TestProperties setFeedSequencerType(String type) {
sequencerType = type;
return this;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java
index ff39aa0903c..6dad3c5f06f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java
@@ -158,7 +158,7 @@ public class ContentSearchCluster extends AbstractConfigProducer<SearchCluster>
String clusterName, ContentSearchCluster search) {
List<ModelElement> indexedDefs = getIndexedSchemas(clusterElem);
if (!indexedDefs.isEmpty()) {
- IndexedSearchCluster isc = new IndexedSearchCluster(search, clusterName, 0);
+ IndexedSearchCluster isc = new IndexedSearchCluster(search, clusterName, 0, deployState.featureFlags());
isc.setRoutingSelector(clusterElem.childAsString("documents.selection"));
Double visibilityDelay = clusterElem.childAsDouble("engine.proton.visibility-delay");
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java
index 6cad778dccf..1d1e1a8e3dc 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DispatchTuning.java
@@ -11,10 +11,16 @@ public class DispatchTuning {
public static final DispatchTuning empty = new DispatchTuning.Builder().build();
- public enum DispatchPolicy { ROUNDROBIN, ADAPTIVE }
+ public enum DispatchPolicy {
+ ROUNDROBIN,
+ LATENCY_AMORTIZED_OVER_REQUESTS,
+ LATENCY_AMORTIZED_OVER_TIME,
+ BEST_OF_RANDOM_2,
+ ADAPTIVE
+ }
private final Integer maxHitsPerPartition;
- private DispatchPolicy dispatchPolicy;
+ private final DispatchPolicy dispatchPolicy;
private final Double minActiveDocsCoverage;
public Double getTopkProbability() {
@@ -36,9 +42,6 @@ public class DispatchTuning {
/** Returns the policy used to select which group to dispatch a query to */
public DispatchPolicy getDispatchPolicy() { return dispatchPolicy; }
- @SuppressWarnings("unused")
- public void setDispatchPolicy(DispatchPolicy dispatchPolicy) { this.dispatchPolicy = dispatchPolicy; }
-
/** Returns the percentage of documents which must be available in a group for that group to receive queries */
public Double getMinActiveDocsCoverage() { return minActiveDocsCoverage; }
@@ -67,10 +70,13 @@ public class DispatchTuning {
return this;
}
- private DispatchPolicy toDispatchPolicy(String policy) {
+ public static DispatchPolicy toDispatchPolicy(String policy) {
switch (policy.toLowerCase()) {
case "adaptive": case "random": return DispatchPolicy.ADAPTIVE; // TODO: Deprecate 'random' on Vespa 9
case "round-robin": return DispatchPolicy.ROUNDROBIN;
+ case "latency-amortized-over-requests" : return DispatchPolicy.LATENCY_AMORTIZED_OVER_REQUESTS;
+ case "latency-amortized-over-time" : return DispatchPolicy.LATENCY_AMORTIZED_OVER_TIME;
+ case "best-of-random-2" : return DispatchPolicy.BEST_OF_RANDOM_2;
default: throw new IllegalArgumentException("Unknown dispatch policy '" + policy + "'");
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java
index 2c83e87df97..56fb915797b 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.model.search;
import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
@@ -18,6 +19,7 @@ import com.yahoo.vespa.config.search.core.ProtonConfig;
import com.yahoo.vespa.configdefinition.IlscriptsConfig;
import com.yahoo.vespa.model.container.docproc.DocprocChain;
import com.yahoo.vespa.model.content.DispatchSpec;
+import com.yahoo.vespa.model.content.DispatchTuning;
import com.yahoo.vespa.model.content.SearchCoverage;
import java.util.ArrayList;
@@ -56,6 +58,7 @@ public class IndexedSearchCluster extends SearchCluster
private final DispatchGroup rootDispatch;
private DispatchSpec dispatchSpec;
private final List<SearchNode> searchNodes = new ArrayList<>();
+ private final DispatchTuning.DispatchPolicy defaultDispatchPolicy;
/**
* Returns the document selector that is able to resolve what documents are to be routed to this search cluster.
@@ -67,10 +70,11 @@ public class IndexedSearchCluster extends SearchCluster
return routingSelector;
}
- public IndexedSearchCluster(AbstractConfigProducer<SearchCluster> parent, String clusterName, int index) {
+ public IndexedSearchCluster(AbstractConfigProducer<SearchCluster> parent, String clusterName, int index, ModelContext.FeatureFlags featureFlags) {
super(parent, clusterName, index);
documentDbsConfigProducer = new MultipleDocumentDatabasesConfigProducer(this, documentDbs);
rootDispatch = new DispatchGroup(this);
+ defaultDispatchPolicy = DispatchTuning.Builder.toDispatchPolicy(featureFlags.queryDispatchPolicy());
}
@Override
@@ -273,6 +277,15 @@ public class IndexedSearchCluster extends SearchCluster
return dispatchSpec;
}
+ private static DistributionPolicy.Enum toDistributionPolicy(DispatchTuning.DispatchPolicy tuning) {
+ return switch (tuning) {
+ case ADAPTIVE: yield DistributionPolicy.ADAPTIVE;
+ case ROUNDROBIN: yield DistributionPolicy.ROUNDROBIN;
+ case BEST_OF_RANDOM_2: yield DistributionPolicy.BEST_OF_RANDOM_2;
+ case LATENCY_AMORTIZED_OVER_REQUESTS: yield DistributionPolicy.LATENCY_AMORTIZED_OVER_REQUESTS;
+ case LATENCY_AMORTIZED_OVER_TIME: yield DistributionPolicy.LATENCY_AMORTIZED_OVER_TIME;
+ };
+ }
@Override
public void getConfig(DispatchConfig.Builder builder) {
for (SearchNode node : getSearchNodes()) {
@@ -289,14 +302,9 @@ public class IndexedSearchCluster extends SearchCluster
if (tuning.dispatch.getMinActiveDocsCoverage() != null)
builder.minActivedocsPercentage(tuning.dispatch.getMinActiveDocsCoverage());
if (tuning.dispatch.getDispatchPolicy() != null) {
- switch (tuning.dispatch.getDispatchPolicy()) {
- case ADAPTIVE:
- builder.distributionPolicy(DistributionPolicy.ADAPTIVE);
- break;
- case ROUNDROBIN:
- builder.distributionPolicy(DistributionPolicy.ROUNDROBIN);
- break;
- }
+ builder.distributionPolicy(toDistributionPolicy(tuning.dispatch.getDispatchPolicy()));
+ } else {
+ builder.distributionPolicy(toDistributionPolicy(defaultDispatchPolicy));
}
if (tuning.dispatch.getMaxHitsPerPartition() != null)
builder.maxHitsPerNode(tuning.dispatch.getMaxHitsPerPartition());
diff --git a/config-model/src/main/resources/schema/content.rnc b/config-model/src/main/resources/schema/content.rnc
index ff45b127b8b..5ca9a0792a2 100644
--- a/config-model/src/main/resources/schema/content.rnc
+++ b/config-model/src/main/resources/schema/content.rnc
@@ -82,7 +82,7 @@ ClusterControllerTuning = element cluster-controller {
DispatchTuning = element dispatch {
element max-hits-per-partition { xsd:nonNegativeInteger }? &
- element dispatch-policy { string "round-robin" | string "adaptive" | string "random" }? &
+ element dispatch-policy { string "round-robin" | string "adaptive" | string "random" | "best-of-random-2" | "latency-amortized-over-requests" }? &
element min-active-docs-coverage { xsd:double }? &
element top-k-probability { xsd:double }?
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
index cfb2fceb60b..17ce43bba2c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
@@ -1005,6 +1005,37 @@ public class ContentClusterTest extends ContentBaseTest {
verifyTopKProbabilityPropertiesControl();
}
+ private void verifyQueryDispatchPolicy(String policy, DispatchConfig.DistributionPolicy.Enum expected) {
+ TestProperties properties = new TestProperties();
+ if (policy != null) {
+ properties.setQueryDispatchPolicy(policy);
+ }
+ VespaModel model = createEnd2EndOneNode(properties);
+
+ ContentCluster cc = model.getContentClusters().get("storage");
+ DispatchConfig.Builder builder = new DispatchConfig.Builder();
+ cc.getSearch().getConfig(builder);
+
+ DispatchConfig cfg = new DispatchConfig(builder);
+ assertEquals(expected, cfg.distributionPolicy());
+ }
+
+ @Test
+ public void default_dispatch_controlled_by_properties() {
+ verifyQueryDispatchPolicy(null, DispatchConfig.DistributionPolicy.ADAPTIVE);
+ verifyQueryDispatchPolicy("adaptive", DispatchConfig.DistributionPolicy.ADAPTIVE);
+ verifyQueryDispatchPolicy("round-robin", DispatchConfig.DistributionPolicy.ROUNDROBIN);
+ verifyQueryDispatchPolicy("best-of-random-2", DispatchConfig.DistributionPolicy.BEST_OF_RANDOM_2);
+ verifyQueryDispatchPolicy("latency-amortized-over-requests", DispatchConfig.DistributionPolicy.LATENCY_AMORTIZED_OVER_REQUESTS);
+ verifyQueryDispatchPolicy("latency-amortized-over-time", DispatchConfig.DistributionPolicy.LATENCY_AMORTIZED_OVER_TIME);
+ try {
+ verifyQueryDispatchPolicy("unknown", DispatchConfig.DistributionPolicy.ADAPTIVE);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Unknown dispatch policy 'unknown'", e.getMessage());
+ }
+ }
+
private boolean resolveThreePhaseUpdateConfigWithFeatureFlag(boolean flagEnableThreePhase) {
VespaModel model = createEnd2EndOneNode(new TestProperties().setUseThreePhaseUpdates(flagEnableThreePhase));
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/DispatchTuningTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/DispatchTuningTest.java
index cddbe267628..af547965749 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/DispatchTuningTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/DispatchTuningTest.java
@@ -20,7 +20,7 @@ public class DispatchTuningTest {
.setTopKProbability(18.3)
.build();
assertEquals(69, dispatch.getMaxHitsPerPartition().intValue());
- assertEquals(12.5, dispatch.getMinActiveDocsCoverage().doubleValue(), 0.0);
+ assertEquals(12.5, dispatch.getMinActiveDocsCoverage(), 0.0);
assertEquals(DispatchTuning.DispatchPolicy.ROUNDROBIN, dispatch.getDispatchPolicy());
assertEquals(18.3, dispatch.getTopkProbability(), 0.0);
}
@@ -44,6 +44,33 @@ public class DispatchTuningTest {
}
@Test
+ void requireThatLatencyAmortizedOverRequestsDispatchWork() {
+ DispatchTuning dispatch = new DispatchTuning.Builder()
+ .setDispatchPolicy("latency-amortized-over-requests")
+ .build();
+ assertEquals(DispatchTuning.DispatchPolicy.LATENCY_AMORTIZED_OVER_REQUESTS, dispatch.getDispatchPolicy());
+ assertNull(dispatch.getMinActiveDocsCoverage());
+ }
+
+ @Test
+ void requireThatLatencyAmortizedOverTimeDispatchWork() {
+ DispatchTuning dispatch = new DispatchTuning.Builder()
+ .setDispatchPolicy("latency-amortized-over-time")
+ .build();
+ assertEquals(DispatchTuning.DispatchPolicy.LATENCY_AMORTIZED_OVER_TIME, dispatch.getDispatchPolicy());
+ assertNull(dispatch.getMinActiveDocsCoverage());
+ }
+
+ @Test
+ void requireThatBestOfRandom2DispatchWork() {
+ DispatchTuning dispatch = new DispatchTuning.Builder()
+ .setDispatchPolicy("best-of-random-2")
+ .build();
+ assertEquals(DispatchTuning.DispatchPolicy.BEST_OF_RANDOM_2, dispatch.getDispatchPolicy());
+ assertNull(dispatch.getMinActiveDocsCoverage());
+ }
+
+ @Test
void requireThatDefaultsAreNull() {
DispatchTuning dispatch = new DispatchTuning.Builder().build();
assertNull(dispatch.getMaxHitsPerPartition());
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/DomDispatchTuningBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/DomDispatchTuningBuilderTest.java
index 564d6024acf..9eaa4ea6ed3 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/DomDispatchTuningBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/DomDispatchTuningBuilderTest.java
@@ -61,36 +61,37 @@ public class DomDispatchTuningBuilderTest {
" </tuning>" +
"</content>");
assertEquals(69, dispatch.getMaxHitsPerPartition().intValue());
- assertEquals(12.5, dispatch.getMinActiveDocsCoverage().doubleValue(), 0.0);
- assertEquals(0.999, dispatch.getTopkProbability().doubleValue(), 0.0);
+ assertEquals(12.5, dispatch.getMinActiveDocsCoverage(), 0.0);
+ assertEquals(0.999, dispatch.getTopkProbability(), 0.0);
}
- @Test
- void requireThatTuningDispatchPolicyRoundRobin() throws Exception {
- DispatchTuning dispatch = newTuningDispatch(
- "<content>" +
- " <tuning>" +
- " <dispatch>" +
- " <dispatch-policy>round-robin</dispatch-policy>" +
- " </dispatch>" +
- " </tuning>" +
- "</content>");
- assertEquals(DispatchTuning.DispatchPolicy.ROUNDROBIN, dispatch.getDispatchPolicy());
+ private static String dispatchPolicy(String policy) {
+ return "<content>" +
+ " <tuning>" +
+ " <dispatch>" +
+ " <dispatch-policy>" + policy +"</dispatch-policy>" +
+ " </dispatch>" +
+ " </tuning>" +
+ "</content>";
}
@Test
- void requireThatTuningDispatchPolicyRandom() throws Exception {
- DispatchTuning dispatch = newTuningDispatch(
- "<content>" +
- " <tuning>" +
- " <dispatch>" +
- " <dispatch-policy>random</dispatch-policy>" +
- " </dispatch>" +
- " </tuning>" +
- "</content>");
- assertEquals(DispatchTuning.DispatchPolicy.ADAPTIVE, dispatch.getDispatchPolicy());
+ void requireThatTuningDispatchPolicies() throws Exception {
+ assertEquals(DispatchTuning.DispatchPolicy.ROUNDROBIN,
+ newTuningDispatch(dispatchPolicy("round-robin")).getDispatchPolicy());
+ assertEquals(DispatchTuning.DispatchPolicy.ADAPTIVE,
+ newTuningDispatch(dispatchPolicy("random")).getDispatchPolicy());
+ assertEquals(DispatchTuning.DispatchPolicy.ADAPTIVE,
+ newTuningDispatch(dispatchPolicy("adaptive")).getDispatchPolicy());
+ assertEquals(DispatchTuning.DispatchPolicy.BEST_OF_RANDOM_2,
+ newTuningDispatch(dispatchPolicy("best-of-random-2")).getDispatchPolicy());
+ assertEquals(DispatchTuning.DispatchPolicy.LATENCY_AMORTIZED_OVER_REQUESTS,
+ newTuningDispatch(dispatchPolicy("latency-amortized-over-requests")).getDispatchPolicy());
+ assertEquals(DispatchTuning.DispatchPolicy.LATENCY_AMORTIZED_OVER_TIME,
+ newTuningDispatch(dispatchPolicy("latency-amortized-over-time")).getDispatchPolicy());
}
+
private static DispatchTuning newTuningDispatch(String xml) throws Exception {
return DomTuningDispatchBuilder.build(
new ModelElement(DocumentBuilderFactory.newInstance()
diff --git a/configdefinitions/src/vespa/dispatch.def b/configdefinitions/src/vespa/dispatch.def
index 37b49ecaeb9..e26a136d245 100644
--- a/configdefinitions/src/vespa/dispatch.def
+++ b/configdefinitions/src/vespa/dispatch.def
@@ -8,7 +8,7 @@ namespace=vespa.config.search
minActivedocsPercentage double default=97.0
# Distribution policy for group selection
-distributionPolicy enum { ROUNDROBIN, ADAPTIVE } default=ADAPTIVE
+distributionPolicy enum { ROUNDROBIN, BEST_OF_RANDOM_2, LATENCY_AMORTIZED_OVER_REQUESTS, LATENCY_AMORTIZED_OVER_TIME, ADAPTIVE } default=ADAPTIVE
## Maximum number of hits that will be requested from a single node
## in this dataset. If not set, there is no limit. Using this option
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
index 1aa70ff4b5b..59a48ad3c7e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
@@ -25,7 +25,6 @@ import com.yahoo.vespa.config.server.application.ConfigNotConvergedException;
import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
import com.yahoo.vespa.config.server.configchange.ReindexActions;
import com.yahoo.vespa.config.server.configchange.RestartActions;
-import com.yahoo.vespa.config.server.session.LocalSession;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.session.Session;
import com.yahoo.vespa.config.server.session.SessionRepository;
@@ -161,9 +160,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
}
private void deleteSession() {
- SessionRepository sessionRepository = sessionRepository();
- LocalSession localSession = sessionRepository.getLocalSession(session.getSessionId());
- sessionRepository.deleteLocalSession(localSession);
+ sessionRepository().deleteLocalSession(session.getSessionId());
}
private SessionRepository sessionRepository() {
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 068323f7784..7c7a12bbf36 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
@@ -166,6 +166,7 @@ public class ModelContextImpl implements ModelContext {
public static class FeatureFlags implements ModelContext.FeatureFlags {
+ private final String queryDispatchPolicy;
private final double defaultTermwiseLimit;
private final boolean useThreePhaseUpdates;
private final String feedSequencer;
@@ -276,8 +277,10 @@ public class ModelContextImpl implements ModelContext {
this.mbus_cpp_events_before_wakeup = flagValue(source, appId, version, Flags.MBUS_CPP_EVENTS_BEFORE_WAKEUP);
this.rpc_num_targets = flagValue(source, appId, version, Flags.RPC_NUM_TARGETS);
this.rpc_events_before_wakeup = flagValue(source, appId, version, Flags.RPC_EVENTS_BEFORE_WAKEUP);
+ this.queryDispatchPolicy = flagValue(source, appId, version, Flags.QUERY_DISPATCH_POLICY);
}
+ @Override public String queryDispatchPolicy() { return queryDispatchPolicy;}
@Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; }
@Override public boolean useThreePhaseUpdates() { return useThreePhaseUpdates; }
@Override public String feedSequencerType() { return feedSequencer; }
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 f2fbff84907..fc33830e707 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
@@ -39,7 +39,6 @@ import com.yahoo.vespa.config.server.http.v2.response.ReindexingResponse;
import com.yahoo.vespa.config.server.tenant.Tenant;
import java.io.IOException;
-import java.io.OutputStream;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
index a6bbd6c20a2..2e9c28bdc3b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
@@ -221,15 +221,31 @@ public class SessionRepository {
Set<LocalSession> sessionIds = new HashSet<>();
for (File session : sessions) {
long sessionId = Long.parseLong(session.getName());
- SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId);
- File sessionDir = getAndValidateExistingSessionAppDir(sessionId);
- ApplicationPackage applicationPackage = FilesApplicationPackage.fromFile(sessionDir);
- LocalSession localSession = new LocalSession(tenantName, sessionId, applicationPackage, sessionZKClient);
+ LocalSession localSession = getSessionFromFile(sessionId);
sessionIds.add(localSession);
}
return sessionIds;
}
+ private LocalSession getSessionFromFile(long sessionId) {
+ SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId);
+ File sessionDir = getAndValidateExistingSessionAppDir(sessionId);
+ ApplicationPackage applicationPackage = FilesApplicationPackage.fromFile(sessionDir);
+ return new LocalSession(tenantName, sessionId, applicationPackage, sessionZKClient);
+ }
+
+ public Set<Long> getLocalSessionsIdsFromFileSystem() {
+ File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter);
+ if (sessions == null) return Set.of();
+
+ Set<Long> sessionIds = new HashSet<>();
+ for (File session : sessions) {
+ long sessionId = Long.parseLong(session.getName());
+ sessionIds.add(sessionId);
+ }
+ return sessionIds;
+ }
+
public ConfigChangeActions prepareLocalSession(Session session, DeployLogger logger, PrepareParams params, Instant now) {
params.vespaVersion().ifPresent(version -> {
if ( ! params.isBootstrap() && ! modelFactoryRegistry.allVersions().contains(version))
@@ -310,8 +326,7 @@ public class SessionRepository {
}
// Will delete session data in ZooKeeper and file system
- public void deleteLocalSession(LocalSession session) {
- long sessionId = session.getSessionId();
+ public void deleteLocalSession(long sessionId) {
log.log(Level.FINE, () -> "Deleting local session " + sessionId);
SessionStateWatcher watcher = sessionStateWatchers.remove(sessionId);
if (watcher != null) watcher.close();
@@ -323,7 +338,7 @@ public class SessionRepository {
private void deleteAllSessions() {
for (LocalSession session : getLocalSessions()) {
- deleteLocalSession(session);
+ deleteLocalSession(session.getSessionId());
}
}
@@ -586,35 +601,48 @@ public class SessionRepository {
public void deleteExpiredSessions(Map<ApplicationId, Long> activeSessions) {
log.log(Level.FINE, () -> "Deleting expired local sessions for tenant '" + tenantName + "'");
- Set<LocalSession> toDelete = new HashSet<>();
+ Set<Long> sessionIdsToDelete = new HashSet<>();
Set<Long> newSessions = findNewSessionsInFileSystem();
try {
- for (LocalSession candidate : getLocalSessionsFromFileSystem()) {
+ for (long sessionId : getLocalSessionsIdsFromFileSystem()) {
// Skip sessions newly added (we might have a session in the file system, but not in ZooKeeper,
// we don't want to touch any of them)
- if (newSessions.contains(candidate.getSessionId()))
+ if (newSessions.contains(sessionId))
continue;
- Instant createTime = candidate.getCreateTime();
- log.log(Level.FINE, () -> "Candidate local session for deletion: " + candidate.getSessionId() +
- ", created: " + createTime + ", state " + candidate.getStatus() + ", can be deleted: " + canBeDeleted(candidate));
+ var sessionZooKeeperClient = createSessionZooKeeperClient(sessionId);
+ Instant createTime = sessionZooKeeperClient.readCreateTime();
+ Session.Status status = sessionZooKeeperClient.readStatus();
- if (hasExpired(createTime) && canBeDeleted(candidate)) {
- toDelete.add(candidate);
+ log.log(Level.FINE, () -> "Candidate local session for deletion: " + sessionId +
+ ", created: " + createTime + ", status " + status + ", can be deleted: " + canBeDeleted(sessionId, status) +
+ ", hasExpired: " + hasExpired(createTime));
+
+ if (hasExpired(createTime) && canBeDeleted(sessionId, status)) {
+ log.log(Level.FINE, () -> "expired: " + hasExpired(createTime) + ", can be deleted: " + canBeDeleted(sessionId, status));
+ sessionIdsToDelete.add(sessionId);
} else if (createTime.plus(Duration.ofDays(1)).isBefore(clock.instant())) {
- Optional<ApplicationId> applicationId = candidate.getOptionalApplicationId();
+ LocalSession session;
+ log.log(Level.FINE, () -> "not expired, but more than 1 day old: " + sessionId);
+ try {
+ session = getSessionFromFile(sessionId);
+ } catch (Exception e) {
+ log.log(Level.FINE, () -> "could not get session from file: " + sessionId + ": " + e.getMessage());
+ continue;
+ }
+ Optional<ApplicationId> applicationId = session.getOptionalApplicationId();
if (applicationId.isEmpty()) continue;
Long activeSession = activeSessions.get(applicationId.get());
- if (activeSession == null || activeSession != candidate.getSessionId()) {
- toDelete.add(candidate);
- log.log(Level.FINE, () -> "Will delete inactive session " + candidate.getSessionId() + " created " +
+ if (activeSession == null || activeSession != sessionId) {
+ sessionIdsToDelete.add(sessionId);
+ log.log(Level.FINE, () -> "Will delete inactive session " + sessionId + " created " +
createTime + " for '" + applicationId + "'");
}
}
}
- toDelete.forEach(this::deleteLocalSession);
+ sessionIdsToDelete.forEach(this::deleteLocalSession);
// Make sure to catch here, to avoid executor just dying in case of issues ...
} catch (Throwable e) {
@@ -628,16 +656,16 @@ public class SessionRepository {
}
// Sessions with state other than UNKNOWN or ACTIVATE or old sessions in UNKNOWN state
- private boolean canBeDeleted(LocalSession candidate) {
- return ( ! List.of(Session.Status.UNKNOWN, Session.Status.ACTIVATE).contains(candidate.getStatus()))
- || oldSessionDirWithUnknownStatus(candidate);
+ private boolean canBeDeleted(long sessionId, Session.Status status) {
+ return ( ! List.of(Session.Status.UNKNOWN, Session.Status.ACTIVATE).contains(status))
+ || oldSessionDirWithUnknownStatus(sessionId, status);
}
- private boolean oldSessionDirWithUnknownStatus(LocalSession session) {
+ private boolean oldSessionDirWithUnknownStatus(long sessionId, Session.Status status) {
Duration expiryTime = Duration.ofHours(configserverConfig.keepSessionsWithUnknownStatusHours());
- File sessionDir = tenantFileSystemDirs.getUserApplicationDir(session.getSessionId());
+ File sessionDir = tenantFileSystemDirs.getUserApplicationDir(sessionId);
return sessionDir.exists()
- && session.getStatus() == Session.Status.UNKNOWN
+ && status == Session.Status.UNKNOWN
&& created(sessionDir).plus(expiryTime).isBefore(clock.instant());
}
diff --git a/configserver/src/main/sh/start-configserver b/configserver/src/main/sh/start-configserver
index 8127b0bfafc..f223c0a8fb9 100755
--- a/configserver/src/main/sh/start-configserver
+++ b/configserver/src/main/sh/start-configserver
@@ -151,9 +151,7 @@ export standalone_jdisc_container__deployment_profile=configserver
# class path
CP="${VESPA_HOME}/lib/jars/jdisc_core-jar-with-dependencies.jar"
-baseuserargs="$VESPA_CONFIGSERVER_JVMARGS"
-serveruserargs="$cloudconfig_server__jvmargs"
-jvmargs="$baseuserargs $serveruserargs"
+jvmoptions="$VESPA_CONFIGSERVER_JVMARGS"
export LD_PRELOAD=${VESPA_HOME}/lib64/vespa/malloc/libvespamalloc.so
@@ -161,8 +159,8 @@ rm -f $cfpfile
vespa-run-as-vespa-user sh -c "printenv > $cfpfile"
fixddir $bundlecachedir
-heap_min=$(get_min_heap_mb "${jvmargs}" 128)
-heap_max=$(get_max_heap_mb "${jvmargs}" 2048)
+heap_min=$(get_min_heap_mb "${jvmoptions}" 128)
+heap_max=$(get_max_heap_mb "${jvmoptions}" 2048)
vespa-run-as-vespa-user vespa-runserver -s ${VESPA_SERVICE_NAME} -r 30 -p $pidfile -- \
java \
-Xms${heap_min}m -Xmx${heap_max}m \
@@ -174,7 +172,7 @@ vespa-run-as-vespa-user vespa-runserver -s ${VESPA_SERVICE_NAME} -r 30 -p $pidfi
-XX:+ExitOnOutOfMemoryError \
-XX:-OmitStackTraceInFastThrow \
-XX:MaxJavaStackTraceDepth=1000000 \
- $jvmargs \
+ $jvmoptions \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.net=ALL-UNNAMED \
diff --git a/configserver/src/test/apps/illegalApp2/hosts.xml b/configserver/src/test/apps/illegalApp2/hosts.xml
new file mode 100644
index 00000000000..a515a4e97da
--- /dev/null
+++ b/configserver/src/test/apps/illegalApp2/hosts.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<hosts>
+ <host name="mytesthost">
+ <alias>node1</alias>
+ </host>
+</hosts>
diff --git a/configserver/src/test/apps/illegalApp2/schemas/music.sd b/configserver/src/test/apps/illegalApp2/schemas/music.sd
new file mode 100644
index 00000000000..f4b11d1e8e4
--- /dev/null
+++ b/configserver/src/test/apps/illegalApp2/schemas/music.sd
@@ -0,0 +1,50 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# A basic search definition - called music, should be saved to music.sd
+search music {
+
+ # It contains one document type only - called music as well
+ document music {
+
+ field title type string {
+ indexing: summary | index # How this field should be indexed
+ # index-to: title, default # Create two indexes
+ weight: 75 # Ranking importancy of this field, used by the built in nativeRank feature
+ }
+
+ field artist type string {
+ indexing: summary | attribute | index
+ # index-to: artist, default
+
+ weight: 25
+ }
+
+ field year type int {
+ indexing: summary | attribute
+ }
+
+ # Increase query
+ field popularity type int {
+ indexing: summary | attribute
+ }
+
+ field url type uri {
+ indexing: summary | index
+ }
+
+ }
+
+ rank-profile default inherits default {
+ first-phase {
+ expression: nativeRank(title,artist) + attribute(popularity)
+ }
+
+ }
+
+ rank-profile textmatch inherits default {
+ first-phase {
+ expression: nativeRank(title,artist)
+ }
+
+ }
+
+}
diff --git a/configserver/src/test/apps/illegalApp2/services.xml b/configserver/src/test/apps/illegalApp2/services.xml
new file mode 100644
index 00000000000..3e957a0e228
--- /dev/null
+++ b/configserver/src/test/apps/illegalApp2/services.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<services version="1.0">
+
+ <admin version="2.0">
+ <adminserver hostalias="node1"/>
+ <logserver hostalias="node1" />
+ </admin>
+
+ <content version="1.0">
+ <redundancy>2</redundancy>
+ <documents>
+ <document type="music" mode="index"/>
+ </documents>
+ <nodes>
+ <node hostalias="node1" distribution-key="0"/>
+ </nodes>
+
+ </content>
+
+ <container version="1.0">
+ <include dir='file:///etc/passwd'/>
+ <document-processing compressdocuments="true">
+ <chain id="ContainerWrapperTest">
+ <documentprocessor id="com.yahoo.vespa.config.AppleDocProc"/>
+ </chain>
+ </document-processing>
+
+ <config name="project.specific">
+ <value>someval</value>
+ </config>
+
+ <nodes>
+ <node hostalias="node1" />
+ </nodes>
+
+ </container>
+
+</services>
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index d1d8c165124..a8439a9061c 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -59,9 +59,7 @@ import org.junit.rules.TemporaryFolder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.file.Files;
-import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.Instant;
@@ -94,6 +92,7 @@ public class ApplicationRepositoryTest {
private final static File testAppLogServerWithContainer = new File("src/test/apps/app-logserver-with-container");
private final static File app1 = new File("src/test/apps/cs1");
private final static File app2 = new File("src/test/apps/cs2");
+ private final static File illegalApp2 = new File("src/test/apps/illegalapp2");
private final static TenantName tenant1 = TenantName.from("test1");
private final static TenantName tenant2 = TenantName.from("test2");
@@ -456,7 +455,7 @@ public class ApplicationRepositoryTest {
deployment4.get().prepare(); // session 5 (not activated)
assertEquals(2, sessionRepository.getLocalSessions().size());
- sessionRepository.deleteLocalSession(localSession);
+ sessionRepository.deleteLocalSession(localSession.getSessionId());
assertEquals(1, sessionRepository.getLocalSessions().size());
// Create a local session without any data in zookeeper (corner case seen in production occasionally)
@@ -464,28 +463,29 @@ public class ApplicationRepositoryTest {
int sessionId = 6;
TenantName tenantName = tester.tenant().getName();
Instant session6CreateTime = clock.instant();
- Files.createDirectory(new TenantFileSystemDirs(serverdb, tenantName).getUserApplicationDir(sessionId).toPath());
- LocalSession localSession2 = new LocalSession(tenant1,
+ TenantFileSystemDirs tenantFileSystemDirs = new TenantFileSystemDirs(serverdb, tenantName);
+ Files.createDirectory(tenantFileSystemDirs.getUserApplicationDir(sessionId).toPath());
+ String hostName = ConfigUtils.getCanonicalHostName();
+ LocalSession localSession2 = new LocalSession(tenantName,
sessionId,
FilesApplicationPackage.fromFile(testApp),
- new SessionZooKeeperClient(curator,
- tenantName,
- sessionId,
- ConfigUtils.getCanonicalHostName()));
+ new SessionZooKeeperClient(curator, tenantName, sessionId, hostName));
sessionRepository.addLocalSession(localSession2);
assertEquals(2, sessionRepository.getLocalSessions().size());
// Create a session, set status to UNKNOWN, we don't want to expire those (creation time is then EPOCH,
// so will be candidate for expiry)
- Session session = sessionRepository.createRemoteSession(7);
- sessionRepository.createSetStatusTransaction(session, Session.Status.UNKNOWN);
+ sessionId = 7;
+ Session session = sessionRepository.createRemoteSession(sessionId);
+ sessionRepository.createSessionZooKeeperClient(sessionId).createNewSession(clock.instant());
+ sessionRepository.createSetStatusTransaction(session, Session.Status.UNKNOWN).commit();
assertEquals(2, sessionRepository.getLocalSessions().size()); // Still 2, no new local session
// Check that trying to expire local session when there exists a local session without any data in zookeeper
// should not delete session if this is a new file ...
deleteExpiredLocalSessionsAndAssertNumberOfSessions(2, tester, sessionRepository);
- // ... but it should be deleted if some time has passed
+ // ... but it should be deleted when some time has passed
clock.advance(Duration.ofSeconds(60));
deleteExpiredLocalSessionsAndAssertNumberOfSessions(1, tester, sessionRepository);
@@ -495,6 +495,21 @@ public class ApplicationRepositoryTest {
// Advance time, session SHOULD be deleted
clock.advance(Duration.ofHours(configserverConfig.keepSessionsWithUnknownStatusHours()).plus(Duration.ofMinutes(1)));
deleteExpiredLocalSessionsAndAssertNumberOfSessions(0, tester, sessionRepository);
+
+ // Create a local session with invalid application package and check that expiring local sessions still works
+ sessionId = 8;
+ java.nio.file.Path applicationPath = tenantFileSystemDirs.getUserApplicationDir(sessionId).toPath();
+ session = sessionRepository.createRemoteSession(sessionId);
+ sessionRepository.createSessionZooKeeperClient(sessionId).createNewSession(clock.instant());
+ sessionRepository.createSetStatusTransaction(session, Session.Status.PREPARE).commit();
+ Files.createDirectory(applicationPath);
+ Files.writeString(Files.createFile(applicationPath.resolve("services.xml")), "non-legal xml");
+ assertEquals(0, sessionRepository.getLocalSessions().size()); // Will not show up in local sessions
+
+ // Advance time, session SHOULD be deleted
+ clock.advance(Duration.ofHours(configserverConfig.keepSessionsWithUnknownStatusHours()).plus(Duration.ofMinutes(1)));
+ deleteExpiredLocalSessionsAndAssertNumberOfSessions(0, tester, sessionRepository);
+ assertFalse(applicationPath.toFile().exists()); // App has been deleted
}
@Test
@@ -737,16 +752,6 @@ public class ApplicationRepositoryTest {
return applicationRepository.getMetadataFromLocalSession(tenant, sessionId);
}
- private void setCreatedTime(java.nio.file.Path file, Instant createdTime) {
- try {
- BasicFileAttributeView attributes = Files.getFileAttributeView(file, BasicFileAttributeView.class);
- FileTime time = FileTime.fromMillis(createdTime.toEpochMilli());
- attributes.setTimes(time, time, time);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
/** Stores all added or set values for each metric and context. */
static class MockMetric implements Metric {
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
index 7782e54dc90..7ea91726673 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
@@ -56,7 +56,6 @@ import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Phaser;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -85,7 +84,7 @@ public final class ConfiguredApplication implements Application {
// Subscriber that is used when this is not a standalone-container. Subscribes
// to config to make sure that container will be registered in slobrok (by {@link com.yahoo.jrt.slobrok.api.Register})
// if slobrok config changes (typically slobroks moving to other nodes)
- private final Optional<SlobrokConfigSubscriber> slobrokConfigSubscriber;
+ private final SlobrokConfigSubscriber slobrokConfigSubscriber;
private final ShutdownDeadline shutdownDeadline;
//TODO: FilterChainRepository should instead always be set up in the model.
@@ -151,8 +150,8 @@ public final class ConfiguredApplication implements Application {
this.metric = metric;
this.configId = System.getProperty("config.id");
this.slobrokConfigSubscriber = (subscriberFactory instanceof CloudSubscriberFactory)
- ? Optional.of(new SlobrokConfigSubscriber(configId))
- : Optional.empty();
+ ? new SlobrokConfigSubscriber(configId)
+ : null;
this.restrictedOsgiFramework = new DisableOsgiFramework(new RestrictedBundleContext(osgiFramework.bundleContext()));
this.shutdownDeadline = new ShutdownDeadline(configId);
this.reconfigurerThread = new Thread(this::doReconfigurationLoop, "configured-application-reconfigurer");
@@ -161,8 +160,8 @@ public final class ConfiguredApplication implements Application {
@Override
public void start() {
- qrConfig = getConfig(QrConfig.class, true);
- reconfigure(qrConfig);
+ qrConfig = getConfig(QrConfig.class);
+ reconfigure(qrConfig.shutdown());
hackToInitializeServer(qrConfig);
ContainerBuilder builder = createBuilderWithGuiceBindings();
@@ -173,27 +172,37 @@ public final class ConfiguredApplication implements Application {
portWatcher.setDaemon(true);
portWatcher.start();
- if (setupRpc()) {
- slobrokRegistrator = registerInSlobrok(qrConfig); // marks this as up
- }
+ setupRpc(qrConfig);
}
- private boolean setupRpc() {
- if ( ! qrConfig.rpc().enabled()) return false;
+ private synchronized void setupRpc(QrConfig cfg) {
+ if (!cfg.rpc().enabled()) return;
supervisor = new Supervisor(new Transport("configured-application")).setDropEmptyBuffers(true);
supervisor.addMethod(new Method("prepareStop", "d", "", this::prepareStop));
- Spec listenSpec = new Spec(qrConfig.rpc().port());
+ listenRpc(cfg);
+ }
+
+ private synchronized void listenRpc(QrConfig cfg) {
+ Spec listenSpec = new Spec(cfg.rpc().port());
try {
acceptor = supervisor.listen(listenSpec);
- return true;
+ slobrokRegistrator = registerInSlobrok(cfg, acceptor.port());
} catch (ListenFailedException e) {
throw new RuntimeException("Could not create rpc server listening on " + listenSpec, e);
}
}
- private Register registerInSlobrok(QrConfig qrConfig) {
+ private void reListenRpc(QrConfig cfg) {
+ unregisterInSlobrok();
+ if (supervisor == null) {
+ setupRpc(cfg);
+ } else if (cfg.rpc().enabled()) {
+ listenRpc(cfg);
+ }
+ }
+ private Register registerInSlobrok(QrConfig qrConfig, int port) {
SlobrokList slobrokList = getSlobrokList();
- Spec mySpec = new Spec(HostName.getLocalhost(), acceptor.port());
+ Spec mySpec = new Spec(HostName.getLocalhost(), port);
Register slobrokRegistrator = new Register(supervisor, slobrokList, mySpec);
slobrokRegistrator.registerName(qrConfig.rpc().slobrokId());
log.log(Level.INFO, "Registered name '" + qrConfig.rpc().slobrokId() +
@@ -205,23 +214,25 @@ public final class ConfiguredApplication implements Application {
// or need to get the config directly (standalone container)
private SlobrokList getSlobrokList() {
SlobrokList slobrokList;
- if (slobrokConfigSubscriber.isPresent()) {
- slobrokList = slobrokConfigSubscriber.get().getSlobroks();
+ if (slobrokConfigSubscriber != null) {
+ slobrokList = slobrokConfigSubscriber.getSlobroks();
} else {
slobrokList = new SlobrokList();
- SlobroksConfig slobrokConfig = getConfig(SlobroksConfig.class, true);
+ SlobroksConfig slobrokConfig = getConfig(SlobroksConfig.class);
slobrokList.setup(slobrokConfig.slobrok().stream().map(SlobroksConfig.Slobrok::connectionspec).toArray(String[]::new));
}
return slobrokList;
}
- private void unregisterInSlobrok() {
- if (slobrokRegistrator != null)
+ private synchronized void unregisterInSlobrok() {
+ if (slobrokRegistrator != null) {
slobrokRegistrator.shutdown();
- if (acceptor != null)
+ slobrokRegistrator = null;
+ }
+ if (acceptor != null) {
acceptor.shutdown().join();
- if (supervisor != null)
- supervisor.transport().shutdown().join();
+ acceptor = null;
+ }
}
private static void hackToInitializeServer(QrConfig config) {
@@ -234,11 +245,11 @@ public final class ConfiguredApplication implements Application {
}
}
- private <T extends ConfigInstance> T getConfig(Class<T> configClass, boolean isInitializing) {
+ private <T extends ConfigInstance> T getConfig(Class<T> configClass) {
Subscriber subscriber = subscriberFactory.getSubscriber(Collections.singleton(new ConfigKey<>(configClass, configId)),
configClass.getName());
try {
- subscriber.waitNextGeneration(isInitializing);
+ subscriber.waitNextGeneration(true);
return configClass.cast(first(subscriber.config().values()));
} finally {
subscriber.close();
@@ -251,24 +262,32 @@ public final class ConfiguredApplication implements Application {
try {
while (true) {
subscriber.waitNextGeneration(false);
- QrConfig newConfig = QrConfig.class.cast(first(subscriber.config().values()));
- reconfigure(qrConfig);
- if (qrConfig.rpc().port() != newConfig.rpc().port()) {
- com.yahoo.protect.Process.logAndDie(
- "Rpc port config has changed from " +
- qrConfig.rpc().port() + " to " + newConfig.rpc().port() +
- ". This we can not handle without a restart so we will just bail out.");
+ if (first(subscriber.config().values()) instanceof QrConfig newConfig) {
+ reconfigure(newConfig.shutdown());
+ synchronized (this) {
+ if (qrConfig.rpc().port() != newConfig.rpc().port()) {
+ log.log(Level.INFO, "Rpc port changed from " + qrConfig.rpc().port() + " to " + newConfig.rpc().port());
+ try {
+ reListenRpc(newConfig);
+ } catch (Throwable e) {
+ com.yahoo.protect.Process.logAndDie("Rpc port config has changed from "
+ + qrConfig.rpc().port() + " to " + newConfig.rpc().port() +
+ ", and we were not able to reconfigure so we will just bail out and restart.", e);
+ }
+ }
+ qrConfig = newConfig;
+ }
+ log.fine("Received new QrConfig :" + newConfig);
}
- log.fine("Received new QrConfig :" + newConfig);
}
} finally {
subscriber.close();
}
}
- void reconfigure(QrConfig qrConfig) {
- dumpHeapOnShutdownTimeout.set(qrConfig.shutdown().dumpHeapOnTimeout());
- shutdownTimeoutS.set(qrConfig.shutdown().timeout());
+ private void reconfigure(QrConfig.Shutdown shutdown) {
+ dumpHeapOnShutdownTimeout.set(shutdown.dumpHeapOnTimeout());
+ shutdownTimeoutS.set(shutdown.timeout());
}
private void initializeAndActivateContainer(ContainerBuilder builder, Runnable cleanupTask) {
@@ -454,9 +473,13 @@ public final class ConfiguredApplication implements Application {
if (configurer != null) {
configurer.shutdown();
}
- slobrokConfigSubscriber.ifPresent(SlobrokConfigSubscriber::shutdown);
+ if (slobrokConfigSubscriber != null) {
+ slobrokConfigSubscriber.shutdown();
+ }
Container.get().shutdown();
unregisterInSlobrok();
+ if (supervisor != null)
+ supervisor.transport().shutdown().join();
shutdownDeadline.cancel();
log.info("Destroy: Finished");
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java
index 9f57512f657..5da1f1a07be 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java
@@ -5,12 +5,21 @@ import com.yahoo.language.Language;
import com.yahoo.language.process.Segmenter;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.query.*;
+import com.yahoo.prelude.query.AndSegmentItem;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.IndexedItem;
+import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.NullItem;
+import com.yahoo.prelude.query.PhraseItem;
+import com.yahoo.prelude.query.PhraseSegmentItem;
+import com.yahoo.prelude.query.WordItem;
import com.yahoo.search.query.QueryTree;
import com.yahoo.search.query.parser.Parsable;
import com.yahoo.search.query.parser.ParserEnvironment;
-import java.util.*;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
/**
* The Vespa query parser.
@@ -20,6 +29,7 @@ import java.util.*;
*/
public abstract class AbstractParser implements CustomParser {
+
/** The current submodes of this parser */
protected Submodes submodes = new Submodes();
@@ -32,6 +42,8 @@ public abstract class AbstractParser implements CustomParser {
/** The IndexFacts.Session of this query */
protected IndexFacts.Session indexFacts;
+ protected String defaultIndex;
+
/**
* The counter for braces in URLs, braces in URLs are accepted so long as
* they are balanced.
@@ -125,28 +137,29 @@ public abstract class AbstractParser implements CustomParser {
@Override
public final Item parse(String queryToParse, String filterToParse, Language parsingLanguage,
- IndexFacts.Session indexFacts, String defaultIndexName) {
- return parse(queryToParse, filterToParse, parsingLanguage, indexFacts, defaultIndexName, null);
+ IndexFacts.Session indexFacts, String defaultIndex) {
+ return parse(queryToParse, filterToParse, parsingLanguage, indexFacts, defaultIndex, null);
}
private Item parse(String queryToParse, String filterToParse, Language parsingLanguage,
- IndexFacts.Session indexFacts, String defaultIndexName, Parsable parsable) {
+ IndexFacts.Session indexFacts, String defaultIndex, Parsable parsable) {
if (queryToParse == null) return null;
- if (defaultIndexName != null)
- defaultIndexName = indexFacts.getCanonicName(defaultIndexName);
+ if (defaultIndex != null)
+ defaultIndex = indexFacts.getCanonicName(defaultIndex);
- tokenize(queryToParse, defaultIndexName, indexFacts, parsingLanguage);
+ tokenize(queryToParse, defaultIndex, indexFacts, parsingLanguage);
if (parsingLanguage == null && parsable != null) {
- String detectionText = generateLanguageDetectionTextFrom(tokens, indexFacts, defaultIndexName);
+ String detectionText = generateLanguageDetectionTextFrom(tokens, indexFacts, defaultIndex);
if (detectionText.isEmpty()) // heuristic detection text extraction is fallible
detectionText = queryToParse;
parsingLanguage = parsable.getOrDetectLanguage(detectionText);
}
- setState(parsingLanguage, indexFacts);
- Item root = parseItems(defaultIndexName);
+ setState(parsingLanguage, indexFacts, defaultIndex);
+ Item root = parseItems();
+
if (filterToParse != null) {
AnyParser filterParser = new AnyParser(environment);
if (root == null) {
@@ -155,11 +168,9 @@ public abstract class AbstractParser implements CustomParser {
root = filterParser.applyFilter(root, filterToParse, parsingLanguage, indexFacts);
}
}
- root = simplifyPhrases(root);
- if (defaultIndexName != null) {
- assignDefaultIndex(indexFacts.getCanonicName(defaultIndexName), root);
- }
- return root;
+ if (defaultIndex != null)
+ assignDefaultIndex(indexFacts.getCanonicName(defaultIndex), root);
+ return simplifyPhrases(root);
}
/**
@@ -221,16 +232,11 @@ public abstract class AbstractParser implements CustomParser {
return detectionText.toString();
}
- private boolean is(Token.Kind kind, Token tokenOrNull) {
- if (tokenOrNull == null) return false;
- return kind.equals(tokenOrNull.kind);
- }
-
- protected abstract Item parseItems(String defaultIndexName);
-
/**
- * Assigns the default index to query terms having no default index. The
- * parser _should_ have done this, for some reason it doesn't.
+ * Assigns the default index to query terms having no default index.
+ *
+ * This will apply the default index to terms without it added through the filter parameter,
+ * where setting defaultIndex into state causes incorrect parsing.
*
* @param defaultIndex the default index to assign
* @param item the item to check
@@ -243,7 +249,7 @@ public abstract class AbstractParser implements CustomParser {
if ("".equals(indexName.getIndexName()))
indexName.setIndexName(defaultIndex);
- }
+ }
else if (item instanceof CompositeItem) {
Iterator<Item> items = ((CompositeItem)item).getItemIterator();
while (items.hasNext())
@@ -251,6 +257,13 @@ public abstract class AbstractParser implements CustomParser {
}
}
+ private boolean is(Token.Kind kind, Token tokenOrNull) {
+ if (tokenOrNull == null) return false;
+ return kind.equals(tokenOrNull.kind);
+ }
+
+ protected abstract Item parseItems();
+
/**
* Unicode normalizes some piece of natural language text. The chosen form
* is compatibility decomposition, canonical composition (NFKC).
@@ -261,10 +274,11 @@ public abstract class AbstractParser implements CustomParser {
return environment.getLinguistics().getNormalizer().normalize(input);
}
- protected void setState(Language queryLanguage, IndexFacts.Session indexFacts) {
+ protected void setState(Language queryLanguage, IndexFacts.Session indexFacts, String defaultIndex) {
this.indexFacts = indexFacts;
- language = queryLanguage;
- submodes.reset();
+ this.defaultIndex = defaultIndex;
+ this.language = queryLanguage;
+ this.submodes.reset();
}
/**
@@ -293,8 +307,7 @@ public abstract class AbstractParser implements CustomParser {
return unwashed;
} else if (unwashed instanceof PhraseItem) {
return collapsePhrase((PhraseItem) unwashed);
- } else if (unwashed instanceof CompositeItem) {
- CompositeItem composite = (CompositeItem) unwashed;
+ } else if (unwashed instanceof CompositeItem composite) {
ListIterator<Item> i = composite.getItemIterator();
while (i.hasNext()) {
@@ -312,9 +325,8 @@ public abstract class AbstractParser implements CustomParser {
}
private static Item collapsePhrase(PhraseItem phrase) {
- if (phrase.getItemCount() == 1 && phrase.getItem(0) instanceof WordItem) {
+ if (phrase.getItemCount() == 1 && phrase.getItem(0) instanceof WordItem word) {
// TODO: Other stuff which needs propagation?
- WordItem word = (WordItem) phrase.getItem(0);
word.setWeight(phrase.getWeight());
return word;
} else {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
index 3358075d670..8f98763a838 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
@@ -1,14 +1,25 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;
-import com.yahoo.prelude.query.*;
+import com.yahoo.prelude.query.AndItem;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.EquivItem;
+import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.NearItem;
+import com.yahoo.prelude.query.NotItem;
+import com.yahoo.prelude.query.ONearItem;
+import com.yahoo.prelude.query.OrItem;
+import com.yahoo.prelude.query.RankItem;
+import com.yahoo.prelude.query.SegmentItem;
+import com.yahoo.prelude.query.WeakAndItem;
+import com.yahoo.prelude.query.WordItem;
import com.yahoo.search.query.parser.ParserEnvironment;
import static com.yahoo.prelude.query.parser.Token.Kind.LBRACE;
import static com.yahoo.prelude.query.parser.Token.Kind.NUMBER;
/**
- * Parser for queries of type advanced.
+ * Parser for queries of type 'advanced'.
*
* @author Steinar Knutsen
* @deprecated YQL should be used for formal queries
@@ -20,7 +31,8 @@ public class AdvancedParser extends StructuredParser {
super(environment);
}
- protected Item parseItems(String defaultIndexName) {
+ @Override
+ protected Item parseItems() {
return advancedItems(true);
}
@@ -53,46 +65,40 @@ public class AdvancedParser extends StructuredParser {
boolean expectingOperator = false;
do {
- item = null;
-
+ item = indexableItem().getFirst();
if (item == null) {
- item = indexableItem();
- if (item == null) {
- item = compositeItem();
- itemIsComposite = true;
- } else {
- itemIsComposite = false;
- }
- if (item != null) {
- Item newTop = null;
+ item = compositeItem();
+ itemIsComposite = true;
+ } else {
+ itemIsComposite = false;
+ }
+ if (item != null) {
+ Item newTop = null;
- if (expectingOperator) {
- newTop = handleAdvancedOperator(topLevelItem, item,
- topLevelIsClosed);
- }
- if (newTop != null) { // Operator found
- topLevelIsClosed = false;
- expectingOperator = false;
- topLevelItem = newTop;
- } else if (topLevelItem == null) {
- topLevelItem = item;
- if (itemIsComposite) {
- topLevelIsClosed = true;
- }
- expectingOperator = true;
- } else if (topLevelItem instanceof CompositeItem
- && !(topLevelItem instanceof SegmentItem)) {
- ((CompositeItem) topLevelItem).addItem(item);
- expectingOperator = true;
- } else {
- AndItem and = new AndItem();
-
- and.addItem(topLevelItem);
- and.addItem(item);
- topLevelItem = and;
- topLevelIsClosed = false;
- expectingOperator = true;
+ if (expectingOperator) {
+ newTop = handleAdvancedOperator(topLevelItem, item, topLevelIsClosed);
+ }
+ if (newTop != null) { // Operator found
+ topLevelIsClosed = false;
+ expectingOperator = false;
+ topLevelItem = newTop;
+ } else if (topLevelItem == null) {
+ topLevelItem = item;
+ if (itemIsComposite) {
+ topLevelIsClosed = true;
}
+ expectingOperator = true;
+ } else if (topLevelItem instanceof CompositeItem && !(topLevelItem instanceof SegmentItem)) {
+ ((CompositeItem) topLevelItem).addItem(item);
+ expectingOperator = true;
+ } else {
+ AndItem and = new AndItem();
+
+ and.addItem(topLevelItem);
+ and.addItem(item);
+ topLevelItem = and;
+ topLevelIsClosed = false;
+ expectingOperator = true;
}
}
@@ -178,7 +184,7 @@ public class AdvancedParser extends StructuredParser {
int distance = consumeNumericArgument();
if (distance==0)
distance=NearItem.defaultDistance;
- if (topLevelIsClosed || !(topLevelItem instanceof NearItem) || distance!=((NearItem)topLevelItem).getDistance()) {
+ if (topLevelIsClosed || !(topLevelItem instanceof NearItem) || distance != ((NearItem)topLevelItem).getDistance()) {
NearItem near = new NearItem(distance);
near.addItem(topLevelItem);
@@ -188,7 +194,7 @@ public class AdvancedParser extends StructuredParser {
} else if (isTheWord("onear", item)) {
int distance = consumeNumericArgument();
if (distance==0)
- distance=ONearItem.defaultDistance;
+ distance= ONearItem.defaultDistance;
if (topLevelIsClosed || !(topLevelItem instanceof ONearItem) || distance!=((ONearItem)topLevelItem).getDistance()) {
ONearItem oNear = new ONearItem(distance);
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
index 09caa72ca59..9a60eaef76b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
@@ -41,16 +41,16 @@ public class AllParser extends SimpleParser {
}
@Override
- protected Item parseItems(String defaultIndexName) {
+ protected Item parseItems() {
int position = tokens.getPosition();
try {
- return parseItemsBody(defaultIndexName);
+ return parseItemsBody();
} finally {
tokens.setPosition(position);
}
}
- protected Item parseItemsBody(String defaultIndexName) {
+ protected Item parseItemsBody() {
// Algorithm: Collect positive, negative, and and'ed items, then combine.
CompositeItem and = null;
NotItem not = null; // Store negatives here as we go
@@ -65,7 +65,7 @@ public class AllParser extends SimpleParser {
current = positiveItem();
if (current == null)
- current = indexableItem(defaultIndexName);
+ current = indexableItem().getFirst();
if (current == null)
current = compositeItem();
@@ -129,8 +129,9 @@ public class AllParser extends SimpleParser {
try {
if ( ! tokens.skip(MINUS)) return null;
if (tokens.currentIsNoIgnore(SPACE)) return null;
-
- item = indexableItem();
+ var itemAndExplicitIndex = indexableItem();
+ item = itemAndExplicitIndex.getFirst();
+ boolean explicitIndex = itemAndExplicitIndex.getSecond();
if (item == null) {
item = compositeItem();
@@ -155,11 +156,11 @@ public class AllParser extends SimpleParser {
// but interpret -(N) as a negative item matching a positive number
// but interpret --N as a negative item matching a negative number
if (item instanceof IntItem &&
- ((IntItem)item).getIndexName().isEmpty() &&
+ ! explicitIndex &&
! isComposited &&
- ! ((IntItem)item).getNumber().startsWith(("-")))
+ ! ((IntItem)item).getNumber().startsWith(("-"))) {
item = null;
-
+ }
return item;
} finally {
if (item == null) {
@@ -204,8 +205,7 @@ public class AllParser extends SimpleParser {
rank.addItem(topLevelItem);
}
return rank;
- } else if ((item instanceof RankItem) && (((RankItem)item).getItem(0) instanceof OrItem)) {
- RankItem itemAsRank = (RankItem) item;
+ } else if ((item instanceof RankItem itemAsRank) && (((RankItem)item).getItem(0) instanceof OrItem)) {
OrItem or = (OrItem) itemAsRank.getItem(0);
((RankItem) topLevelItem).addItem(0, or);
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
index f4ff769ad05..7fc4c823018 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
@@ -14,9 +14,7 @@ import com.yahoo.prelude.query.RankItem;
import com.yahoo.prelude.query.TermItem;
import com.yahoo.search.query.parser.ParserEnvironment;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.Set;
import static com.yahoo.prelude.query.parser.Token.Kind.*;
@@ -31,12 +29,13 @@ public class AnyParser extends SimpleParser {
super(environment);
}
- protected Item parseItems(String defaultIndexName) {
- return anyItems(true, defaultIndexName);
+ @Override
+ protected Item parseItems() {
+ return anyItems(true);
}
Item parseFilter(String filter, Language queryLanguage, IndexFacts.Session indexFacts) {
- setState(queryLanguage, indexFacts);
+ setState(queryLanguage, indexFacts, null);
tokenize(filter, null, indexFacts, queryLanguage);
Item filterRoot = anyItems(true);
@@ -55,7 +54,7 @@ public class AnyParser extends SimpleParser {
if ( ! tokens.skipMultiple(MINUS)) return null;
if (tokens.currentIsNoIgnore(SPACE)) return null;
- item = indexableItem();
+ item = indexableItem().getFirst();
if (item == null) {
item = compositeItem();
@@ -124,7 +123,7 @@ public class AnyParser extends SimpleParser {
}
Item applyFilter(Item root, String filter, Language queryLanguage, IndexFacts.Session indexFacts) {
- setState(queryLanguage, indexFacts);
+ setState(queryLanguage, indexFacts, null);
tokenize(filter, null, indexFacts, queryLanguage);
return filterItems(root);
}
@@ -148,16 +147,14 @@ public class AnyParser extends SimpleParser {
private Item filterItems(Item root) {
while (tokens.hasNext()) {
- Item item = null;
-
- item = positiveItem();
+ Item item = positiveItem();
root = addAndFilter(root, item);
if (item == null) {
item = negativeItem();
root = addNotFilter(root, item);
}
if (item == null) {
- item = indexableItem();
+ item = indexableItem().getFirst();
root = addRankFilter(root, item);
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
index e867def5903..e3b2278475b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
@@ -7,7 +7,6 @@ import com.yahoo.prelude.query.Item;
import com.yahoo.search.query.parser.Parser;
import java.util.Collections;
-import java.util.Objects;
import java.util.Set;
/**
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/ParseException.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/ParseException.java
index bef2ca9ffe9..82515c51c05 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/ParseException.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/ParseException.java
@@ -1,13 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;
-
/**
* Parser exceptions. JavaCC legacy, never thrown.
*
- * @author bratseth
+ * @author bratseth
*/
-@SuppressWarnings("serial")
public class ParseException extends RuntimeException {
public ParseException(String message) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
index 72eb56dd0fb..01b5b943829 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
@@ -16,7 +16,8 @@ public class PhraseParser extends AbstractParser {
super(environment);
}
- protected Item parseItems(String defaultIndex) {
+ @Override
+ protected Item parseItems() {
return forcedPhrase();
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/ProgrammaticParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/ProgrammaticParser.java
index 6a005bc0ec9..209753a596c 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/ProgrammaticParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/ProgrammaticParser.java
@@ -9,8 +9,6 @@ import com.yahoo.search.query.QueryTree;
import com.yahoo.search.query.parser.Parsable;
import com.yahoo.search.query.textserialize.TextSerialize;
-import java.util.Set;
-
/**
* @author Simon Thoresen Hult
*/
@@ -32,4 +30,5 @@ public final class ProgrammaticParser implements CustomParser {
if (queryToParse == null) return null;
return TextSerialize.parse(queryToParse);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
index fafbf55a522..b7355c43f81 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
@@ -1,7 +1,16 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;
-import com.yahoo.prelude.query.*;
+import com.yahoo.prelude.query.AndItem;
+import com.yahoo.prelude.query.BlockItem;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.NotItem;
+import com.yahoo.prelude.query.OrItem;
+import com.yahoo.prelude.query.PhraseItem;
+import com.yahoo.prelude.query.RankItem;
+import com.yahoo.prelude.query.TermItem;
+import com.yahoo.prelude.query.TrueItem;
import com.yahoo.search.query.parser.ParserEnvironment;
import java.util.Iterator;
@@ -33,12 +42,12 @@ abstract class SimpleParser extends StructuredParser {
* If there's a explicit composite and some other terms,
* a rank terms combines them
*/
- protected Item anyItems(boolean topLevel, String defaultIndexName) {
+ protected Item anyItems(boolean topLevel) {
int position = tokens.getPosition();
Item item = null;
try {
- item = anyItemsBody(topLevel, defaultIndexName);
+ item = anyItemsBody(topLevel);
return item;
} finally {
if (item == null) {
@@ -47,14 +56,10 @@ abstract class SimpleParser extends StructuredParser {
}
}
- protected Item anyItems(boolean topLevel) {
- return anyItems(topLevel, null);
- }
-
- private Item anyItemsBody(boolean topLevel, String defaultIndexName) {
+ private Item anyItemsBody(boolean topLevel) {
Item topLevelItem = null;
NotItem not = null;
- Item item = null;
+ Item item;
do {
item = positiveItem();
if (item != null) {
@@ -92,7 +97,7 @@ abstract class SimpleParser extends StructuredParser {
}
if (item == null) {
- item = indexableItem(defaultIndexName);
+ item = indexableItem().getFirst();
if (item != null) {
if (topLevelItem == null) {
topLevelItem = item;
@@ -177,9 +182,7 @@ abstract class SimpleParser extends StructuredParser {
return null;
}
- if (item == null) {
- item = indexableItem();
- }
+ item = indexableItem().getFirst();
if (item == null) {
item = compositeItem();
@@ -200,12 +203,10 @@ abstract class SimpleParser extends StructuredParser {
* (+ items) are not found, but negatives are.
*/
private Item getItemAsPositiveItem(Item item, NotItem not) {
- if (!(item instanceof RankItem)) {
+ if (!(item instanceof RankItem rank)) {
return item;
}
- RankItem rank = (RankItem) item;
-
// Remove the not from the rank item, the rank should generally
// be the first, but this is not always the case
int limit = rank.getItemCount();
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java
index c668cf66447..9d9aee54df0 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java
@@ -1,13 +1,30 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;
+import com.yahoo.collections.Pair;
import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.query.*;
+import com.yahoo.prelude.query.AndItem;
+import com.yahoo.prelude.query.AndSegmentItem;
+import com.yahoo.prelude.query.BlockItem;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.IntItem;
+import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.MarkerWordItem;
+import com.yahoo.prelude.query.PhraseItem;
+import com.yahoo.prelude.query.PhraseSegmentItem;
+import com.yahoo.prelude.query.PrefixItem;
+import com.yahoo.prelude.query.SegmentItem;
+import com.yahoo.prelude.query.Substring;
+import com.yahoo.prelude.query.SubstringItem;
+import com.yahoo.prelude.query.SuffixItem;
+import com.yahoo.prelude.query.TaggableItem;
+import com.yahoo.prelude.query.TermItem;
+import com.yahoo.prelude.query.UriItem;
+import com.yahoo.prelude.query.WordItem;
import com.yahoo.search.query.parser.ParserEnvironment;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import static com.yahoo.prelude.query.parser.Token.Kind.*;
@@ -52,19 +69,22 @@ abstract class StructuredParser extends AbstractParser {
submodes.setFromIndex(indexName, indexFacts);
}
- protected Item indexableItem() {
- return indexableItem(null);
- }
-
- protected Item indexableItem(String defaultIndexName) {
+ /**
+ * Returns an item and whether it had an explicit index ('indexname:' prefix).
+ *
+ * @return an item and whether it has an explicit index, or a Pair with the first element null if none
+ */
+ protected Pair<Item, Boolean> indexableItem() {
int position = tokens.getPosition();
Item item = null;
try {
+ boolean explicitIndex = false;
String indexName = indexPrefix();
- if (Objects.isNull(indexName)) {
- indexName = defaultIndexName;
- }
+ if (indexName != null)
+ explicitIndex = true;
+ else
+ indexName = this.defaultIndex;
setSubmodeFromIndex(indexName, indexFacts);
item = number();
@@ -86,7 +106,6 @@ abstract class StructuredParser extends AbstractParser {
if (item != null) {
weight = weightSuffix();
}
-
if (indexName != null && item != null) {
item.setIndexName(indexName);
}
@@ -95,7 +114,7 @@ abstract class StructuredParser extends AbstractParser {
item.setWeight(weight);
}
- return item;
+ return new Pair<>(item, explicitIndex);
} finally {
if (item == null) {
tokens.setPosition(position);
@@ -109,8 +128,7 @@ abstract class StructuredParser extends AbstractParser {
if (tokens.currentIsNoIgnore(SPACE)) {
return false;
}
- if (tokens.currentIsNoIgnore(NUMBER)
- || tokens.currentIsNoIgnore(WORD)) {
+ if (tokens.currentIsNoIgnore(NUMBER) || tokens.currentIsNoIgnore(WORD)) {
return true;
}
tokens.skipNoIgnore();
@@ -201,7 +219,6 @@ abstract class StructuredParser extends AbstractParser {
item = indexPrefix();
}
}
-
return item;
} finally {
if (item == null) {
@@ -286,7 +303,6 @@ abstract class StructuredParser extends AbstractParser {
tokens.skip(LSQUAREBRACKET);
if (item == null)
tokens.skipNoIgnore(SPACE);
-
// TODO: Better definition of start and end of numeric items
if (item == null && tokens.currentIsNoIgnore(MINUS) && (tokens.currentNoIgnore(1).kind == NUMBER)) {
tokens.skipNoIgnore();
@@ -592,7 +608,7 @@ abstract class StructuredParser extends AbstractParser {
if (firstWord instanceof IntItem) {
IntItem asInt = (IntItem) firstWord;
firstWord = new WordItem(asInt.stringValue(), asInt.getIndexName(),
- true, asInt.getOrigin());
+ true, asInt.getOrigin());
}
composite.addItem(firstWord);
composite.addItem(word);
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/Token.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/Token.java
index b668df9208c..3bf4d9dcf01 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/Token.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/Token.java
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;
-
import com.yahoo.prelude.query.Substring;
/**
@@ -11,7 +10,7 @@ import com.yahoo.prelude.query.Substring;
*/
public class Token {
- public static enum Kind {
+ public enum Kind {
EOF("<EOF>"),
NUMBER("<NUMBER>"),
WORD("<WORD>"),
@@ -77,31 +76,6 @@ public class Token {
/** Returns whether this is a <i>special token</i> */
public boolean isSpecial() { return special; }
- public String toString() { return image; }
-
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (object == null) {
- return false;
- }
- if (object.getClass() != this.getClass()) {
- return false;
- }
-
- Token other = (Token) object;
-
- if (this.kind != other.kind) {
- return false;
- }
- if (!(this.image.equals(other.image))) {
- return false;
- }
-
- return true;
- }
-
/**
* Returns the substring containing the image ins original form (including casing),
* as well as all the text surrounding the token
@@ -110,6 +84,22 @@ public class Token {
*/
public Substring getSubstring() { return substring; }
+ @Override
+ public String toString() { return image; }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null) return false;
+ if (object.getClass() != this.getClass()) return false;
+
+ Token other = (Token) object;
+ if (this.kind != other.kind) return false;
+ if (!(this.image.equals(other.image))) return false;
+ return true;
+ }
+
+ @Override
public int hashCode() {
return image.hashCode() ^ kind.hashCode();
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenPosition.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenPosition.java
index 9c60abab637..5ead962e430 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenPosition.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenPosition.java
@@ -37,9 +37,7 @@ final class TokenPosition {
* Returns null (no exception) if there are no more tokens.
*/
public Token current() {
- Token token = current(0);
-
- return token;
+ return current(0);
}
/**
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenizeParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenizeParser.java
index eefbe5fa0d0..dbbc321d057 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenizeParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/TokenizeParser.java
@@ -22,7 +22,7 @@ public final class TokenizeParser extends AbstractParser {
}
@Override
- protected Item parseItems(String defaultIndex) {
+ protected Item parseItems() {
WeakAndItem weakAnd = new WeakAndItem();
Token token;
while (null != (token = tokens.next())) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
index 93b8cf1ed83..c1d415b8e27 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
@@ -84,7 +84,6 @@ public final class Tokenizer {
* @param indexFacts information about the indexes we will search
* @return a read-only list of tokens. This list can only be used by this thread
*/
- @SuppressWarnings({"deprecation"})
// To avoid this we need to pass an IndexFacts.session down instead - easily done but not without breaking API's
public List<Token> tokenize(String string, String defaultIndexName, IndexFacts.Session indexFacts) {
this.source = string;
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/UnicodePropertyDump.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/UnicodePropertyDump.java
index b01b1295f45..8d2adfe0d78 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/UnicodePropertyDump.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/UnicodePropertyDump.java
@@ -26,13 +26,13 @@ class UnicodePropertyDump {
boolean debug = false;
if (arg.length > 0) {
- start = Integer.valueOf(arg[0]).intValue();
+ start = Integer.parseInt(arg[0]);
}
if (arg.length > 1) {
- end = Integer.valueOf(arg[1]).intValue();
+ end = Integer.parseInt(arg[1]);
}
if (arg.length > 2) {
- debug = Boolean.valueOf(arg[2]).booleanValue();
+ debug = Boolean.parseBoolean(arg[2]);
}
dumpProperties(start, end, debug, System.out);
}
@@ -109,4 +109,5 @@ class UnicodePropertyDump {
out.println();
}
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/WebParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/WebParser.java
index 40497d94a6d..aff28179050 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/WebParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/WebParser.java
@@ -28,7 +28,7 @@ public class WebParser extends AllParser {
}
@Override
- protected Item parseItemsBody(String defaultIndexName) {
+ protected Item parseItemsBody() {
// Algorithm: Collect positive, negative, and'ed and or'ed elements, then combine.
CompositeItem and = null;
OrItem or = null;
@@ -45,7 +45,7 @@ public class WebParser extends AllParser {
current = positiveItem();
if (current == null)
- current = indexableItem(defaultIndexName);
+ current = indexableItem().getFirst();
if (current != null) {
if (and != null && (current instanceof WordItem) && "OR".equals(((WordItem)current).getRawWord())) {
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
index e66c48ddb74..9da89c6bfd8 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
@@ -2,6 +2,7 @@
package com.yahoo.search.dispatch;
import java.io.Closeable;
+import java.time.Duration;
import java.util.function.BiConsumer;
/**
@@ -15,13 +16,13 @@ public abstract class CloseableInvoker implements Closeable {
protected abstract void release();
- private BiConsumer<Boolean, Long> teardown = null;
+ private BiConsumer<Boolean, Duration> teardown = null;
private boolean success = false;
private long startTime = 0;
- public void teardown(BiConsumer<Boolean, Long> teardown) {
+ public void teardown(BiConsumer<Boolean, Duration> teardown) {
this.teardown = teardown;
- this.startTime = System.currentTimeMillis();
+ this.startTime = System.nanoTime();
}
protected void setFinalStatus(boolean success) {
@@ -31,7 +32,7 @@ public abstract class CloseableInvoker implements Closeable {
@Override
public final void close() {
if (teardown != null) {
- teardown.accept(success, System.currentTimeMillis() - startTime);
+ teardown.accept(success, Duration.ofNanos(System.nanoTime() - startTime));
teardown = null;
}
release();
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
index 68a8e351b34..9ae97dabccd 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
@@ -25,11 +25,11 @@ import com.yahoo.search.query.profile.types.QueryProfileType;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.vespa.config.search.DispatchConfig;
+import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* A dispatcher communicates with search nodes to perform queries and fill hits.
@@ -96,6 +96,15 @@ public class Dispatcher extends AbstractComponent {
this(new ClusterMonitor<>(searchCluster, true), searchCluster, dispatchConfig, new RpcInvokerFactory(resourcePool, searchCluster), metric);
}
+ private static LoadBalancer.Policy toLoadBalancerPolicy(DispatchConfig.DistributionPolicy.Enum policy) {
+ return switch (policy) {
+ case ROUNDROBIN: yield LoadBalancer.Policy.ROUNDROBIN;
+ case BEST_OF_RANDOM_2: yield LoadBalancer.Policy.BEST_OF_RANDOM_2;
+ case ADAPTIVE,LATENCY_AMORTIZED_OVER_REQUESTS: yield LoadBalancer.Policy.LATENCY_AMORTIZED_OVER_REQUESTS;
+ case LATENCY_AMORTIZED_OVER_TIME: yield LoadBalancer.Policy.LATENCY_AMORTIZED_OVER_TIME;
+ };
+ }
+
/* Protected for simple mocking in tests. Beware that searchCluster is shutdown on in deconstruct() */
protected Dispatcher(ClusterMonitor<Node> clusterMonitor,
SearchCluster searchCluster,
@@ -107,8 +116,7 @@ public class Dispatcher extends AbstractComponent {
this.searchCluster = searchCluster;
this.clusterMonitor = clusterMonitor;
- this.loadBalancer = new LoadBalancer(searchCluster,
- dispatchConfig.distributionPolicy() == DispatchConfig.DistributionPolicy.ROUNDROBIN);
+ this.loadBalancer = new LoadBalancer(searchCluster, toLoadBalancerPolicy(dispatchConfig.distributionPolicy()));
this.invokerFactory = invokerFactory;
this.metric = metric;
this.metricContext = metric.createContext(null);
@@ -219,7 +227,7 @@ public class Dispatcher extends AbstractComponent {
invoker.get().teardown((success, time) -> loadBalancer.releaseGroup(group, success, time));
return invoker.get();
} else {
- loadBalancer.releaseGroup(group, false, 0);
+ loadBalancer.releaseGroup(group, false, Duration.ZERO);
if (rejected == null) {
rejected = new HashSet<>();
}
@@ -239,7 +247,7 @@ public class Dispatcher extends AbstractComponent {
*/
private Set<Integer> rejectGroupBlockingFeed(List<Group> groups) {
if (groups.size() == 1) return null;
- List<Group> groupsRejectingFeed = groups.stream().filter(Group::isBlockingWrites).collect(Collectors.toList());
+ List<Group> groupsRejectingFeed = groups.stream().filter(Group::isBlockingWrites).toList();
if (groupsRejectingFeed.size() != 1) return null;
Set<Integer> rejected = new HashSet<>();
rejected.add(groupsRejectingFeed.get(0).id());
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java b/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
index 4c0bcb38d15..6d8bb1dce3d 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
@@ -4,6 +4,7 @@ package com.yahoo.search.dispatch;
import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.SearchCluster;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -31,16 +32,22 @@ public class LoadBalancer {
private final List<GroupStatus> scoreboard;
private final GroupScheduler scheduler;
- public LoadBalancer(SearchCluster searchCluster, boolean roundRobin) {
+ public enum Policy { ROUNDROBIN, LATENCY_AMORTIZED_OVER_REQUESTS, LATENCY_AMORTIZED_OVER_TIME, BEST_OF_RANDOM_2}
+
+ public LoadBalancer(SearchCluster searchCluster, Policy policy) {
this.scoreboard = new ArrayList<>(searchCluster.groups().size());
for (Group group : searchCluster.orderedGroups()) {
scoreboard.add(new GroupStatus(group));
}
- if (roundRobin || scoreboard.size() == 1) {
- this.scheduler = new RoundRobinScheduler(scoreboard);
- } else {
- this.scheduler = new AdaptiveScheduler(new Random(), scoreboard);
- }
+ if (scoreboard.size() == 1)
+ policy = Policy.ROUNDROBIN;
+
+ this.scheduler = switch (policy) {
+ case ROUNDROBIN: yield new RoundRobinScheduler(scoreboard);
+ case BEST_OF_RANDOM_2: yield new BestOfRandom2(new Random(), scoreboard);
+ case LATENCY_AMORTIZED_OVER_REQUESTS: yield new AdaptiveScheduler(new Random(), scoreboard);
+ case LATENCY_AMORTIZED_OVER_TIME: yield new AdaptiveScheduler(new Random(), scoreboard); // TODO Intentionally the same for now
+ };
}
/**
@@ -71,13 +78,13 @@ public class LoadBalancer {
*
* @param group previously allocated group
* @param success was the query successful
- * @param searchTimeMs query execution time in milliseconds, used for adaptive load balancing
+ * @param searchTime query execution time, used for adaptive load balancing
*/
- public void releaseGroup(Group group, boolean success, double searchTimeMs) {
+ public void releaseGroup(Group group, boolean success, Duration searchTime) {
synchronized (this) {
for (GroupStatus sched : scoreboard) {
if (sched.group.id() == group.id()) {
- sched.release(success, searchTimeMs / 1000.0);
+ sched.release(success, searchTime);
break;
}
}
@@ -99,22 +106,23 @@ public class LoadBalancer {
allocations++;
}
- void release(boolean success, double searchTime) {
+ void release(boolean success, Duration searchTime) {
+ double searchSeconds = searchTime.toMillis()/1000.0;
allocations--;
if (allocations < 0) {
log.warning("Double free of query target group detected");
allocations = 0;
}
if (success) {
- searchTime = Math.max(searchTime, MIN_QUERY_TIME);
+ searchSeconds = Math.max(searchSeconds, MIN_QUERY_TIME);
double decayRate = Math.min(queries + MIN_LATENCY_DECAY_RATE, DEFAULT_LATENCY_DECAY_RATE);
- averageSearchTime = (searchTime + (decayRate - 1) * averageSearchTime) / decayRate;
+ averageSearchTime = (searchSeconds + (decayRate - 1) * averageSearchTime) / decayRate;
queries++;
}
}
- double averageSearchTime() {
- return averageSearchTime;
+ Duration averageSearchTime() {
+ return Duration.ofNanos((long)(averageSearchTime*1000000000));
}
double averageSearchTimeInverse() {
@@ -125,9 +133,9 @@ public class LoadBalancer {
return group.id();
}
- void setQueryStatistics(long queries, double averageSearchTime) {
+ void setQueryStatistics(long queries, Duration averageSearchTime) {
this.queries = queries;
- this.averageSearchTime = averageSearchTime;
+ this.averageSearchTime = averageSearchTime.toMillis()/1000.0;
}
}
@@ -239,4 +247,47 @@ public class LoadBalancer {
}
}
+ static class BestOfRandom2 implements GroupScheduler {
+ private final Random random;
+ private final List<GroupStatus> scoreboard;
+ public BestOfRandom2(Random random, List<GroupStatus> scoreboard) {
+ this.random = random;
+ this.scoreboard = scoreboard;
+ }
+ @Override
+ public Optional<GroupStatus> takeNextGroup(Set<Integer> rejectedGroups) {
+ GroupStatus gs = selectBestOf2(rejectedGroups, true);
+ return (gs != null)
+ ? Optional.of(gs)
+ : Optional.ofNullable(selectBestOf2(rejectedGroups, false));
+ }
+
+ private GroupStatus selectBestOf2(Set<Integer> rejectedGroups, boolean requireCoverage) {
+ List<Integer> candidates = new ArrayList<>(scoreboard.size());
+ for (int i=0; i < scoreboard.size(); i++) {
+ GroupStatus gs = scoreboard.get(i);
+ if (rejectedGroups == null || !rejectedGroups.contains(gs.group.id())) {
+ if (!requireCoverage || gs.group.hasSufficientCoverage()) {
+ candidates.add(i);
+ }
+ }
+ }
+ GroupStatus candA = selectRandom(candidates);
+ GroupStatus candB = selectRandom(candidates);
+ if (candA == null) return candB;
+ if (candB == null) return candA;
+ if (candB.allocations < candA.allocations) return candB;
+ return candA;
+ }
+ private GroupStatus selectRandom(List<Integer> candidates) {
+ if ( ! candidates.isEmpty()) {
+ int index = random.nextInt(candidates.size());
+ Integer groupIndex = candidates.remove(index);
+ return scoreboard.get(groupIndex);
+ }
+ return null;
+ }
+
+ }
+
}
diff --git a/container-search/src/test/java/com/yahoo/prelude/test/QueryTestCase.java b/container-search/src/test/java/com/yahoo/prelude/test/QueryTestCase.java
index 95ecf3c2dba..06b6eca5f84 100644
--- a/container-search/src/test/java/com/yahoo/prelude/test/QueryTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/test/QueryTestCase.java
@@ -195,7 +195,7 @@ public class QueryTestCase {
assertTrue(p.hashCode() != q.hashCode());
}
- /** Test using the defauultindex feature */
+ /** Test using the defaultindex feature */
@Test
void testDefaultIndex() {
Query q = newQuery("?query=hi hello keyword:kanoo " +
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java b/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
index e9ed1c48302..c9981b3598b 100644
--- a/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/LoadBalancerTest.java
@@ -2,6 +2,7 @@
package com.yahoo.search.dispatch;
import com.yahoo.search.dispatch.LoadBalancer.AdaptiveScheduler;
+import com.yahoo.search.dispatch.LoadBalancer.BestOfRandom2;
import com.yahoo.search.dispatch.LoadBalancer.GroupStatus;
import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.Node;
@@ -9,6 +10,7 @@ import com.yahoo.search.dispatch.searchcluster.SearchCluster;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -29,7 +31,7 @@ public class LoadBalancerTest {
void requireThatLoadBalancerServesSingleNodeSetups() {
Node n1 = new Node(0, "test-node1", 0);
SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1), null, null);
- LoadBalancer lb = new LoadBalancer(cluster, true);
+ LoadBalancer lb = new LoadBalancer(cluster, LoadBalancer.Policy.ROUNDROBIN);
Optional<Group> grp = lb.takeGroup(null);
Group group = grp.orElseGet(() -> {
@@ -43,7 +45,7 @@ public class LoadBalancerTest {
Node n1 = new Node(0, "test-node1", 0);
Node n2 = new Node(1, "test-node2", 1);
SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2), null, null);
- LoadBalancer lb = new LoadBalancer(cluster, true);
+ LoadBalancer lb = new LoadBalancer(cluster, LoadBalancer.Policy.ROUNDROBIN);
Optional<Group> grp = lb.takeGroup(null);
Group group = grp.orElseGet(() -> {
@@ -59,7 +61,7 @@ public class LoadBalancerTest {
Node n3 = new Node(0, "test-node3", 1);
Node n4 = new Node(1, "test-node4", 1);
SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2, n3, n4), null, null);
- LoadBalancer lb = new LoadBalancer(cluster, true);
+ LoadBalancer lb = new LoadBalancer(cluster, LoadBalancer.Policy.ROUNDROBIN);
Optional<Group> grp = lb.takeGroup(null);
assertTrue(grp.isPresent());
@@ -70,14 +72,14 @@ public class LoadBalancerTest {
Node n1 = new Node(0, "test-node1", 0);
Node n2 = new Node(1, "test-node2", 1);
SearchCluster cluster = new SearchCluster("a", createDispatchConfig(n1, n2), null, null);
- LoadBalancer lb = new LoadBalancer(cluster, true);
+ LoadBalancer lb = new LoadBalancer(cluster, LoadBalancer.Policy.ROUNDROBIN);
// get first group
Optional<Group> grp = lb.takeGroup(null);
Group group = grp.get();
int id1 = group.id();
// release allocation
- lb.releaseGroup(group, true, 1.0);
+ lb.releaseGroup(group, true, Duration.ofMillis(1));
// get second group
grp = lb.takeGroup(null);
@@ -90,28 +92,28 @@ public class LoadBalancerTest {
final double delta = 0.00001;
GroupStatus gs = newGroupStatus(1);
- gs.setQueryStatistics(0, 1.0);
- updateSearchTime(gs, 1.0);
- assertEquals(1.0, gs.averageSearchTime(), delta);
- updateSearchTime(gs, 2.0);
- assertEquals(1.02326, gs.averageSearchTime(), delta);
- updateSearchTime(gs, 2.0);
- assertEquals(1.04545, gs.averageSearchTime(), delta);
- updateSearchTime(gs, 0.1);
- updateSearchTime(gs, 0.1);
- updateSearchTime(gs, 0.1);
- updateSearchTime(gs, 0.1);
- assertEquals(0.966667, gs.averageSearchTime(), delta);
+ gs.setQueryStatistics(0, Duration.ofSeconds(1));
+ updateSearchTime(gs, Duration.ofSeconds(1));
+ assertEquals(Duration.ofSeconds(1), gs.averageSearchTime());
+ updateSearchTime(gs, Duration.ofSeconds(2));
+ assertEquals(Duration.ofNanos(1023255813), gs.averageSearchTime());
+ updateSearchTime(gs, Duration.ofSeconds(2));
+ assertEquals(Duration.ofNanos(1045454545), gs.averageSearchTime());
+ updateSearchTime(gs, Duration.ofMillis(100));
+ updateSearchTime(gs, Duration.ofMillis(100));
+ updateSearchTime(gs, Duration.ofMillis(100));
+ updateSearchTime(gs, Duration.ofMillis(100));
+ assertEquals(Duration.ofNanos(966666666), gs.averageSearchTime());
for (int i = 0; i < 10000; i++) {
- updateSearchTime(gs, 1.0);
+ updateSearchTime(gs, Duration.ofSeconds(1));
}
- assertEquals(1.0, gs.averageSearchTime(), delta);
- updateSearchTime(gs, 0.1);
- assertEquals(0.9991, gs.averageSearchTime(), delta);
+ assertEquals(Duration.ofNanos(999999812), gs.averageSearchTime());
+ updateSearchTime(gs, Duration.ofMillis(100));
+ assertEquals(Duration.ofNanos(999099812), gs.averageSearchTime());
for (int i = 0; i < 10000; i++) {
- updateSearchTime(gs, 0.0);
+ updateSearchTime(gs, Duration.ZERO);
}
- assertEquals(0.001045, gs.averageSearchTime(), delta);
+ assertEquals(Duration.ofNanos(1045087), gs.averageSearchTime());
}
@Test
@@ -138,7 +140,7 @@ public class LoadBalancerTest {
List<GroupStatus> scoreboard = new ArrayList<>();
for (int i = 0; i < 5; i++) {
GroupStatus gs = newGroupStatus(i);
- gs.setQueryStatistics(1, 0.1 * (i + 1));
+ gs.setQueryStatistics(1, Duration.ofMillis((long)(0.1 * (i + 1)*1000.0)));
scoreboard.add(gs);
}
Random seq = sequence(0.0, 0.4379, 0.4380, 0.6569, 0.6570, 0.8029, 0.8030, 0.9124, 0.9125);
@@ -155,7 +157,40 @@ public class LoadBalancerTest {
assertEquals(4, sched.takeNextGroup(null).get().groupId());
}
- private static void updateSearchTime(GroupStatus gs, double time) {
+ private static GroupStatus allocate(GroupStatus gs) {
+ gs.allocate();
+ return gs;
+ }
+ @Test
+ void requireBestOfRandom2Scheduler() {
+ List<GroupStatus> scoreboard = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ scoreboard.add(newGroupStatus(i));
+ }
+ Random seq = sequence(
+ 0.1, 0.125,
+ 0.1, 0.125,
+ 0.1, 0.125,
+ 0.1, 0.125,
+ 0.1, 0.375,
+ 0.9, 0.125,
+ 0.9, 0.125,
+ 0.9, 0.125
+ );
+ BestOfRandom2 sched = new BestOfRandom2(seq, scoreboard);
+
+ assertEquals(0, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(1, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(0, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(1, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(2, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(4, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(4, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(4, allocate(sched.takeNextGroup(null).get()).groupId());
+ assertEquals(0, allocate(sched.takeNextGroup(null).get()).groupId());
+ }
+
+ private static void updateSearchTime(GroupStatus gs, Duration time) {
gs.allocate();
gs.release(true, time);
}
@@ -183,6 +218,10 @@ public class LoadBalancerTest {
}
return retv;
}
+ @Override
+ public int nextInt(int bound) {
+ return (int)(nextDouble() * bound);
+ }
};
}
diff --git a/container-search/src/test/java/com/yahoo/search/test/QueryWithFilterTestCase.java b/container-search/src/test/java/com/yahoo/search/test/QueryWithFilterTestCase.java
new file mode 100644
index 00000000000..3920a95bd98
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/test/QueryWithFilterTestCase.java
@@ -0,0 +1,80 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.test;
+
+import com.yahoo.language.Language;
+import com.yahoo.language.Linguistics;
+import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.prelude.Index;
+import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.IndexModel;
+import com.yahoo.prelude.SearchDefinition;
+import com.yahoo.search.Query;
+import com.yahoo.search.searchchain.Execution;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author bratseth
+ */
+public class QueryWithFilterTestCase {
+
+ /** Tests that default-index is not applied to ALL filters */
+ @Test
+ void testRankFilter() {
+ Query q = newQueryFromEncoded("?query=trump" +
+ "&model.type=all" +
+ "&model.defaultIndex=text" +
+ "&filter=filterattribute%3Afrontpage_US_en-US");
+ assertEquals("RANK text:trump |filterattribute:frontpage_US_en-US",
+ q.getModel().getQueryTree().toString());
+ }
+
+ /** Tests that default-index is not applied to NOT filters */
+ @Test
+ void testAndFilter() {
+ Query q = newQueryFromEncoded("?query=trump" +
+ "&model.type=all" +
+ "&model.defaultIndex=text" +
+ "&filter=%2B%28filterattribute%3Afrontpage_US_en-US%29");
+ assertEquals("AND text:trump |filterattribute:frontpage_US_en-US",
+ q.getModel().getQueryTree().toString());
+ }
+
+ /** Tests that default-index is not applied to NOT filters */
+ @Test
+ void testAndFilterWithoutExplicitIndex() {
+ Query q = newQueryFromEncoded("?query=trump" +
+ "&model.type=all" +
+ "&model.defaultIndex=text" +
+ "&filter=%2B%28filterTerm%29");
+ assertEquals("AND text:trump |text:filterTerm",
+ q.getModel().getQueryTree().toString());
+ }
+
+ private Query newQueryFromEncoded(String queryString) {
+ return newQueryFromEncoded(queryString, null, new SimpleLinguistics());
+ }
+
+ private Query newQueryFromEncoded(String encodedQueryString, Language language, Linguistics linguistics) {
+ Query query = new Query(encodedQueryString);
+ query.getModel().setExecution(new Execution(Execution.Context.createContextStub(createIndexFacts(),
+ linguistics)));
+ query.getModel().setLanguage(language);
+ return query;
+ }
+
+ private IndexFacts createIndexFacts() {
+ SearchDefinition sd = new SearchDefinition("test");
+ sd.addIndex(new Index("test"));
+ sd.addIndex(attribute("filterattribute"));
+ return new IndexFacts(new IndexModel(sd));
+ }
+
+ private Index attribute(String name) {
+ Index index = new Index(name);
+ index.setExact(true, null);
+ return index;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
index 8b4c00e9b9d..e0524091c39 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.zone;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzDomain;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeType;
@@ -34,6 +35,9 @@ public interface ZoneRegistry {
/** Returns whether the system of this registry contains the given zone */
boolean hasZone(ZoneId zoneId);
+ /** Returns whether cloudAccount in this system supports given zone */
+ boolean hasZone(ZoneId zoneId, CloudAccount cloudAccount);
+
/** Returns a list containing the id of all zones in this registry */
ZoneFilter zones();
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SimplePrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SimplePrincipal.java
index 780171d0ccb..363d0726a1f 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SimplePrincipal.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SimplePrincipal.java
@@ -18,6 +18,10 @@ public class SimplePrincipal implements Principal {
this.name = name;
}
+ public static SimplePrincipal of(Principal principal) {
+ return new SimplePrincipal(principal.getName());
+ }
+
@Override
public String getName() {
return name;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 44f9c0ea3b8..ae0467fcc86 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -5,6 +5,7 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
+import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import java.security.Principal;
import java.security.PublicKey;
@@ -20,16 +21,16 @@ import java.util.Optional;
*/
public class CloudTenant extends Tenant {
- private final Optional<Principal> creator;
- private final BiMap<PublicKey, Principal> developerKeys;
+ private final Optional<SimplePrincipal> creator;
+ private final BiMap<PublicKey, SimplePrincipal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
private final ArchiveAccess archiveAccess;
private final Optional<Instant> invalidateUserSessionsBefore;
/** Public for the serialization layer — do not use! */
- public CloudTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
- BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
+ public CloudTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<SimplePrincipal> creator,
+ BiMap<PublicKey, SimplePrincipal> developerKeys, TenantInfo info,
List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess, Optional<Instant> invalidateUserSessionsBefore) {
super(name, createdAt, lastLoginInfo, Optional.empty());
this.creator = creator;
@@ -45,12 +46,12 @@ public class CloudTenant extends Tenant {
return new CloudTenant(requireName(tenantName),
createdAt,
LastLoginInfo.EMPTY,
- Optional.ofNullable(creator),
+ Optional.ofNullable(creator).map(SimplePrincipal::of),
ImmutableBiMap.of(), TenantInfo.empty(), List.of(), new ArchiveAccess(), Optional.empty());
}
/** The user that created the tenant */
- public Optional<Principal> creator() {
+ public Optional<SimplePrincipal> creator() {
return creator;
}
@@ -60,7 +61,7 @@ public class CloudTenant extends Tenant {
}
/** Returns the set of developer keys and their corresponding developers for this tenant. */
- public BiMap<PublicKey, Principal> developerKeys() { return developerKeys; }
+ public BiMap<PublicKey, SimplePrincipal> developerKeys() { return developerKeys; }
/** List of configured secret stores */
public List<TenantSecretStore> tenantSecretStores() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
index ac7c6319c1b..da40f63d543 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
@@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
+import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
@@ -124,15 +125,15 @@ public abstract class LockedTenant {
/** A locked CloudTenant. */
public static class Cloud extends LockedTenant {
- private final Optional<Principal> creator;
- private final BiMap<PublicKey, Principal> developerKeys;
+ private final Optional<SimplePrincipal> creator;
+ private final BiMap<PublicKey, SimplePrincipal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
private final ArchiveAccess archiveAccess;
private final Optional<Instant> invalidateUserSessionsBefore;
- private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
- BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
+ private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<SimplePrincipal> creator,
+ BiMap<PublicKey, SimplePrincipal> developerKeys, TenantInfo info,
List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess, Optional<Instant> invalidateUserSessionsBefore) {
super(name, createdAt, lastLoginInfo);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
@@ -153,15 +154,18 @@ public abstract class LockedTenant {
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
- BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys);
+ BiMap<PublicKey, SimplePrincipal> keys = HashBiMap.create(developerKeys);
+ SimplePrincipal simplePrincipal = new SimplePrincipal(principal.getName());
if (keys.containsKey(key))
throw new IllegalArgumentException("Key " + KeyUtils.toPem(key) + " is already owned by " + keys.get(key));
- keys.put(key, principal);
+ if (keys.inverse().containsKey(simplePrincipal))
+ throw new IllegalArgumentException(principal + " is already associated with key " + KeyUtils.toPem(keys.inverse().get(simplePrincipal)));
+ keys.put(key, simplePrincipal);
return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore);
}
public Cloud withoutDeveloperKey(PublicKey key) {
- BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys);
+ BiMap<PublicKey, SimplePrincipal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
index 6178bfbb89e..153ce87ff6b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -36,14 +36,19 @@ public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceL
}
/**
- * Returns the subset of instances where all production deployments are compatible with the given version.
+ * Returns the subset of instances where all production deployments are compatible with the given version,
+ * and at least one known build is compatible with the given version.
*
* @param platform the version which applications returned are compatible with
*/
public InstanceList compatibleWithPlatform(Version platform, Function<ApplicationId, VersionCompatibility> compatibility) {
- return matching(id -> instance(id).productionDeployments().values().stream()
- .flatMap(deployment -> application(id).revisions().get(deployment.revision()).compileVersion().stream())
- .noneMatch(version -> compatibility.apply(id).refuse(platform, version)));
+ return matching(id -> instance(id).productionDeployments().values().stream()
+ .flatMap(deployment -> application(id).revisions().get(deployment.revision()).compileVersion().stream())
+ .noneMatch(version -> compatibility.apply(id).refuse(platform, version))
+ && application(id).revisions().production().stream()
+ .anyMatch(revision -> revision.compileVersion()
+ .map(compiled -> compatibility.apply(id).accept(platform, compiled))
+ .orElse(true)));
}
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
index 281ac50e63a..b3df417bd80 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
@@ -54,8 +55,8 @@ public class ApplicationPackageValidator {
* @throws IllegalArgumentException if any validations fail
*/
public void validate(Application application, ApplicationPackage applicationPackage, Instant instant) {
- validateSteps(applicationPackage.deploymentSpec());
validateCloudAccounts(application, applicationPackage.deploymentSpec());
+ validateSteps(applicationPackage.deploymentSpec());
validateEndpointRegions(applicationPackage.deploymentSpec());
validateEndpointChange(application, applicationPackage, instant);
validateCompactedEndpoint(applicationPackage);
@@ -88,12 +89,22 @@ public class ApplicationPackageValidator {
private void validateSteps(DeploymentSpec deploymentSpec) {
for (var spec : deploymentSpec.instances()) {
for (var zone : spec.zones()) {
- if (zone.environment().isManuallyDeployed())
- throw new IllegalArgumentException("region must be one with automated deployments, but got: " + zone.environment());
+ Environment environment = zone.environment();
+ if (environment.isManuallyDeployed())
+ throw new IllegalArgumentException("region must be one with automated deployments, but got: " + environment);
- if ( zone.environment() == Environment.prod
- && ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(), zone.region().orElseThrow())))
- throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!");
+ if (environment == Environment.prod) {
+ RegionName region = zone.region().orElseThrow();
+ if (!controller.zoneRegistry().hasZone(ZoneId.from(environment, region))) {
+ throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!");
+ }
+ Optional<CloudAccount> cloudAccount = spec.cloudAccount(environment, region);
+ if (cloudAccount.isPresent() && !controller.zoneRegistry().hasZone(ZoneId.from(environment, region), cloudAccount.get())) {
+ throw new IllegalArgumentException("Zone " + zone + " in deployment spec is not configured for " +
+ "use in cloud account '" + cloudAccount.get().value() +
+ "', in this system");
+ }
+ }
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index d83f552ab25..1f2a016f630 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -323,14 +323,6 @@ public class DeploymentTrigger {
instance -> instance.withJobPause(jobType, OptionalLong.empty()))));
}
- /** Triggers a change of this application, unless it already has a change. */
- public void triggerChange(ApplicationId instanceId, Change change) {
- applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
- if ( ! application.get().require(instanceId.instance()).change().hasTargets())
- forceChange(instanceId, change);
- });
- }
-
/** Overrides the given instance's platform and application changes with any contained in the given change. */
public void forceChange(ApplicationId instanceId, Change change) {
applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 7114402f824..11a784ce899 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -233,41 +233,45 @@ public class JobController {
Run run = run(id);
return run.stepStatus(copyVespaLogs).map(succeeded::equals).orElse(false)
? controller.serviceRegistry().runDataStore().getLogs(id, tester)
- : getVespaLogsFromLogserver(run, fromMillis, tester);
+ : getVespaLogsFromLogserver(run, fromMillis, tester).orElse(InputStream.nullInputStream());
}
public static Optional<Instant> deploymentCompletedAt(Run run, boolean tester) {
return (tester ? run.stepInfo(installTester)
: run.stepInfo(installInitialReal).or(() -> run.stepInfo(installReal)))
- .flatMap(StepInfo::startTime);
+ .flatMap(StepInfo::startTime).map(start -> start.minusSeconds(10));
}
public void storeVespaLogs(RunId id) {
Run run = run(id);
if ( ! id.type().isProduction()) {
- try (InputStream logs = getVespaLogsFromLogserver(run, 0, false)) {
- controller.serviceRegistry().runDataStore().putLogs(id, false, logs);
- }
- catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ getVespaLogsFromLogserver(run, 0, false).ifPresent(logs -> {
+ try (logs) {
+ controller.serviceRegistry().runDataStore().putLogs(id, false, logs);
+ }
+ catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
}
if (id.type().isTest()) {
- try (InputStream logs = getVespaLogsFromLogserver(run, 0, true)) {
- controller.serviceRegistry().runDataStore().putLogs(id, true, logs);
- }
- catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ getVespaLogsFromLogserver(run, 0, true).ifPresent(logs -> {
+ try (logs) {
+ controller.serviceRegistry().runDataStore().putLogs(id, true, logs);
+ }
+ catch(IOException e){
+ throw new UncheckedIOException(e);
+ }
+ });
}
}
- private InputStream getVespaLogsFromLogserver(Run run, long fromMillis, boolean tester) {
- long deploymentCompletedAtMillis = deploymentCompletedAt(run, tester).orElse(Instant.EPOCH).toEpochMilli();
- return controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
- run.id().type().zone()),
- Map.of("from", Long.toString(Math.max(fromMillis, deploymentCompletedAtMillis)),
- "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli())));
+ private Optional<InputStream> getVespaLogsFromLogserver(Run run, long fromMillis, boolean tester) {
+ return deploymentCompletedAt(run, tester).map(at ->
+ controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
+ run.id().type().zone()),
+ Map.of("from", Long.toString(Math.max(fromMillis, at.toEpochMilli())),
+ "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli()))));
}
/** Fetches any new test log entries, and records the id of the last of these, for continuation. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index 1932dc65657..269623de3f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -119,12 +119,18 @@ public class Upgrader extends ControllerMaintainer {
}
int numberToUpgrade = policy == UpgradePolicy.canary ? instances.size() : numberOfApplicationsToUpgrade();
- for (ApplicationId id : instances.matching(targets.keySet()::contains).first(numberToUpgrade)) {
- log.log(Level.INFO, "Triggering upgrade to " + targets.get(id) + " for " + id);
- if (failingRevision.contains(id))
+ for (ApplicationId id : instances.matching(targets.keySet()::contains)) {
+ if (failingRevision.contains(id)) {
+ log.log(Level.INFO, "Cancelling failing revision for " + id);
controller().applications().deploymentTrigger().cancelChange(id, ChangesToCancel.APPLICATION);
-
- controller().applications().deploymentTrigger().triggerChange(id, Change.of(targets.get(id)));
+ }
+
+ if (controller().applications().requireInstance(id).change().isEmpty()) {
+ log.log(Level.INFO, "Triggering upgrade to " + targets.get(id) + " for " + id);
+ controller().applications().deploymentTrigger().forceChange(id, Change.of(targets.get(id)));
+ --numberToUpgrade;
+ }
+ if (numberToUpgrade <= 0) break;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
index 8a363405c41..b6e18881dab 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
@@ -82,7 +82,7 @@ public class Notification {
feedBlock,
/** Application cluster is reindexing document(s) */
- reindex;
+ reindex
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
index ecb9db8195f..32f5e6714f2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
@@ -69,8 +69,8 @@ public class BufferedLogStore {
List<LogEntry> stepEntries = log.computeIfAbsent(step, __ -> new ArrayList<>());
for (LogEntry entry : entries) {
if (sizeLowerBound > chunkSize) {
- buffer.writeLastLogEntryId(id, type, lastEntryId);
buffer.writeLog(id, type, lastChunkId, logSerializer.toJson(log));
+ buffer.writeLastLogEntryId(id, type, lastEntryId);
lastChunkId = lastEntryId + 1;
if (++numberOfChunks > maxLogSize / chunkSize && ! forceLog) {
log = Map.of(step, List.of(new LogEntry(++lastEntryId,
@@ -87,8 +87,8 @@ public class BufferedLogStore {
stepEntries.add(new LogEntry(++lastEntryId, entry.at(), entry.type(), entry.message()));
sizeLowerBound += entry.message().length();
}
- buffer.writeLastLogEntryId(id, type, lastEntryId);
buffer.writeLog(id, type, lastChunkId, logSerializer.toJson(log));
+ buffer.writeLastLogEntryId(id, type, lastEntryId);
}
/** Reads all log entries after the given threshold, from the buffered log, i.e., for an active run. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index e91fbe8b1b7..fc7cafe4c89 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -137,7 +137,7 @@ public class TenantSerializer {
root.setLong(deletedAtField, tenant.deletedAt().toEpochMilli());
}
- private void developerKeysToSlime(BiMap<PublicKey, Principal> keys, Cursor array) {
+ private void developerKeysToSlime(BiMap<PublicKey, ? extends Principal> keys, Cursor array) {
keys.forEach((key, user) -> {
Cursor object = array.addObject();
object.setString("key", KeyUtils.toPem(key));
@@ -184,8 +184,8 @@ public class TenantSerializer {
TenantName name = TenantName.from(tenantObject.field(nameField).asString());
Instant createdAt = SlimeUtils.instant(tenantObject.field(createdAtField));
LastLoginInfo lastLoginInfo = lastLoginInfoFromSlime(tenantObject.field(lastLoginInfoField));
- Optional<Principal> creator = SlimeUtils.optionalString(tenantObject.field(creatorField)).map(SimplePrincipal::new);
- BiMap<PublicKey, Principal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField));
+ Optional<SimplePrincipal> creator = SlimeUtils.optionalString(tenantObject.field(creatorField)).map(SimplePrincipal::new);
+ BiMap<PublicKey, SimplePrincipal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField));
TenantInfo info = tenantInfoFromSlime(tenantObject.field(tenantInfoField));
List<TenantSecretStore> tenantSecretStores = secretStoresFromSlime(tenantObject.field(secretStoresField));
ArchiveAccess archiveAccess = archiveAccessFromSlime(tenantObject);
@@ -200,8 +200,8 @@ public class TenantSerializer {
return new DeletedTenant(name, createdAt, deletedAt);
}
- private BiMap<PublicKey, Principal> developerKeysFromSlime(Inspector array) {
- ImmutableBiMap.Builder<PublicKey, Principal> keys = ImmutableBiMap.builder();
+ private BiMap<PublicKey, SimplePrincipal> developerKeysFromSlime(Inspector array) {
+ ImmutableBiMap.Builder<PublicKey, SimplePrincipal> keys = ImmutableBiMap.builder();
array.traverse((ArrayTraverser) (__, keyObject) ->
keys.put(KeyUtils.fromPemEncodedPublicKey(keyObject.field("key").asString()),
new SimplePrincipal(keyObject.field("user").asString())));
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 a4bb9034a85..7e186cbe34c 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
@@ -59,7 +59,6 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbi
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.integration.aws.TenantRoles;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
@@ -165,7 +164,6 @@ import static java.util.Map.Entry.comparingByKey;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toUnmodifiableList;
/**
* This implements the application/v4 API which is used to deploy and manage applications
@@ -202,15 +200,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
public HttpResponse auditAndHandle(HttpRequest request) {
try {
Path path = new Path(request.getUri());
- switch (request.getMethod()) {
- case GET: return handleGET(path, request);
- case PUT: return handlePUT(path, request);
- case POST: return handlePOST(path, request);
- case PATCH: return handlePATCH(path, request);
- case DELETE: return handleDELETE(path, request);
- case OPTIONS: return handleOPTIONS();
- default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
- }
+ return switch (request.getMethod()) {
+ case GET: yield handleGET(path, request);
+ case PUT: yield handlePUT(path, request);
+ case POST: yield handlePOST(path, request);
+ case PATCH: yield handlePATCH(path, request);
+ case DELETE: yield handleDELETE(path, request);
+ case OPTIONS: yield handleOPTIONS();
+ default: yield ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
+ };
}
catch (RestApiException.Forbidden e) {
return ErrorResponse.forbidden(Exceptions.toMessageString(e));
@@ -225,16 +223,12 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return ErrorResponse.badRequest(Exceptions.toMessageString(e));
}
catch (ConfigServerException e) {
- switch (e.code()) {
- case NOT_FOUND:
- return ErrorResponse.notFoundError(Exceptions.toMessageString(e));
- case ACTIVATION_CONFLICT:
- return new ErrorResponse(CONFLICT, e.code().name(), Exceptions.toMessageString(e));
- case INTERNAL_SERVER_ERROR:
- return ErrorResponse.internalServerError(Exceptions.toMessageString(e));
- default:
- return new ErrorResponse(BAD_REQUEST, e.code().name(), Exceptions.toMessageString(e));
- }
+ return switch (e.code()) {
+ case NOT_FOUND: yield ErrorResponse.notFoundError(Exceptions.toMessageString(e));
+ case ACTIVATION_CONFLICT: yield new ErrorResponse(CONFLICT, e.code().name(), Exceptions.toMessageString(e));
+ case INTERNAL_SERVER_ERROR: yield ErrorResponse.internalServerError(Exceptions.toMessageString(e));
+ default: yield new ErrorResponse(BAD_REQUEST, e.code().name(), Exceptions.toMessageString(e));
+ };
}
catch (RuntimeException e) {
log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e);
@@ -714,19 +708,18 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static TenantContacts.Audience fromAudience(String value) {
- switch (value) {
- case "tenant": return TenantContacts.Audience.TENANT;
- case "notifications": return TenantContacts.Audience.NOTIFICATIONS;
+ return switch (value) {
+ case "tenant": yield TenantContacts.Audience.TENANT;
+ case "notifications": yield TenantContacts.Audience.NOTIFICATIONS;
default: throw new IllegalArgumentException("Unknown contact audience '" + value + "'.");
- }
+ };
}
private static String toAudience(TenantContacts.Audience audience) {
- switch (audience) {
- case TENANT: return "tenant";
- case NOTIFICATIONS: return "notifications";
- default: throw new IllegalArgumentException("Unexpected contact audience '" + audience + "'.");
- }
+ return switch (audience) {
+ case TENANT: yield "tenant";
+ case NOTIFICATIONS: yield "notifications";
+ };
}
@@ -820,18 +813,18 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private TenantContacts updateTenantInfoContacts(Inspector insp, TenantContacts oldContacts) {
if (!insp.valid()) return oldContacts;
- List<TenantContacts.Contact> contacts = SlimeUtils.entriesStream(insp).map(inspector -> {
+ List<TenantContacts.EmailContact> contacts = SlimeUtils.entriesStream(insp).map(inspector -> {
String email = inspector.field("email").asString().trim();
List<TenantContacts.Audience> audiences = SlimeUtils.entriesStream(inspector.field("audiences"))
.map(audience -> fromAudience(audience.asString()))
- .collect(Collectors.toUnmodifiableList());
+ .toList();
if (!email.contains("@")) {
throw new IllegalArgumentException("'email' needs to be an email address");
}
return new TenantContacts.EmailContact(audiences, email);
- }).collect(toUnmodifiableList());
+ }).toList();
return new TenantContacts(contacts);
}
@@ -884,24 +877,21 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String notificationTypeAsString(Notification.Type type) {
- switch (type) {
- case submission:
- case applicationPackage: return "applicationPackage";
- case testPackage: return "testPackage";
- case deployment: return "deployment";
- case feedBlock: return "feedBlock";
- case reindex: return "reindex";
- default: throw new IllegalArgumentException("No serialization defined for notification type " + type);
- }
+ return switch (type) {
+ case submission, applicationPackage: yield "applicationPackage";
+ case testPackage: yield "testPackage";
+ case deployment: yield "deployment";
+ case feedBlock: yield "feedBlock";
+ case reindex: yield "reindex";
+ };
}
private static String notificationLevelAsString(Notification.Level level) {
- switch (level) {
- case info: return "info";
- case warning: return "warning";
- case error: return "error";
- default: throw new IllegalArgumentException("No serialization defined for notification level " + level);
- }
+ return switch (level) {
+ case info: yield "info";
+ case warning: yield "warning";
+ case error: yield "error";
+ };
}
private HttpResponse applications(String tenantName, Optional<String> applicationName, HttpRequest request) {
@@ -1081,7 +1071,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(root);
}
- private void toSlime(Cursor keysArray, Map<PublicKey, Principal> keys) {
+ private void toSlime(Cursor keysArray, Map<PublicKey, ? extends Principal> keys) {
keys.forEach((key, principal) -> {
Cursor keyObject = keysArray.addObject();
keyObject.setString("key", KeyUtils.toPem(key));
@@ -1334,17 +1324,19 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String valueOf(Node.State state) {
- switch (state) {
- case failed: return "failed";
- case parked: return "parked";
- case dirty: return "dirty";
- case ready: return "ready";
- case active: return "active";
- case inactive: return "inactive";
- case reserved: return "reserved";
- case provisioned: return "provisioned";
+ return switch (state) {
+ case failed: yield "failed";
+ case parked: yield "parked";
+ case dirty: yield "dirty";
+ case ready: yield "ready";
+ case active: yield "active";
+ case inactive: yield "inactive";
+ case reserved: yield "reserved";
+ case provisioned: yield "provisioned";
+ case breakfixed: yield "breakfixed";
+ case deprovisioned: yield "deprovisioned";
default: throw new IllegalArgumentException("Unexpected node state '" + state + "'.");
- }
+ };
}
static String valueOf(Node.ServiceState state) {
@@ -1360,31 +1352,29 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String valueOf(Node.ClusterType type) {
- switch (type) {
- case admin: return "admin";
- case content: return "content";
- case container: return "container";
- case combined: return "combined";
- default: throw new IllegalArgumentException("Unexpected node cluster type '" + type + "'.");
- }
+ return switch (type) {
+ case admin: yield "admin";
+ case content: yield "content";
+ case container: yield "container";
+ case combined: yield "combined";
+ case unknown: throw new IllegalArgumentException("Unexpected node cluster type '" + type + "'.");
+ };
}
private static String valueOf(NodeResources.DiskSpeed diskSpeed) {
- switch (diskSpeed) {
- case fast : return "fast";
- case slow : return "slow";
- case any : return "any";
- default: throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed.name() + "'");
- }
+ return switch (diskSpeed) {
+ case fast : yield "fast";
+ case slow : yield "slow";
+ case any : yield "any";
+ };
}
private static String valueOf(NodeResources.StorageType storageType) {
- switch (storageType) {
- case remote : return "remote";
- case local : return "local";
- case any : return "any";
- default: throw new IllegalArgumentException("Unknown storage type '" + storageType.name() + "'");
- }
+ return switch (storageType) {
+ case remote : yield "remote";
+ case local : yield "local";
+ case any : yield "any";
+ };
}
private HttpResponse logs(String tenantName, String applicationName, String instanceName, String environment, String region, Map<String, String> queryParameters) {
@@ -2146,11 +2136,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
List<String> clusterNames = Optional.ofNullable(request.getProperty("clusterId")).stream()
.flatMap(clusters -> Stream.of(clusters.split(",")))
.filter(cluster -> ! cluster.isBlank())
- .collect(toUnmodifiableList());
+ .toList();
List<String> documentTypes = Optional.ofNullable(request.getProperty("documentType")).stream()
.flatMap(types -> Stream.of(types.split(",")))
.filter(type -> ! type.isBlank())
- .collect(toUnmodifiableList());
+ .toList();
Double speed = request.hasProperty("speed") ? Double.parseDouble(request.getProperty("speed")) : null;
boolean indexedOnly = request.getBooleanProperty("indexedOnly");
@@ -2209,13 +2199,12 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String toString(ApplicationReindexing.State state) {
- switch (state) {
- case PENDING: return "pending";
- case RUNNING: return "running";
- case FAILED: return "failed";
- case SUCCESSFUL: return "successful";
- default: return null;
- }
+ return switch (state) {
+ case PENDING: yield "pending";
+ case RUNNING: yield "running";
+ case FAILED: yield "failed";
+ case SUCCESSFUL: yield "successful";
+ };
}
/** Enables reindexing of an application in a zone. */
@@ -2893,12 +2882,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String tenantType(Tenant tenant) {
- switch (tenant.type()) {
- case athenz: return "ATHENS";
- case cloud: return "CLOUD";
- case deleted: return "DELETED";
- default: throw new IllegalArgumentException("Unknown tenant type: " + tenant.getClass().getSimpleName());
- }
+ return switch (tenant.type()) {
+ case athenz: yield "ATHENS";
+ case cloud: yield "CLOUD";
+ case deleted: yield "DELETED";
+ };
}
private static ApplicationId appIdFromPath(Path path) {
@@ -3003,29 +2991,27 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private static String rotationStateString(RotationState state) {
- switch (state) {
- case in: return "IN";
- case out: return "OUT";
- }
- return "UNKNOWN";
+ return switch (state) {
+ case in: yield "IN";
+ case out: yield "OUT";
+ case unknown: yield "UNKNOWN";
+ };
}
private static String endpointScopeString(Endpoint.Scope scope) {
- switch (scope) {
- case weighted: return "weighted";
- case application: return "application";
- case global: return "global";
- case zone: return "zone";
- }
- throw new IllegalArgumentException("Unknown endpoint scope " + scope);
+ return switch (scope) {
+ case weighted: yield "weighted";
+ case application: yield "application";
+ case global: yield "global";
+ case zone: yield "zone";
+ };
}
private static String routingMethodString(RoutingMethod method) {
- switch (method) {
- case exclusive: return "exclusive";
- case sharedLayer4: return "sharedLayer4";
- }
- throw new IllegalArgumentException("Unknown routing method " + method);
+ return switch (method) {
+ case exclusive: yield "exclusive";
+ case sharedLayer4: yield "sharedLayer4";
+ };
}
private static <T> T getAttribute(HttpRequest request, String attributeName, Class<T> cls) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index b5177cd1d3e..6c193e9b539 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -9,6 +9,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
@@ -1194,13 +1195,25 @@ public class ControllerTest {
.cloudAccount(cloudAccount)
.region(zone.region())
.build();
+ // Deployment fails because cloud account is not declared for this tenant
try {
context.submit(applicationPackage).deploy();
- fail("Expected exception"); // Account invalid for tenant
- } catch (IllegalArgumentException ignored) {
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Cloud account '012345678912' is not valid for tenant 'tenant'", e.getMessage());
}
+ // Deployment fails because requested region is not configured in cloud account
tester.controllerTester().flagSource().withListFlag(PermanentFlags.CLOUD_ACCOUNTS.id(), List.of(cloudAccount), String.class);
+ try {
+ context.submit(applicationPackage).deploy();
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Zone prod.us-west-1 in deployment spec is not configured for use in cloud account '012345678912', in this system", e.getMessage());
+ }
+
+ // Deployment succeeds
+ tester.controllerTester().zoneRegistry().setCloudAccountZones(new CloudAccount(cloudAccount), zone);
context.submit(applicationPackage).deploy();
assertEquals(cloudAccount, tester.controllerTester().configServer().cloudAccount(context.deploymentIdIn(zone)).get().value());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index 62fafa12993..6dfeaf55ea4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -1221,7 +1221,7 @@ public class DeploymentTriggerTest {
assertEquals(Change.empty(), app.instance().change());
// Application is pinned to previous version, and downgrades to that. Tests are re-run.
- tester.deploymentTrigger().triggerChange(app.instanceId(), Change.of(version0).withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version0).withPin());
app.runJob(stagingTest).runJob(productionUsEast3);
tester.clock().advance(Duration.ofMinutes(1));
app.failDeployment(testUsEast3);
@@ -2106,12 +2106,12 @@ public class DeploymentTriggerTest {
Version version2 = new Version("7.8.9");
Version version3 = new Version("8.9.10");
tester.controllerTester().upgradeSystem(version2);
- tester.deploymentTrigger().triggerChange(appToAvoidVersionGC.instanceId(), Change.of(version2));
+ tester.deploymentTrigger().forceChange(appToAvoidVersionGC.instanceId(), Change.of(version2));
appToAvoidVersionGC.deployPlatform(version2);
// app upgrades first zone to version3, and then the other two to version2.
tester.controllerTester().upgradeSystem(version3);
- tester.deploymentTrigger().triggerChange(app.instanceId(), Change.of(version3));
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version3));
app.runJob(systemTest).runJob(stagingTest);
tester.triggerJobs();
tester.upgrader().overrideConfidence(version3, VespaVersion.Confidence.broken);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
index 4131bfd8b9f..680e69998b6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
@@ -5,6 +5,7 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzDomain;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeType;
@@ -43,6 +44,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
private final Map<Environment, RegionName> defaultRegionForEnvironment = new HashMap<>();
private final Map<CloudName, UpgradePolicy> osUpgradePolicies = new HashMap<>();
private final Map<ZoneApi, List<RoutingMethod>> zoneRoutingMethods = new HashMap<>();
+ private final Map<CloudAccount, Set<ZoneId>> cloudAccountZones = new HashMap<>();
private final Set<ZoneApi> reprovisionToUpgradeOs = new HashSet<>();
private final SystemName system; // Don't even think about making it non-final! ƪ(`▿▿▿▿´ƪ)
@@ -146,6 +148,11 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
return this;
}
+ public ZoneRegistryMock setCloudAccountZones(CloudAccount cloudAccount, ZoneId... zones) {
+ this.cloudAccountZones.put(cloudAccount, Set.of(zones));
+ return this;
+ }
+
@Override
public SystemName system() {
return system;
@@ -254,6 +261,11 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
}
@Override
+ public boolean hasZone(ZoneId zoneId, CloudAccount cloudAccount) {
+ return hasZone(zoneId) && cloudAccountZones.getOrDefault(cloudAccount, Set.of()).contains(zoneId);
+ }
+
+ @Override
public URI getConfigServerVipUri(ZoneId zoneId) {
return URI.create(Text.format("https://cfg.%s.test.vip:4443/", zoneId.value()));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
index 9b2a1607e76..15855770c0b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
@@ -32,7 +32,7 @@ public class OutstandingChangeDeployerTest {
var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
Version version = new Version(6, 2);
- tester.deploymentTrigger().triggerChange(app.instanceId(), Change.of(version));
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version));
assertEquals(Change.of(version), app.instance().change());
assertFalse(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
index 324c9706df9..a927439de1c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
@@ -80,7 +80,7 @@ public class ControllerContainerCloudTest extends ControllerContainerTest {
}
public RequestBuilder data(byte[] data) { this.data = data; return this; }
public RequestBuilder data(String data) { this.data = data.getBytes(StandardCharsets.UTF_8); return this; }
- public RequestBuilder principal(String principal) { this.principal = new SimplePrincipal(principal); return this; }
+ public RequestBuilder principal(String principal) { this.principal = new SimplePrincipal(principal){ }; return this; }
public RequestBuilder user(User user) { this.user = user; return this; }
public RequestBuilder roles(Set<Role> roles) { this.roles = roles; return this; }
public RequestBuilder roles(Role... roles) { return roles(Set.of(roles)); }
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index ec9be1f04c3..fcbecfa2e68 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -121,7 +121,7 @@ public class SignatureFilterTest {
Instant.EPOCH,
LastLoginInfo.EMPTY,
Optional.empty(),
- ImmutableBiMap.of(publicKey, () -> "user"),
+ ImmutableBiMap.of(publicKey, new SimplePrincipal("user")),
TenantInfo.empty(),
List.of(),
new ArchiveAccess(),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index 1344b106bbe..f34dd3fe629 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -136,6 +136,14 @@ public class UserApiTest extends ControllerContainerCloudTest {
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Key " + quotedPemPublicKey + " is already owned by joe@dev\"}",
400);
+ // POST a different developer key for an existing user is forbidden
+ tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST)
+ .principal("joe@dev")
+ .roles(Set.of(Role.developer(id.tenant())))
+ .data("{\"key\":\"" + otherPemPublicKey + "\"}"),
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"joe@dev is already associated with key " + quotedPemPublicKey + "\"}",
+ 400);
+
// POST in a different pem developer key
tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST)
.principal("developer@tenant")
diff --git a/default_build_settings.cmake b/default_build_settings.cmake
index 009cd6d615e..e973cfcf7f7 100644
--- a/default_build_settings.cmake
+++ b/default_build_settings.cmake
@@ -64,7 +64,7 @@ endfunction()
function(setup_vespa_default_build_settings_darwin)
message("-- Setting up default build settings for darwin")
- set(DEFAULT_VESPA_LLVM_VERSION "13" PARENT_SCOPE)
+ set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
set(DEFAULT_CMAKE_PREFIX_PATH "${VESPA_DEPS}" "/usr/local/opt/bison" "/usr/local/opt/flex" "/usr/local/opt/openssl@1.1" "/usr/local/opt/openblas" "/usr/local/opt/icu4c" PARENT_SCOPE)
set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib" "/usr/local/opt/bison/lib" "/usr/local/opt/flex/lib" "/usr/local/opt/icu4c/lib" "/usr/local/opt/openssl@1.1/lib" "/usr/local/opt/openblas/lib")
list(APPEND DEFAULT_EXTRA_LINK_DIRECTORY "/usr/local/lib")
@@ -74,12 +74,6 @@ function(setup_vespa_default_build_settings_darwin)
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${DEFAULT_EXTRA_INCLUDE_DIRECTORY}" PARENT_SCOPE)
endfunction()
-function(setup_vespa_default_build_settings_fedora_34)
- message("-- Setting up default build settings for fedora 34")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "12" PARENT_SCOPE)
-endfunction()
-
function(setup_vespa_default_build_settings_fedora_35)
message("-- Setting up default build settings for fedora 35")
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
@@ -98,6 +92,12 @@ function(setup_vespa_default_build_settings_fedora_37)
set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
endfunction()
+function(setup_vespa_default_build_settings_fedora_38)
+ message("-- Setting up default build settings for fedora 38")
+ set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
+ set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
+endfunction()
+
function(setup_vespa_default_build_settings_amzn_2)
message("-- Setting up default build settings for amzn 2")
set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib64" "/usr/lib64/llvm7.0/lib" PARENT_SCOPE)
@@ -236,14 +236,14 @@ function(vespa_use_default_build_settings)
setup_vespa_default_build_settings_almalinux_9_0()
elseif(VESPA_OS_DISTRO STREQUAL "darwin")
setup_vespa_default_build_settings_darwin()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 34")
- setup_vespa_default_build_settings_fedora_34()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 35")
setup_vespa_default_build_settings_fedora_35()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 36")
setup_vespa_default_build_settings_fedora_36()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 37")
setup_vespa_default_build_settings_fedora_37()
+ elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 38")
+ setup_vespa_default_build_settings_fedora_38()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "amzn 2")
setup_vespa_default_build_settings_amzn_2()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "amzn 2022")
@@ -351,8 +351,8 @@ function(vespa_use_default_cxx_compiler)
unset(DEFAULT_CMAKE_CXX_COMPILER)
if(NOT DEFINED VESPA_COMPILER_VARIANT OR VESPA_COMPILER_VARIANT STREQUAL "gcc")
if(APPLE)
- set(DEFAULT_CMAKE_C_COMPILER "/usr/local/bin/gcc-11")
- set(DEFAULT_CMAKE_CXX_COMPILER "/usr/local/bin/g++-11")
+ set(DEFAULT_CMAKE_C_COMPILER "/usr/local/bin/gcc-12")
+ set(DEFAULT_CMAKE_CXX_COMPILER "/usr/local/bin/g++-12")
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "amzn 2")
set(DEFAULT_CMAKE_C_COMPILER "/usr/bin/gcc10-gcc")
set(DEFAULT_CMAKE_CXX_COMPILER "/usr/bin/gcc10-g++")
@@ -403,12 +403,12 @@ function(vespa_use_default_java_home)
if (DEFINED JAVA_HOME)
return()
endif()
- set(DEFAULT_JAVA_HOME "/usr/lib/jvm/java-11-openjdk")
+ set(DEFAULT_JAVA_HOME "/usr/lib/jvm/java-17-openjdk")
if(APPLE)
execute_process(COMMAND "/usr/libexec/java_home" OUTPUT_VARIABLE DEFAULT_JAVA_HOME)
string(STRIP "${DEFAULT_JAVA_HOME}" DEFAULT_JAVA_HOME)
- elseif(VESPA_OS_DISTRO STREQUAL "ubuntu")
- set(DEFAULT_JAVA_HOME "/usr/lib/jvm/java-11-openjdk-amd64" PARENT_SCOPE)
+ elseif(VESPA_OS_DISTRO STREQUAL "ubuntu" OR VESPA_OS_DISTRO STREQUAL "debian")
+ set(DEFAULT_JAVA_HOME "/usr/lib/jvm/java-17-openjdk-amd64" PARENT_SCOPE)
endif()
if(COMMAND vespa_use_specific_java_home)
vespa_use_specific_java_home()
diff --git a/dist/vespa.spec b/dist/vespa.spec
index b94a77491b8..4b4c82c436b 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -111,7 +111,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.11.0
+BuildRequires: vespa-onnxruntime-devel = 1.12.1
BuildRequires: vespa-openssl-devel >= 1.1.1o-1
%define _use_vespa_openssl 1
BuildRequires: vespa-protobuf-devel = 3.19.1
@@ -139,7 +139,7 @@ BuildRequires: vespa-openssl-devel >= 1.1.1o-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.11.0
+BuildRequires: vespa-onnxruntime-devel = 1.12.1
BuildRequires: vespa-protobuf-devel = 3.19.1
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
%endif
@@ -149,7 +149,7 @@ BuildRequires: maven
BuildRequires: maven-openjdk17
BuildRequires: openssl-devel
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.11.0
+BuildRequires: vespa-onnxruntime-devel = 1.12.1
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
BuildRequires: protobuf-devel
%if 0%{?_centos_stream}
@@ -169,15 +169,11 @@ BuildRequires: maven-openjdk17
%endif
BuildRequires: openssl-devel
BuildRequires: vespa-lz4-devel >= 1.9.2-2
-BuildRequires: vespa-onnxruntime-devel = 1.11.0
+BuildRequires: vespa-onnxruntime-devel = 1.12.1
BuildRequires: vespa-libzstd-devel >= 1.4.5-2
-%if 0%{?fc34}
-BuildRequires: protobuf-devel
%if 0%{?amzn2022}
+BuildRequires: protobuf-devel
BuildRequires: llvm-devel >= 13.0.0
-%else
-BuildRequires: llvm-devel >= 12.0.0
-%endif
BuildRequires: boost-devel >= 1.75
BuildRequires: gtest-devel
BuildRequires: gmock-devel
@@ -198,8 +194,15 @@ BuildRequires: gmock-devel
%endif
%if 0%{?fc37}
BuildRequires: protobuf-devel
-BuildRequires: llvm-devel >= 14.0.0
-BuildRequires: boost-devel >= 1.76
+BuildRequires: llvm-devel >= 14.0.5
+BuildRequires: boost-devel >= 1.78
+BuildRequires: gtest-devel
+BuildRequires: gmock-devel
+%endif
+%if 0%{?fc38}
+BuildRequires: protobuf-devel
+BuildRequires: llvm-devel >= 14.0.5
+BuildRequires: boost-devel >= 1.78
BuildRequires: gtest-devel
BuildRequires: gmock-devel
%endif
@@ -328,12 +331,8 @@ Requires: gtest
%endif
%if 0%{?fedora}
Requires: gtest
-%if 0%{?fc34}
%if 0%{?amzn2022}
%define _vespa_llvm_version 13
-%else
-%define _vespa_llvm_version 12
-%endif
%endif
%if 0%{?fc35}
%define _vespa_llvm_version 13
@@ -344,6 +343,9 @@ Requires: gtest
%if 0%{?fc37}
%define _vespa_llvm_version 14
%endif
+%if 0%{?fc38}
+%define _vespa_llvm_version 14
+%endif
%define _extra_link_directory %{_vespa_deps_prefix}/lib64
%define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas
%endif
@@ -459,12 +461,8 @@ Requires: protobuf
%endif
%if 0%{?fedora}
Requires: protobuf
-%if 0%{?fc34}
%if 0%{?amzn2022}
Requires: llvm-libs >= 13.0.0
-%else
-Requires: llvm-libs >= 12.0.0
-%endif
%endif
%if 0%{?fc35}
Requires: llvm-libs >= 13.0.0
@@ -473,10 +471,13 @@ Requires: llvm-libs >= 13.0.0
Requires: llvm-libs >= 14.0.0
%endif
%if 0%{?fc37}
-Requires: llvm-libs >= 14.0.0
+Requires: llvm-libs >= 14.0.5
+%endif
+%if 0%{?fc38}
+Requires: llvm-libs >= 14.0.5
%endif
%endif
-Requires: vespa-onnxruntime = 1.11.0
+Requires: vespa-onnxruntime = 1.12.1
%description libs
@@ -571,7 +572,7 @@ nearest neighbor search used for low-level benchmarking.
%endif
%else
%setup -q
-%if ( 0%{?el8} || 0%{?fc34} ) && %{_vespa_llvm_version} < 13
+%if 0%{?el8} && %{_vespa_llvm_version} < 13
if grep -qs 'result_pair<R>(' /usr/include/llvm/ADT/STLExtras.h
then
patch /usr/include/llvm/ADT/STLExtras.h < dist/STLExtras.h.diff
diff --git a/document/src/vespa/document/bucket/bucketid.cpp b/document/src/vespa/document/bucket/bucketid.cpp
index f01bfb67674..9ca4db1e51c 100644
--- a/document/src/vespa/document/bucket/bucketid.cpp
+++ b/document/src/vespa/document/bucket/bucketid.cpp
@@ -8,6 +8,7 @@
#include <vespa/vespalib/stllike/hash_set.hpp>
#include <vespa/vespalib/util/stringfmt.h>
#include <limits>
+#include <xxh3.h>
using vespalib::nbostream;
using vespalib::asciistream;
@@ -76,6 +77,12 @@ void BucketId::initialize() noexcept {
fillStripMasks(BucketId::_stripMasks, BucketId::maxNumBits);
}
+uint64_t
+BucketId::hash::operator () (const BucketId& bucketId) const noexcept {
+ const uint64_t raw_id = bucketId.getId();
+ return XXH3_64bits(&raw_id, sizeof(uint64_t));
+}
+
vespalib::string
BucketId::toString() const
{
diff --git a/document/src/vespa/document/bucket/bucketid.h b/document/src/vespa/document/bucket/bucketid.h
index d54f86e4ae7..370948c1acc 100644
--- a/document/src/vespa/document/bucket/bucketid.h
+++ b/document/src/vespa/document/bucket/bucketid.h
@@ -37,9 +37,7 @@ class BucketId
{
public:
struct hash {
- size_t operator () (const BucketId& g) const noexcept {
- return g.getId();
- }
+ uint64_t operator () (const BucketId& g) const noexcept;
};
/**
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
index 9f40013f627..c1877373ce2 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -20,7 +20,7 @@ public class FetchVector {
* Note: If this enum is changed, you must also change {@link DimensionHelper}.
*/
public enum Dimension {
- /** A legal value for TenantName, e.g. vespa-team */
+ /** Value from TenantName::value, e.g. vespa-team */
TENANT_ID,
/** Value from ApplicationId::serializedForm of the form tenant:applicationName:instance. */
@@ -29,7 +29,7 @@ public class FetchVector {
/** Node type from com.yahoo.config.provision.NodeType::name, e.g. tenant, host, confighost, controller, etc. */
NODE_TYPE,
- /** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::value, e.g. content, container, admin */
+ /** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::name, e.g. content, container, admin */
CLUSTER_TYPE,
/** Cluster ID from com.yahoo.config.provision.ClusterSpec.Id::value, e.g. cluster-controllers, logserver. */
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 e349165b855..21f95c7ac6a 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -11,6 +11,7 @@ import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
+import java.util.function.Predicate;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CONSOLE_USER_EMAIL;
@@ -61,6 +62,14 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
+ public static final UnboundStringFlag QUERY_DISPATCH_POLICY = defineStringFlag(
+ "query-dispatch-policy", "adaptive",
+ List.of("baldersheim"), "2022-08-20", "2023-01-01",
+ "Select query dispatch policy, valid values are adaptive, round-robin, best-of-random-2," +
+ " latency-amortized-over-requests, latency-amortized-over-time",
+ "Takes effect at redeployment (requires restart)",
+ ZONE_ID, APPLICATION_ID);
+
public static final UnboundStringFlag FEED_SEQUENCER_TYPE = defineStringFlag(
"feed-sequencer-type", "THROUGHPUT",
List.of("baldersheim"), "2020-12-02", "2023-01-01",
@@ -332,7 +341,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-09-01",
+ List.of("hmusum"), "2022-01-06", "2022-10-01",
"Enable Data Highway in AWS",
"Takes effect on restart of Docker container",
ZONE_ID, APPLICATION_ID);
@@ -410,8 +419,8 @@ public class Flags {
public static final UnboundStringFlag APPLICATION_FILES_WITH_UNKNOWN_EXTENSION = defineStringFlag(
"fail-deployment-for-files-with-unknown-extension", "FAIL",
- List.of("hmusum"), "2022-04-27", "2022-09-01",
- "Whether to log, fail or do nothing for deployments when app has a file with unknown extension (valid values: LOG, FAIL)",
+ List.of("hmusum"), "2022-04-27", "2022-10-01",
+ "Whether to log or fail for deployments when app has a file with unknown extension (valid values: LOG, FAIL)",
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
@@ -431,14 +440,14 @@ public class Flags {
public static final UnboundListFlag<String> FILE_DISTRIBUTION_ACCEPTED_COMPRESSION_TYPES = defineListFlag(
"file-distribution-accepted-compression-types", List.of("gzip", "lz4"), String.class,
- List.of("hmusum"), "2022-07-05", "2022-09-05",
+ List.of("hmusum"), "2022-07-05", "2022-10-01",
"´List of accepted compression types used when asking for a file reference. Valid values: gzip, lz4",
"Takes effect on restart of service",
APPLICATION_ID);
public static final UnboundListFlag<String> FILE_DISTRIBUTION_COMPRESSION_TYPES_TO_SERVE = defineListFlag(
"file-distribution-compression-types-to-use", List.of("lz4", "gzip"), String.class,
- List.of("hmusum"), "2022-07-05", "2022-09-05",
+ List.of("hmusum"), "2022-07-05", "2022-10-01",
"List of compression types to use (in preferred order), matched with accepted compression types when serving file references. Valid values: gzip, lz4",
"Takes effect on restart of service",
APPLICATION_ID);
@@ -490,7 +499,19 @@ public class Flags {
public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(UnboundStringFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
+ return defineStringFlag(flagId, defaultValue, owners,
+ createdAt, expiresAt, description,
+ modificationEffect, value -> true,
+ dimensions);
+ }
+
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
+ String createdAt, String expiresAt, String description,
+ String modificationEffect, Predicate<String> validator,
+ FetchVector.Dimension... dimensions) {
+ return define((i, d, v) -> new UnboundStringFlag(i, d, v, validator),
+ flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
}
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
@@ -532,7 +553,7 @@ public class Flags {
@FunctionalInterface
private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> {
- U create(FlagId id, T defaultVale, FetchVector defaultFetchVector);
+ U create(FlagId id, T defaultValue, FetchVector defaultFetchVector);
}
/**
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
index b5a292a554d..e193fca59f5 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
@@ -9,6 +9,8 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CLUSTER_ID;
@@ -17,6 +19,7 @@ import static com.yahoo.vespa.flags.FetchVector.Dimension.CONSOLE_USER_EMAIL;
import static com.yahoo.vespa.flags.FetchVector.Dimension.HOSTNAME;
import static com.yahoo.vespa.flags.FetchVector.Dimension.NODE_TYPE;
import static com.yahoo.vespa.flags.FetchVector.Dimension.TENANT_ID;
+import static com.yahoo.vespa.flags.FetchVector.Dimension.VESPA_VERSION;
import static com.yahoo.vespa.flags.FetchVector.Dimension.ZONE_ID;
/**
@@ -113,6 +116,19 @@ public class PermanentFlags {
"Takes effect on next deployment from controller",
ZONE_ID, APPLICATION_ID);
+ private static final String VERSION_QUALIFIER_REGEX = "[a-zA-Z0-9_-]+";
+ private static final Pattern QUALIFIER_PATTERN = Pattern.compile("^" + VERSION_QUALIFIER_REGEX + "$");
+ private static final Pattern VERSION_PATTERN = Pattern.compile("^\\d\\.\\d\\.\\d(\\." + VERSION_QUALIFIER_REGEX + ")?$");
+
+ public static final UnboundStringFlag WANTED_DOCKER_TAG = defineStringFlag(
+ "wanted-docker-tag", "",
+ "If non-empty the flag value overrides the docker image tag of the wantedDockerImage of the node object. " +
+ "If the flag value contains '.', it must specify a valid Vespa version like '8.83.42'. " +
+ "Otherwise a '.' + the flag value will be appended.",
+ "Takes effect on the next host admin tick. The upgrade to the new wanted docker image is orchestrated.",
+ value -> value.isEmpty() || QUALIFIER_PATTERN.matcher(value).find() || VERSION_PATTERN.matcher(value).find(),
+ HOSTNAME, NODE_TYPE, TENANT_ID, APPLICATION_ID, CLUSTER_TYPE, CLUSTER_ID, VESPA_VERSION);
+
public static final UnboundStringFlag ZOOKEEPER_SERVER_VERSION = defineStringFlag(
"zookeeper-server-version", "3.7.1", // Note: Nodes running Vespa 7 have 3.7.1 as the only available version
"ZooKeeper server version, a jar file zookeeper-server-<ZOOKEEPER_SERVER_VERSION>-jar-with-dependencies.jar must exist",
@@ -280,6 +296,11 @@ public class PermanentFlags {
return Flags.defineStringFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions);
}
+ private static UnboundStringFlag defineStringFlag(
+ String flagId, String defaultValue, String description, String modificationEffect, Predicate<String> validator, FetchVector.Dimension... dimensions) {
+ return Flags.defineStringFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, validator, dimensions);
+ }
+
private static UnboundIntFlag defineIntFlag(
String flagId, int defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) {
return Flags.defineIntFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions);
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
index f96be55e2eb..9c69e917fa6 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
@@ -4,6 +4,10 @@ package com.yahoo.vespa.flags;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
/**
* @author hakonhall
*/
@@ -13,8 +17,26 @@ public class UnboundStringFlag extends UnboundFlagImpl<String, StringFlag, Unbou
}
public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector) {
- super(id, defaultValue, defaultFetchVector,
- new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText),
- UnboundStringFlag::new, StringFlag::new);
+ this(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText));
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, Predicate<String> validator) {
+ this(id, defaultValue, new FetchVector(), validator);
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector fetchVector, Predicate<String> validator) {
+ this(id, defaultValue, fetchVector,
+ new SimpleFlagSerializer<>(stringValue -> {
+ if (!validator.test(stringValue))
+ throw new IllegalArgumentException("Invalid value: '" + stringValue + "'");
+ return new TextNode(stringValue);
+ },
+ JsonNode::isTextual, JsonNode::asText));
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector,
+ FlagSerializer<String> serializer) {
+ super(id, defaultValue, defaultFetchVector, serializer, UnboundStringFlag::new, StringFlag::new);
}
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
index c8e45f0f61a..29942998083 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
@@ -91,7 +91,6 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
}
/** Creates an expression with simple lingustics for testing */
- @SuppressWarnings("deprecation")
public static ScriptExpression fromString(String expression) throws ParseException {
return fromString(expression, new SimpleLinguistics(), Embedder.throwsOnUse.asMap());
}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cors/CorsLogic.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cors/CorsLogic.java
index 3f6801eebe7..1ff76fd45ac 100644
--- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cors/CorsLogic.java
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cors/CorsLogic.java
@@ -41,6 +41,6 @@ class CorsLogic {
}
private static boolean requestOriginMatchesAnyAllowed(String requestOrigin, Set<String> allowedUrls) {
- return allowedUrls.stream().anyMatch(requestOrigin::startsWith) || allowedUrls.contains("*");
+ return allowedUrls.stream().anyMatch(requestOrigin::equals) || allowedUrls.contains("*");
}
}
diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cors/CorsPreflightRequestFilterTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cors/CorsPreflightRequestFilterTest.java
index b5b94d5a2c2..7ba050b7cc0 100644
--- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cors/CorsPreflightRequestFilterTest.java
+++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cors/CorsPreflightRequestFilterTest.java
@@ -43,6 +43,14 @@ public class CorsPreflightRequestFilterTest {
}
@Test
+ void extended_request_origin_does_not_yield_allow_origin_header_in_response() {
+ final String ALLOWED_ORIGIN = "https://allowed.origin";
+ final String EXTENDED_ORIGIN = "https://allowed.origin.as.subdomain.com";
+ HeaderFields headers = doFilterRequest(newRequestFilter(ALLOWED_ORIGIN), EXTENDED_ORIGIN);
+ assertNull(headers.getFirst(ALLOW_ORIGIN_HEADER));
+ }
+
+ @Test
void allowed_wildcard_origin_yields_origin_header_in_response() {
final String ALLOWED_ORIGIN = "http://allowed.origin";
HeaderFields headers = doFilterRequest(newRequestFilter("*"), ALLOWED_ORIGIN);
diff --git a/jrt/src/com/yahoo/jrt/Acceptor.java b/jrt/src/com/yahoo/jrt/Acceptor.java
index 8368941007b..d4bdb7007f1 100644
--- a/jrt/src/com/yahoo/jrt/Acceptor.java
+++ b/jrt/src/com/yahoo/jrt/Acceptor.java
@@ -1,14 +1,12 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jrt;
-
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
-
/**
* A class used to listen on a network socket. A separate thread is
* used to accept connections and register them with the underlying
diff --git a/linguistics/src/main/java/com/yahoo/language/opennlp/OpenNlpTokenizer.java b/linguistics/src/main/java/com/yahoo/language/opennlp/OpenNlpTokenizer.java
index dba19b47821..8080dc92729 100644
--- a/linguistics/src/main/java/com/yahoo/language/opennlp/OpenNlpTokenizer.java
+++ b/linguistics/src/main/java/com/yahoo/language/opennlp/OpenNlpTokenizer.java
@@ -7,24 +7,21 @@ import com.yahoo.language.process.Normalizer;
import com.yahoo.language.process.SpecialTokenRegistry;
import com.yahoo.language.process.StemMode;
import com.yahoo.language.process.Token;
-import com.yahoo.language.process.TokenType;
import com.yahoo.language.process.Tokenizer;
import com.yahoo.language.process.Transformer;
import com.yahoo.language.simple.SimpleNormalizer;
-import com.yahoo.language.simple.SimpleToken;
-import com.yahoo.language.simple.SimpleTokenType;
import com.yahoo.language.simple.SimpleTokenizer;
import com.yahoo.language.simple.SimpleTransformer;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.snowball.SnowballStemmer;
-import java.util.ArrayList;
import java.util.List;
/**
* Tokenizer using OpenNlp
*
* @author matskin
+ * @author bratseth
*/
public class OpenNlpTokenizer implements Tokenizer {
@@ -51,26 +48,11 @@ public class OpenNlpTokenizer implements Tokenizer {
@Override
public Iterable<Token> tokenize(String input, Language language, StemMode stemMode, boolean removeAccents) {
- if (input.isEmpty()) return List.of();
Stemmer stemmer = stemmerFor(language, stemMode);
- if (stemmer == null) return simpleTokenizer.tokenize(input, language, stemMode, removeAccents);
-
- List<Token> tokens = new ArrayList<>();
- int nextCode = input.codePointAt(0);
- TokenType prevType = SimpleTokenType.valueOf(nextCode);
- for (int prev = 0, next = Character.charCount(nextCode); next <= input.length(); ) {
- nextCode = next < input.length() ? input.codePointAt(next) : SPACE_CODE;
- TokenType nextType = SimpleTokenType.valueOf(nextCode);
- if (!prevType.isIndexable() || !nextType.isIndexable()) {
- String original = input.substring(prev, next);
- String token = processToken(original, language, stemMode, removeAccents, stemmer);
- tokens.add(new SimpleToken(original).setOffset(prev).setType(prevType).setTokenString(token));
- prev = next;
- prevType = nextType;
- }
- next += Character.charCount(nextCode);
- }
- return tokens;
+ if (stemmer == null)
+ return simpleTokenizer.tokenize(input, language, stemMode, removeAccents);
+ else
+ return simpleTokenizer.tokenize(input, token -> processToken(token, language, stemMode, removeAccents, stemmer));
}
private String processToken(String token, Language language, StemMode stemMode, boolean removeAccents,
diff --git a/linguistics/src/main/java/com/yahoo/language/simple/SimpleToken.java b/linguistics/src/main/java/com/yahoo/language/simple/SimpleToken.java
index 7479e326b45..b6ca219afc8 100644
--- a/linguistics/src/main/java/com/yahoo/language/simple/SimpleToken.java
+++ b/linguistics/src/main/java/com/yahoo/language/simple/SimpleToken.java
@@ -111,45 +111,28 @@ public class SimpleToken implements Token {
}
@Override
- public int hashCode() {
- return orig.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Token)) {
- return false;
- }
- Token rhs = (Token)obj;
- if (!getType().equals(rhs.getType())) {
- return false;
- }
- if (!equalsOpt(getOrig(), rhs.getOrig())) {
- return false;
- }
- if (getOffset() != rhs.getOffset()) {
- return false;
- }
- if (!equalsOpt(getScript(), rhs.getScript())) {
- return false;
- }
- if (!equalsOpt(getTokenString(), rhs.getTokenString())) {
- return false;
- }
- if (isSpecialToken() != rhs.isSpecialToken()) {
- return false;
- }
- if (getNumComponents() != rhs.getNumComponents()) {
- return false;
- }
+ public boolean equals(Object o) {
+ if (!(o instanceof Token other)) return false;
+
+ if (getType() != other.getType()) return false;
+ if (!equalsOpt(getOrig(), other.getOrig())) return false;
+ if (getOffset() != other.getOffset()) return false;
+ if (!equalsOpt(getScript(), other.getScript())) return false;
+ if (!equalsOpt(getTokenString(), other.getTokenString())) return false;
+ if (isSpecialToken() != other.isSpecialToken()) return false;
+ if (getNumComponents() != other.getNumComponents()) return false;
for (int i = 0, len = getNumComponents(); i < len; ++i) {
- if (!equalsOpt(getComponent(i), rhs.getComponent(i))) {
+ if (!equalsOpt(getComponent(i), other.getComponent(i)))
return false;
- }
}
return true;
}
+ @Override
+ public int hashCode() {
+ return orig.hashCode();
+ }
+
private static boolean equalsOpt(Object lhs, Object rhs) {
if (lhs == null || rhs == null) {
return lhs == rhs;
diff --git a/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenType.java b/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenType.java
index ace2fd3246e..5c321e4da9b 100644
--- a/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenType.java
+++ b/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenType.java
@@ -10,58 +10,58 @@ public class SimpleTokenType {
public static TokenType valueOf(int codePoint) {
switch (Character.getType(codePoint)) {
- case Character.NON_SPACING_MARK:
- // "combining grave accent"
- // and "DEVANAGARI VOWEL SIGN SHORT E" etc
- // (letter-like)
- case Character.COMBINING_SPACING_MARK:
- // "DEVANAGARI VOWEL SIGN SHORT O"
- // and similar (letter-like)
- case Character.LETTER_NUMBER:
- // "SMALL ROMAN NUMERAL SIX" etc (letter-like)
- case Character.UPPERCASE_LETTER:
- case Character.LOWERCASE_LETTER:
- case Character.TITLECASE_LETTER:
- case Character.MODIFIER_LETTER:
- case Character.OTHER_LETTER:
- return TokenType.ALPHABETIC;
+ case Character.NON_SPACING_MARK:
+ // "combining grave accent"
+ // and "DEVANAGARI VOWEL SIGN SHORT E" etc
+ // (letter-like)
+ case Character.COMBINING_SPACING_MARK:
+ // "DEVANAGARI VOWEL SIGN SHORT O"
+ // and similar (letter-like)
+ case Character.LETTER_NUMBER:
+ // "SMALL ROMAN NUMERAL SIX" etc (letter-like)
+ case Character.UPPERCASE_LETTER:
+ case Character.LOWERCASE_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ case Character.OTHER_LETTER:
+ return TokenType.ALPHABETIC;
- case Character.ENCLOSING_MARK:
- // "enclosing circle" etc is symbol-like
- case Character.MATH_SYMBOL:
- case Character.CURRENCY_SYMBOL:
- case Character.MODIFIER_SYMBOL:
- case Character.OTHER_SYMBOL:
- return TokenType.SYMBOL;
+ case Character.ENCLOSING_MARK:
+ // "enclosing circle" etc is symbol-like
+ case Character.MATH_SYMBOL:
+ case Character.CURRENCY_SYMBOL:
+ case Character.MODIFIER_SYMBOL:
+ case Character.OTHER_SYMBOL:
+ return TokenType.SYMBOL;
- case Character.OTHER_NUMBER:
- // "SUPERSCRIPT TWO",
- // "DINGBAT CIRCLED SANS-SERIF DIGIT THREE"
- // and more numbers that should mostly normalize
- // to digits
- case Character.DECIMAL_DIGIT_NUMBER:
- return TokenType.NUMERIC;
+ case Character.OTHER_NUMBER:
+ // "SUPERSCRIPT TWO",
+ // "DINGBAT CIRCLED SANS-SERIF DIGIT THREE"
+ // and more numbers that should mostly normalize
+ // to digits
+ case Character.DECIMAL_DIGIT_NUMBER:
+ return TokenType.NUMERIC;
- case Character.SPACE_SEPARATOR:
- case Character.LINE_SEPARATOR:
- case Character.PARAGRAPH_SEPARATOR:
- return TokenType.SPACE;
+ case Character.SPACE_SEPARATOR:
+ case Character.LINE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ return TokenType.SPACE;
- case Character.DASH_PUNCTUATION:
- case Character.START_PUNCTUATION:
- case Character.END_PUNCTUATION:
- case Character.CONNECTOR_PUNCTUATION:
- case Character.OTHER_PUNCTUATION:
- case Character.INITIAL_QUOTE_PUNCTUATION:
- case Character.FINAL_QUOTE_PUNCTUATION:
- return TokenType.PUNCTUATION;
+ case Character.DASH_PUNCTUATION:
+ case Character.START_PUNCTUATION:
+ case Character.END_PUNCTUATION:
+ case Character.CONNECTOR_PUNCTUATION:
+ case Character.OTHER_PUNCTUATION:
+ case Character.INITIAL_QUOTE_PUNCTUATION:
+ case Character.FINAL_QUOTE_PUNCTUATION:
+ return TokenType.PUNCTUATION;
- case Character.CONTROL:
- case Character.FORMAT:
- case Character.SURROGATE:
- case Character.PRIVATE_USE:
- case Character.UNASSIGNED:
- return TokenType.UNKNOWN;
+ case Character.CONTROL:
+ case Character.FORMAT:
+ case Character.SURROGATE:
+ case Character.PRIVATE_USE:
+ case Character.UNASSIGNED:
+ return TokenType.UNKNOWN;
}
throw new UnsupportedOperationException(String.valueOf(Character.getType(codePoint)));
}
diff --git a/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenizer.java b/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenizer.java
index 3dc28b99144..b791c843357 100644
--- a/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenizer.java
+++ b/linguistics/src/main/java/com/yahoo/language/simple/SimpleTokenizer.java
@@ -7,8 +7,8 @@ import com.yahoo.language.process.*;
import com.yahoo.language.simple.kstem.KStemmer;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import java.util.logging.Logger;
import java.util.logging.Level;
@@ -49,30 +49,46 @@ public class SimpleTokenizer implements Tokenizer {
this.specialTokenRegistry = specialTokenRegistry;
}
+ /** Tokenize the input, applying the transform of this to each token string. */
@Override
public Iterable<Token> tokenize(String input, Language language, StemMode stemMode, boolean removeAccents) {
- if (input.isEmpty()) return Collections.emptyList();
+ return tokenize(input,
+ token -> processToken(token, language, stemMode, removeAccents));
+ }
+
+ /** Tokenize the input, and apply the given transform to each token string. */
+ public Iterable<Token> tokenize(String input, Function<String, String> tokenProocessor) {
+ if (input.isEmpty()) return List.of();
List<Token> tokens = new ArrayList<>();
int nextCode = input.codePointAt(0);
TokenType prevType = SimpleTokenType.valueOf(nextCode);
+ TokenType tokenType = prevType;
for (int prev = 0, next = Character.charCount(nextCode); next <= input.length(); ) {
nextCode = next < input.length() ? input.codePointAt(next) : SPACE_CODE;
TokenType nextType = SimpleTokenType.valueOf(nextCode);
if (!prevType.isIndexable() || !nextType.isIndexable()) {
String original = input.substring(prev, next);
- String token = processToken(original, language, stemMode, removeAccents);
tokens.add(new SimpleToken(original).setOffset(prev)
- .setType(prevType)
- .setTokenString(token));
+ .setType(tokenType)
+ .setTokenString(tokenProocessor.apply(original)));
prev = next;
prevType = nextType;
+ tokenType = prevType;
+ }
+ else {
+ tokenType = determineType(tokenType, nextType);
}
next += Character.charCount(nextCode);
}
return tokens;
}
+ private TokenType determineType(TokenType tokenType, TokenType characterType) {
+ if (characterType == TokenType.ALPHABETIC) return TokenType.ALPHABETIC;
+ return tokenType;
+ }
+
private String processToken(String token, Language language, StemMode stemMode, boolean removeAccents) {
String original = token;
log.log(Level.FINEST, () -> "processToken '" + original + "'");
diff --git a/linguistics/src/test/java/com/yahoo/language/opennlp/OpenNlpTokenizationTestCase.java b/linguistics/src/test/java/com/yahoo/language/opennlp/OpenNlpTokenizationTestCase.java
index 77489f2eb44..cd2a0f73895 100644
--- a/linguistics/src/test/java/com/yahoo/language/opennlp/OpenNlpTokenizationTestCase.java
+++ b/linguistics/src/test/java/com/yahoo/language/opennlp/OpenNlpTokenizationTestCase.java
@@ -4,6 +4,7 @@ package com.yahoo.language.opennlp;
import com.yahoo.language.Language;
import com.yahoo.language.process.StemMode;
import com.yahoo.language.process.Token;
+import com.yahoo.language.process.TokenType;
import com.yahoo.language.process.Tokenizer;
import org.junit.Test;
@@ -151,11 +152,9 @@ public class OpenNlpTokenizationTestCase {
String input = "tafsirnya\u0648\u0643\u064F\u0646\u0652";
for (StemMode stemMode : new StemMode[] { StemMode.NONE,
StemMode.SHORTEST }) {
- for (Language l : new Language[] { Language.INDONESIAN,
- Language.ENGLISH, Language.ARABIC }) {
+ for (Language l : List.of(Language.INDONESIAN, Language.ENGLISH, Language.ARABIC)) {
for (boolean accentDrop : new boolean[] { true, false }) {
- for (Token token : tokenizer.tokenize(input,
- l, stemMode, accentDrop)) {
+ for (Token token : tokenizer.tokenize(input, l, stemMode, accentDrop)) {
if (token.getTokenString().length() == 0) {
assertFalse(token.isIndexable());
}
@@ -165,6 +164,31 @@ public class OpenNlpTokenizationTestCase {
}
}
+ @Test
+ public void testTokenTypes() {
+ testTokenTypes(Language.ENGLISH);
+ testTokenTypes(Language.SPANISH);
+ }
+
+ public void testTokenTypes(Language language) {
+ assertEquals(TokenType.ALPHABETIC, tokenize("word", language).iterator().next().getType());
+ assertEquals(TokenType.NUMERIC, tokenize("123", language).iterator().next().getType());
+ assertEquals(TokenType.SPACE, tokenize(" ", language).iterator().next().getType());
+ assertEquals(TokenType.PUNCTUATION, tokenize(".", language).iterator().next().getType());
+ assertEquals(TokenType.ALPHABETIC, tokenize("123word", language).iterator().next().getType());
+
+ var tokens = tokenize("123 123word word123", language).iterator();
+ assertEquals(TokenType.NUMERIC, tokens.next().getType());
+ assertEquals(TokenType.SPACE, tokens.next().getType());
+ assertEquals(TokenType.ALPHABETIC, tokens.next().getType());
+ assertEquals(TokenType.SPACE, tokens.next().getType());
+ assertEquals(TokenType.ALPHABETIC, tokens.next().getType());
+ }
+
+ private Iterable<Token> tokenize(String input, Language language) {
+ return tokenizer.tokenize(input, language, StemMode.SHORTEST, true);
+ }
+
private void recurseDecompose(Token t) {
assertTrue(t.getOffset() >= 0);
assertTrue(t.getOrig().length() >= 0);
diff --git a/linguistics/src/test/java/com/yahoo/language/process/GramSplitterTestCase.java b/linguistics/src/test/java/com/yahoo/language/process/GramSplitterTestCase.java
index 5054f5a9bff..fa8419e200f 100644
--- a/linguistics/src/test/java/com/yahoo/language/process/GramSplitterTestCase.java
+++ b/linguistics/src/test/java/com/yahoo/language/process/GramSplitterTestCase.java
@@ -171,21 +171,30 @@ public class GramSplitterTestCase {
public void testChineseComma() {
String text = "我喜欢红色、蓝色和紫色";
Iterator<GramSplitter.Gram> grams = gramSplitter.split(text, 2);
- for (; grams.hasNext(); ) {
- System.out.println(grams.next().extractFrom(text));
- }
+ assertEquals("我喜", grams.next().extractFrom(text));
+ assertEquals("喜欢", grams.next().extractFrom(text));
+ assertEquals("欢红", grams.next().extractFrom(text));
+ assertEquals("红色", grams.next().extractFrom(text));
+ assertEquals("蓝色", grams.next().extractFrom(text));
+ assertEquals("色和", grams.next().extractFrom(text));
+ assertEquals("和紫", grams.next().extractFrom(text));
+ assertEquals("紫色", grams.next().extractFrom(text));
}
@Test
public void testEnglishComma() {
String text = "我喜欢红色,蓝色和紫色";
Iterator<GramSplitter.Gram> grams = gramSplitter.split(text, 2);
- for (; grams.hasNext(); ) {
- System.out.println(grams.next().extractFrom(text));
- }
+ assertEquals("我喜", grams.next().extractFrom(text));
+ assertEquals("喜欢", grams.next().extractFrom(text));
+ assertEquals("欢红", grams.next().extractFrom(text));
+ assertEquals("红色", grams.next().extractFrom(text));
+ assertEquals("蓝色", grams.next().extractFrom(text));
+ assertEquals("色和", grams.next().extractFrom(text));
+ assertEquals("和紫", grams.next().extractFrom(text));
+ assertEquals("紫色", grams.next().extractFrom(text));
}
-
private void assertGramSplits(String input, int gramSize, String ... expected) {
assertEquals(Arrays.asList(expected), gramSplitter.split(input, gramSize).toExtractedList());
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
index a371cdcde25..a112c0d2697 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImpl.java
@@ -49,9 +49,9 @@ public class NodeAdminImpl implements NodeAdmin {
private final Gauge jvmHeapUsed;
private final Gauge jvmHeapFree;
private final Gauge jvmHeapTotal;
- private final Gauge memoryOverhead;
private final Gauge containerCount;
private final Counter numberOfUnhandledExceptions;
+ private final Metrics metrics;
public NodeAdminImpl(NodeAgentFactory nodeAgentFactory, Metrics metrics, Clock clock, FileSystem fileSystem) {
this(nodeAgentContext -> create(clock, nodeAgentFactory, nodeAgentContext),
@@ -82,8 +82,8 @@ public class NodeAdminImpl implements NodeAdmin {
this.jvmHeapUsed = metrics.declareGauge("mem.heap.used");
this.jvmHeapFree = metrics.declareGauge("mem.heap.free");
this.jvmHeapTotal = metrics.declareGauge("mem.heap.total");
- this.memoryOverhead = metrics.declareGauge("mem.system.overhead");
this.containerCount = metrics.declareGauge("container.count");
+ this.metrics = metrics;
}
@Override
@@ -139,7 +139,8 @@ public class NodeAdminImpl implements NodeAdmin {
if (!isSuspended) {
containerCount.sample(numContainers);
ProcMeminfo meminfo = procMeminfoReader.read();
- memoryOverhead.sample(meminfo.memTotalBytes() - meminfo.memAvailableBytes() - totalContainerMemoryBytes);
+ metrics.declareGauge("mem.system.overhead", new Dimensions(Map.of("containers", Long.toString(numContainers))))
+ .sample(meminfo.memTotalBytes() - meminfo.memAvailableBytes() - totalContainerMemoryBytes);
}
}
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 6408132fe0b..025723fe71c 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
@@ -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.node.admin.nodeagent;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -212,15 +213,15 @@ public class NodeAgentImpl implements NodeAgent {
changed = true;
}
- Optional<DockerImage> actualDockerImage = context.node().wantedDockerImage().filter(n -> containerState == UNKNOWN);
- if (!Objects.equals(context.node().currentDockerImage(), actualDockerImage)) {
+ Optional<DockerImage> wantedDockerImage = context.node().wantedDockerImage().filter(n -> containerState == UNKNOWN);
+ if (!Objects.equals(context.node().currentDockerImage(), wantedDockerImage)) {
DockerImage currentImage = context.node().currentDockerImage().orElse(DockerImage.EMPTY);
- DockerImage newImage = actualDockerImage.orElse(DockerImage.EMPTY);
+ DockerImage newImage = wantedDockerImage.orElse(DockerImage.EMPTY);
currentNodeAttributes.withDockerImage(currentImage);
- currentNodeAttributes.withVespaVersion(currentImage.tagAsVersion());
+ currentNodeAttributes.withVespaVersion(context.node().currentVespaVersion().orElse(Version.emptyVersion));
newNodeAttributes.withDockerImage(newImage);
- newNodeAttributes.withVespaVersion(newImage.tagAsVersion());
+ newNodeAttributes.withVespaVersion(context.node().wantedVespaVersion().orElse(Version.emptyVersion));
changed = true;
}
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 62bde5ff1a8..4cbfac4842e 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
@@ -25,7 +25,10 @@ public class RestartTest {
DockerImage dockerImage = DockerImage.fromString("registry.example.com/repo/image:1.2.3");
try (ContainerTester tester = new ContainerTester(List.of(dockerImage))) {
String hostname = "host1.test.yahoo.com";
- NodeSpec nodeSpec = NodeSpec.Builder.testSpec(hostname).wantedDockerImage(dockerImage).build();
+ NodeSpec nodeSpec = NodeSpec.Builder.testSpec(hostname)
+ .wantedDockerImage(dockerImage)
+ .wantedVespaVersion(dockerImage.tagAsVersion())
+ .build();
tester.addChildNodeRepositoryNode(nodeSpec);
ContainerName host1 = new ContainerName("host1");
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 e4c2f595164..a7697e5cb5f 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
@@ -187,7 +187,7 @@ public class NodeAgentImplTest {
inOrder.verify(containerOperations, times(1)).resumeNode(eq(context));
inOrder.verify(healthChecker, times(1)).verifyHealth(eq(context));
inOrder.verify(nodeRepository).updateNodeAttributes(
- hostName, new NodeAttributes().withDockerImage(dockerImage).withVespaVersion(dockerImage.tagAsVersion()).withRebootGeneration(0));
+ hostName, new NodeAttributes().withDockerImage(dockerImage).withVespaVersion(vespaVersion).withRebootGeneration(0));
inOrder.verify(orchestrator, never()).resume(hostName);
}
@@ -614,7 +614,7 @@ public class NodeAgentImplTest {
inOrder.verify(aclMaintainer, times(1)).converge(eq(context));
inOrder.verify(containerOperations, times(1)).resumeNode(eq(context));
inOrder.verify(nodeRepository).updateNodeAttributes(
- hostName, new NodeAttributes().withDockerImage(dockerImage).withVespaVersion(dockerImage.tagAsVersion()).withRebootGeneration(0));
+ hostName, new NodeAttributes().withDockerImage(dockerImage).withVespaVersion(vespaVersion).withRebootGeneration(0));
inOrder.verify(orchestrator).resume(hostName);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
index 87a9735f91e..309280c8f15 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
@@ -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.provision.restapi;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.DockerImage;
@@ -10,10 +11,14 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.PermanentFlags;
+import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Address;
+import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.hosted.provision.node.TrustStoreItem;
import com.yahoo.vespa.hosted.provision.node.filter.NodeFilter;
@@ -47,6 +52,7 @@ class NodesResponse extends SlimeJsonResponse {
private final boolean recursive;
private final Function<HostName, Optional<HostInfo>> orchestrator;
private final NodeRepository nodeRepository;
+ private final StringFlag wantedDockerTagFlag;
public NodesResponse(ResponseType responseType, HttpRequest request,
Orchestrator orchestrator, NodeRepository nodeRepository) {
@@ -56,6 +62,7 @@ class NodesResponse extends SlimeJsonResponse {
this.recursive = request.getBooleanProperty("recursive");
this.orchestrator = orchestrator.getHostResolver();
this.nodeRepository = nodeRepository;
+ this.wantedDockerTagFlag = PermanentFlags.WANTED_DOCKER_TAG.bindTo(nodeRepository.flagSource());
Cursor root = slime.setObject();
switch (responseType) {
@@ -146,7 +153,7 @@ class NodesResponse extends SlimeJsonResponse {
toSlime(allocation.membership(), object.setObject("membership"));
object.setLong("restartGeneration", allocation.restartGeneration().wanted());
object.setLong("currentRestartGeneration", allocation.restartGeneration().current());
- object.setString("wantedDockerImage", nodeRepository.containerImages().get(node).withTag(allocation.membership().cluster().vespaVersion()).asString());
+ object.setString("wantedDockerImage", nodeRepository.containerImages().get(node).withTag(resolveVersionFlag(wantedDockerTagFlag, node, allocation)).asString());
object.setString("wantedVespaVersion", allocation.membership().cluster().vespaVersion().toFullString());
NodeResourcesSerializer.toSlime(allocation.requestedResources(), object.setObject("requestedResources"));
allocation.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray("networkPorts")));
@@ -189,6 +196,24 @@ class NodesResponse extends SlimeJsonResponse {
node.cloudAccount().ifPresent(cloudAccount -> object.setString("cloudAccount", cloudAccount.value()));
}
+ private Version resolveVersionFlag(StringFlag flag, Node node, Allocation allocation) {
+ String value = flag
+ .with(FetchVector.Dimension.HOSTNAME, node.hostname())
+ .with(FetchVector.Dimension.NODE_TYPE, node.type().name())
+ .with(FetchVector.Dimension.TENANT_ID, allocation.owner().tenant().value())
+ .with(FetchVector.Dimension.APPLICATION_ID, allocation.owner().serializedForm())
+ .with(FetchVector.Dimension.CLUSTER_TYPE, allocation.membership().cluster().type().name())
+ .with(FetchVector.Dimension.CLUSTER_ID, allocation.membership().cluster().id().value())
+ .with(FetchVector.Dimension.VESPA_VERSION, allocation.membership().cluster().vespaVersion().toFullString())
+ .value();
+
+ return value.isEmpty() ?
+ allocation.membership().cluster().vespaVersion() :
+ value.indexOf('.') == -1 ?
+ allocation.membership().cluster().vespaVersion().withQualifier(value) :
+ new Version(value);
+ }
+
private void toSlime(ApplicationId id, Cursor object) {
object.setString("tenant", id.tenant().value());
object.setString("application", id.application().value());
diff --git a/parent/pom.xml b/parent/pom.xml
index a83c7f50960..cab654eb4a5 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -1066,7 +1066,7 @@
<maven-site-plugin.version>3.9.1</maven-site-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<mockito.version>4.0.0</mockito.version>
- <onnxruntime.version>1.11.0</onnxruntime.version> <!-- WARNING: sync cloud-tenant-base-dependencies-enforcer/pom.xml -->
+ <onnxruntime.version>1.12.1</onnxruntime.version> <!-- WARNING: sync cloud-tenant-base-dependencies-enforcer/pom.xml -->
<org.json.version>20220320</org.json.version>
<org.lz4.version>1.8.0</org.lz4.version>
<prometheus.client.version>0.6.0</prometheus.client.version>
diff --git a/screwdriver.yaml b/screwdriver.yaml
index 0ffc3316f95..21ab6625eb4 100644
--- a/screwdriver.yaml
+++ b/screwdriver.yaml
@@ -283,6 +283,17 @@ jobs:
exit 1
fi
+ verify-opensource-rpm-installable:
+ image: quay.io/centos/centos:stream8
+ annotations:
+ screwdriver.cd/buildPeriodically: H 0 * * *
+ steps:
+ - install: |
+ dnf config-manager --add-repo https://raw.githubusercontent.com/vespa-engine/vespa/master/dist/vespa-engine.repo
+ dnf config-manager --enable powertools
+ dnf install -y epel-release
+ dnf install -y vespa
+
mirror-copr-rpms-to-artifactory:
annotations:
screwdriver.cd/cpu: LOW
diff --git a/searchcore/src/apps/verify_ranksetup/CMakeLists.txt b/searchcore/src/apps/verify_ranksetup/CMakeLists.txt
index cc8434ef46f..536392739d9 100644
--- a/searchcore/src/apps/verify_ranksetup/CMakeLists.txt
+++ b/searchcore/src/apps/verify_ranksetup/CMakeLists.txt
@@ -1,12 +1,20 @@
# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(searchcore_verify_ranksetup_app
+vespa_add_library(searchcore_verify_ranksetup
SOURCES
verify_ranksetup.cpp
- OUTPUT_NAME vespa-verify-ranksetup-bin
- INSTALL bin
+ INSTALL lib64
DEPENDS
searchcore_fconfig
searchcore_matching
searchcore_documentmetastore
)
-vespa_generate_config(searchcore_verify_ranksetup_app verify-ranksetup.def)
+vespa_generate_config(searchcore_verify_ranksetup verify-ranksetup.def)
+
+vespa_add_executable(searchcore_verify_ranksetup_app
+ SOURCES
+ verify_ranksetup_app.cpp
+ OUTPUT_NAME vespa-verify-ranksetup-bin
+ INSTALL bin
+ DEPENDS
+ searchcore_verify_ranksetup
+)
diff --git a/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp
index e72771f5aa6..dcfbc34653d 100644
--- a/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp
+++ b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp
@@ -1,5 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "verify_ranksetup.h"
#include "config-verify-ranksetup.h"
#include <vespa/config-attributes.h>
#include <vespa/config-indexschema.h>
@@ -21,12 +22,10 @@
#include <vespa/searchlib/fef/fef.h>
#include <vespa/searchlib/fef/test/plugin/setup.h>
#include <vespa/config/subscription/configsubscriber.hpp>
-#include <vespa/vespalib/util/signalhandler.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/stllike/asciistream.h>
#include <optional>
-#include <vespa/log/log.h>
-LOG_SETUP("vespa-verify-ranksetup");
-
using config::ConfigContext;
using config::ConfigHandle;
using config::ConfigRuntimeException;
@@ -50,8 +49,10 @@ using vespalib::eval::SimpleConstantValue;
using vespalib::eval::TensorSpec;
using vespalib::eval::Value;
using vespalib::eval::ValueType;
+using vespalib::make_string_short::fmt;
-std::optional<vespalib::string> get_file(const vespalib::string &ref, const VerifyRanksetupConfig &myCfg) {
+std::optional<vespalib::string>
+get_file(const vespalib::string &ref, const VerifyRanksetupConfig &myCfg) {
for (const auto &entry: myCfg.file) {
if (ref == entry.ref) {
return entry.path;
@@ -60,37 +61,43 @@ std::optional<vespalib::string> get_file(const vespalib::string &ref, const Veri
return std::nullopt;
}
-RankingExpressions make_expressions(const RankingExpressionsConfig &expressionsCfg, const VerifyRanksetupConfig &myCfg) {
+RankingExpressions
+make_expressions(const RankingExpressionsConfig &expressionsCfg, const VerifyRanksetupConfig &myCfg,
+ std::vector<search::fef::Message> & messages) {
RankingExpressions expressions;
for (const auto &entry: expressionsCfg.expression) {
if (auto file = get_file(entry.fileref, myCfg)) {
- LOG(debug, "Add expression %s with ref=%s and path=%s", entry.name.c_str(), entry.fileref.c_str(), file.value().c_str());
expressions.add(entry.name, file.value());
} else {
- LOG(warning, "could not find file name for ranking expression '%s' (ref:'%s')",
- entry.name.c_str(), entry.fileref.c_str());
+ messages.emplace_back(search::fef::Level::WARNING,
+ fmt("could not find file name for ranking expression '%s' (ref:'%s')",
+ entry.name.c_str(), entry.fileref.c_str()));
}
}
return expressions;
}
-OnnxModels make_models(const OnnxModelsConfig &modelsCfg, const VerifyRanksetupConfig &myCfg) {
+OnnxModels
+make_models(const OnnxModelsConfig &modelsCfg, const VerifyRanksetupConfig &myCfg,
+ std::vector<search::fef::Message> & messages) {
OnnxModels::Vector model_list;
for (const auto &entry: modelsCfg.model) {
if (auto file = get_file(entry.fileref, myCfg)) {
model_list.emplace_back(entry.name, file.value());
OnnxModels::configure(entry, model_list.back());
} else {
- LOG(warning, "could not find file name for onnx model '%s' (ref:'%s')",
- entry.name.c_str(), entry.fileref.c_str());
+ messages.emplace_back(search::fef::Level::WARNING,
+ fmt("could not find file name for onnx model '%s' (ref:'%s')",
+ entry.name.c_str(), entry.fileref.c_str()));
}
}
return OnnxModels(model_list);
}
-class App
+class VerifyRankSetup
{
-public:
+private:
+ std::vector<search::fef::Message> _messages;
bool verify(const search::index::Schema &schema,
const search::fef::Properties &props,
const IConstantValueRepo &repo,
@@ -105,30 +112,42 @@ public:
const RankingExpressionsConfig &expressionsCfg,
const OnnxModelsConfig &modelsCfg);
- int usage();
- int main(int argc, char **argv);
+public:
+ VerifyRankSetup();
+ ~VerifyRankSetup();
+ const std::vector<search::fef::Message> & getMessages() const { return _messages; }
+ bool verify(const std::string & configId);
};
struct DummyConstantValueRepo : IConstantValueRepo {
const RankingConstantsConfig &cfg;
DummyConstantValueRepo(const RankingConstantsConfig &cfg_in) : cfg(cfg_in) {}
- virtual vespalib::eval::ConstantValue::UP getConstant(const vespalib::string &name) const override {
- for (const auto &entry: cfg.constant) {
- if (entry.name == name) {
- try {
- auto tensor = vespalib::eval::value_from_spec(TensorSpec(entry.type), FastValueBuilderFactory::get());
- return std::make_unique<SimpleConstantValue>(std::move(tensor));
- } catch (std::exception &) {
- return std::make_unique<BadConstantValue>();
- }
+ vespalib::eval::ConstantValue::UP getConstant(const vespalib::string &name) const override;
+};
+
+vespalib::eval::ConstantValue::UP
+DummyConstantValueRepo::getConstant(const vespalib::string &name) const {
+ for (const auto &entry: cfg.constant) {
+ if (entry.name == name) {
+ try {
+ auto tensor = vespalib::eval::value_from_spec(TensorSpec(entry.type), FastValueBuilderFactory::get());
+ return std::make_unique<SimpleConstantValue>(std::move(tensor));
+ } catch (std::exception &) {
+ return std::make_unique<BadConstantValue>();
}
}
- return vespalib::eval::ConstantValue::UP(nullptr);
}
-};
+ return vespalib::eval::ConstantValue::UP(nullptr);
+}
+
+VerifyRankSetup::VerifyRankSetup()
+ : _messages()
+{ }
+
+VerifyRankSetup::~VerifyRankSetup() = default;
bool
-App::verify(const search::index::Schema &schema,
+VerifyRankSetup::verify(const search::index::Schema &schema,
const search::fef::Properties &props,
const IConstantValueRepo &repo,
const RankingExpressions &expressions,
@@ -143,25 +162,25 @@ App::verify(const search::index::Schema &schema,
rankSetup.configure(); // reads config values from the property map
bool ok = true;
if (!rankSetup.getFirstPhaseRank().empty()) {
- ok = verifyFeature(factory, indexEnv, rankSetup.getFirstPhaseRank(), "first phase ranking") && ok;
+ ok = verifyFeature(factory, indexEnv, rankSetup.getFirstPhaseRank(), "first phase ranking", _messages) && ok;
}
if (!rankSetup.getSecondPhaseRank().empty()) {
- ok = verifyFeature(factory, indexEnv, rankSetup.getSecondPhaseRank(), "second phase ranking") && ok;
+ ok = verifyFeature(factory, indexEnv, rankSetup.getSecondPhaseRank(), "second phase ranking", _messages) && ok;
}
for (size_t i = 0; i < rankSetup.getSummaryFeatures().size(); ++i) {
- ok = verifyFeature(factory, indexEnv, rankSetup.getSummaryFeatures()[i], "summary features") && ok;
+ ok = verifyFeature(factory, indexEnv, rankSetup.getSummaryFeatures()[i], "summary features", _messages) && ok;
}
for (const auto & feature : rankSetup.get_match_features()) {
- ok = verifyFeature(factory, indexEnv, feature, "match features") && ok;
+ ok = verifyFeature(factory, indexEnv, feature, "match features", _messages) && ok;
}
for (size_t i = 0; i < rankSetup.getDumpFeatures().size(); ++i) {
- ok = verifyFeature(factory, indexEnv, rankSetup.getDumpFeatures()[i], "dump features") && ok;
+ ok = verifyFeature(factory, indexEnv, rankSetup.getDumpFeatures()[i], "dump features", _messages) && ok;
}
return ok;
}
bool
-App::verifyConfig(const VerifyRanksetupConfig &myCfg,
+VerifyRankSetup::verifyConfig(const VerifyRanksetupConfig &myCfg,
const RankProfilesConfig &rankCfg,
const IndexschemaConfig &schemaCfg,
const AttributesConfig &attributeCfg,
@@ -174,8 +193,8 @@ App::verifyConfig(const VerifyRanksetupConfig &myCfg,
search::index::SchemaBuilder::build(schemaCfg, schema);
search::index::SchemaBuilder::build(attributeCfg, schema);
DummyConstantValueRepo repo(constantsCfg);
- auto expressions = make_expressions(expressionsCfg, myCfg);
- auto models = make_models(modelsCfg, myCfg);
+ auto expressions = make_expressions(expressionsCfg, myCfg, _messages);
+ auto models = make_models(modelsCfg, myCfg, _messages);
for(size_t i = 0; i < rankCfg.rankprofile.size(); i++) {
search::fef::Properties properties;
const RankProfilesConfig::Rankprofile &profile = rankCfg.rankprofile[i];
@@ -184,33 +203,20 @@ App::verifyConfig(const VerifyRanksetupConfig &myCfg,
profile.fef.property[j].value);
}
if (verify(schema, properties, repo, expressions, models)) {
- LOG(info, "rank profile '%s': pass", profile.name.c_str());
+ _messages.emplace_back(search::fef::Level::INFO,
+ fmt("rank profile '%s': pass", profile.name.c_str()));
} else {
- LOG(error, "rank profile '%s': FAIL", profile.name.c_str());
+ _messages.emplace_back(search::fef::Level::ERROR,
+ fmt("rank profile '%s': FAIL", profile.name.c_str()));
ok = false;
}
}
return ok;
}
-int
-App::usage()
-{
- fprintf(stderr, "Usage: vespa-verify-ranksetup <config-id>\n");
- return 1;
-}
-
-int
-App::main(int argc, char **argv)
+bool
+VerifyRankSetup::verify(const std::string & configid)
{
- if (argc != 2) {
- return usage();
- }
-
- const std::string configid = argv[1];
- LOG(debug, "verifying rank setup for config id '%s' ...",
- configid.c_str());
-
bool ok = false;
try {
auto ctx = std::make_shared<ConfigContext>(*config::legacyConfigId2Spec(configid));
@@ -233,18 +239,19 @@ App::main(int argc, char **argv)
*expressionsHandle->getConfig(),
*modelsHandle->getConfig());
} catch (ConfigRuntimeException & e) {
- LOG(error, "Unable to subscribe to config: %s", e.getMessage().c_str());
+ _messages.emplace_back(search::fef::Level::ERROR,
+ fmt("Unable to subscribe to config: %s", e.getMessage().c_str()));
} catch (InvalidConfigException & e) {
- LOG(error, "Error getting config: %s", e.getMessage().c_str());
- }
- if (!ok) {
- return 1;
+ _messages.emplace_back(search::fef::Level::ERROR,
+ fmt("Error getting config: %s", e.getMessage().c_str()));
}
- return 0;
+ return ok;
}
-int main(int argc, char **argv) {
- vespalib::SignalHandler::PIPE.ignore();
- App app;
- return app.main(argc, argv);
+std::pair<bool, std::vector<search::fef::Message>>
+verifyRankSetup(const char * configId) {
+ VerifyRankSetup verifier;
+ bool ok = verifier.verify(configId);
+
+ return {ok, verifier.getMessages()};
}
diff --git a/searchcore/src/apps/verify_ranksetup/verify_ranksetup.h b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.h
new file mode 100644
index 00000000000..d65dede8696
--- /dev/null
+++ b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.h
@@ -0,0 +1,7 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/searchlib/fef/verify_feature.h>
+
+std::pair<bool, std::vector<search::fef::Message>> verifyRankSetup(const char * configId);
diff --git a/searchcore/src/apps/verify_ranksetup/verify_ranksetup_app.cpp b/searchcore/src/apps/verify_ranksetup/verify_ranksetup_app.cpp
new file mode 100644
index 00000000000..4f1bf1acbed
--- /dev/null
+++ b/searchcore/src/apps/verify_ranksetup/verify_ranksetup_app.cpp
@@ -0,0 +1,56 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "verify_ranksetup.h"
+#include <vespa/vespalib/util/signalhandler.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("vespa-verify-ranksetup");
+
+class App
+{
+public:
+ int usage();
+ int main(int argc, char **argv);
+};
+
+int
+App::usage()
+{
+ fprintf(stderr, "Usage: vespa-verify-ranksetup <config-id>\n");
+ return 1;
+}
+
+namespace {
+ns_log::Logger::LogLevel
+toLogLevel(search::fef::Level level) {
+ switch (level) {
+ case search::fef::Level::INFO:
+ return ns_log::Logger::LogLevel::info;
+ case search::fef::Level::WARNING:
+ return ns_log::Logger::LogLevel::warning;
+ case search::fef::Level::ERROR:
+ return ns_log::Logger::LogLevel::error;
+ }
+ abort();
+}
+}
+int
+App::main(int argc, char **argv)
+{
+ if (argc != 2) {
+ return usage();
+ }
+
+ auto [ok, messages] = verifyRankSetup(argv[1]);
+
+ for (const auto & msg : messages) {
+ VLOG(toLogLevel(msg.first), "%s", msg.second.c_str());
+ }
+ return ok ? 0 : 1;
+}
+
+int main(int argc, char **argv) {
+ vespalib::SignalHandler::PIPE.ignore();
+ App app;
+ return app.main(argc, argv);
+}
diff --git a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.cpp b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.cpp
index 3ddb1afdb35..486472aec0f 100644
--- a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.cpp
+++ b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.cpp
@@ -2,7 +2,6 @@
#include "bucket_db_snapshot.h"
#include <vespa/persistence/spi/persistenceprovider.h>
-#include <vespa/vespalib/stllike/hash_set.hpp>
#include <vespa/vespalib/stllike/hash_map.hpp>
#include <cassert>
@@ -66,9 +65,4 @@ BucketDbSnapshot::try_get_bucket_info(BucketId bucket_id) const
}
-namespace vespalib {
-
-template class hash_map<BucketId, BucketInfo, BucketId::hash>;
-template class hash_set<BucketId, BucketId::hash>;
-
-}
+VESPALIB_HASH_MAP_INSTANTIATE_H(document::BucketId, storage::spi::BucketInfo, document::BucketId::hash);
diff --git a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.h b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.h
index d750a73f207..189250194b4 100644
--- a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.h
+++ b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot.h
@@ -22,6 +22,10 @@ class BucketDbSnapshot
public:
using BucketIdSet = vespalib::hash_set<document::BucketId, document::BucketId::hash>;
BucketDbSnapshot();
+ BucketDbSnapshot(const BucketDbSnapshot &) = delete;
+ BucketDbSnapshot & operator=(const BucketDbSnapshot &) = delete;
+ BucketDbSnapshot(BucketDbSnapshot &&) noexcept = default;
+ BucketDbSnapshot & operator=(BucketDbSnapshot &&) noexcept = default;
~BucketDbSnapshot();
void populate(document::BucketSpace bucket_space, storage::spi::PersistenceProvider& provider);
uint32_t count_new_documents(const BucketDbSnapshot &old) const;
diff --git a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot_vector.h b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot_vector.h
index 7799ed8fc58..df6183fd6f9 100644
--- a/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot_vector.h
+++ b/searchcore/src/vespa/searchcore/bmcluster/bucket_db_snapshot_vector.h
@@ -16,8 +16,9 @@ class BucketDbSnapshotVector
vespalib::hash_map<document::BucketSpace, std::vector<BucketDbSnapshot>, document::BucketSpace::hash> _snapshots;
using BucketIdSet = BucketDbSnapshot::BucketIdSet;
public:
-
BucketDbSnapshotVector(const std::vector<storage::spi::PersistenceProvider *>& providers, const storage::lib::ClusterStateBundle &cluster_state_bundle);
+ BucketDbSnapshotVector(const BucketDbSnapshotVector &) = delete;
+ BucketDbSnapshotVector & operator = (const BucketDbSnapshotVector &) = delete;
~BucketDbSnapshotVector();
uint32_t count_moved_documents(const BucketDbSnapshotVector &old) const;
uint32_t count_lost_unique_documents(const BucketDbSnapshotVector &old) const;
diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
index a99d5cbd573..0fee6e494fe 100644
--- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
+++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
@@ -14,6 +14,7 @@ namespace proton {
using bucketdb::RemoveBatchEntry;
+
BucketDB::BucketDB()
: _map(),
_cachedBucketId(),
@@ -95,7 +96,6 @@ BucketDB::modify(const GlobalId &gid,
}
}
-
bucketdb::BucketState
BucketDB::get(BucketId bucketId) const
{
diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
index bd6aa16504b..7c1c336ab48 100644
--- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
+++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
@@ -13,15 +13,14 @@ namespace proton {
class BucketDB
{
-public:
- typedef document::GlobalId GlobalId;
- typedef document::BucketId BucketId;
- typedef storage::spi::Timestamp Timestamp;
- typedef storage::spi::BucketChecksum BucketChecksum;
- typedef bucketdb::BucketState BucketState;
- typedef vespalib::hash_map<BucketId, BucketState, BucketId::hash> Map;
-
private:
+ using GlobalId = document::GlobalId;
+ using BucketId = document::BucketId;
+ using Timestamp = storage::spi::Timestamp;
+ using BucketChecksum = storage::spi::BucketChecksum;
+ using BucketState = bucketdb::BucketState;
+ using Map = vespalib::hash_map<BucketId, BucketState, document::BucketId::hash>;
+
Map _map;
BucketId _cachedBucketId;
BucketState _cachedBucketState;
diff --git a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
index 4e78bf04df6..697028e42cc 100644
--- a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
+++ b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp
@@ -31,7 +31,7 @@ public:
* using event barriers to resolve visibility constraints in the
* future without changing the external API.
**/
- class Snapshot : public vespalib::Sequence<T*>
+ class Snapshot final : public vespalib::Sequence<T*>
{
private:
std::vector<HandlerSP> _handlers;
@@ -41,7 +41,7 @@ public:
Snapshot() : _handlers(), _offset(0) { }
Snapshot(const StdMap &map) : _handlers(), _offset(0) {
_handlers.reserve(map.size());
- for (auto itr : map) {
+ for (const auto & itr : map) {
_handlers.push_back(itr.second);
}
}
@@ -51,11 +51,39 @@ public:
Snapshot(const Snapshot &) = delete;
Snapshot & operator = (const Snapshot &) = delete;
+ size_t size() const { return _handlers.size(); }
+
bool valid() const override { return (_offset < _handlers.size()); }
T *get() const override { return _handlers[_offset].get(); }
void next() override { ++_offset; }
};
+ class UnsafeSnapshot final : public vespalib::Sequence<T*>
+ {
+ private:
+ std::vector<T *> _handlers;
+ size_t _offset;
+
+ public:
+ UnsafeSnapshot() : _handlers(), _offset(0) { }
+ UnsafeSnapshot(const StdMap &map) : _handlers(), _offset(0) {
+ _handlers.reserve(map.size());
+ for (const auto & itr : map) {
+ _handlers.push_back(itr.second.get());
+ }
+ }
+ UnsafeSnapshot(UnsafeSnapshot &&) noexcept = default;
+ UnsafeSnapshot & operator = (UnsafeSnapshot &&) noexcept = default;
+ UnsafeSnapshot(const UnsafeSnapshot &) = delete;
+ UnsafeSnapshot & operator = (const UnsafeSnapshot &) = delete;
+
+ size_t size() const { return _handlers.size(); }
+
+ bool valid() const override { return (_offset < _handlers.size()); }
+ T *get() const override { return _handlers[_offset]; }
+ void next() override { ++_offset; }
+ };
+
/**
* Convenience typedefs.
*/
@@ -170,12 +198,19 @@ public:
**/
Snapshot snapshot() const { return Snapshot(_handlers); }
+ /**
+ * Create a snapshot of the handlers currently contained in this
+ * map and return it as a sequence. Note that any lifetime guarantees
+ * must be be given at a higher level
+ *
+ * @return handler sequence
+ **/
+ UnsafeSnapshot unsafeSnapshot() const { return UnsafeSnapshot(_handlers); }
+
// we want to use snapshots rather than direct iteration to reduce locking;
// the below functions should be deprecated when possible.
- iterator begin() { return _handlers.begin(); }
const_iterator begin() const { return _handlers.begin(); }
- iterator end() { return _handlers.end(); }
const_iterator end() const { return _handlers.end(); }
size_t size() const { return _handlers.size(); }
};
diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
index 011d97d4609..fd3deb2abd7 100644
--- a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
+++ b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp
@@ -367,7 +367,7 @@ FlushEngine::initFlush(const FlushContext &ctx)
void
FlushEngine::flushDone(const FlushContext &ctx, uint32_t taskId)
{
- vespalib::duration duration = vespalib::duration::zero();
+ vespalib::duration duration;
{
std::lock_guard<std::mutex> guard(_lock);
duration = _flushing[taskId].elapsed();
@@ -422,7 +422,7 @@ FlushEngine::getCurrentlyFlushingSet() const
uint32_t
FlushEngine::initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP &target)
{
- uint32_t taskId(0);
+ uint32_t taskId;
{
std::lock_guard<std::mutex> guard(_lock);
taskId = _taskId++;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
index 826860b743e..108397e6e5a 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
@@ -32,10 +32,12 @@ using search::MatchingElements;
using search::attribute::IAttributeContext;
using search::fef::MatchDataLayout;
using search::fef::MatchData;
+using search::fef::RankSetup;
using search::fef::indexproperties::hitcollector::HeapSize;
using search::queryeval::Blueprint;
using search::queryeval::SearchIterator;
using vespalib::Doom;
+using vespalib::make_string_short::fmt;
namespace proton::matching {
@@ -117,7 +119,8 @@ Matcher::Matcher(const search::index::Schema &schema, const Properties &props, c
_rankSetup = std::make_shared<search::fef::RankSetup>(_blueprintFactory, _indexEnv);
_rankSetup->configure(); // reads config values from the property map
if (!_rankSetup->compile()) {
- throw vespalib::IllegalArgumentException("failed to compile rank setup", VESPA_STRLOC);
+ throw vespalib::IllegalArgumentException(fmt("failed to compile rank setup :\n%s",
+ _rankSetup->getJoinedWarnings().c_str()), VESPA_STRLOC);
}
}
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.cpp
index cc619c3a09a..1bcb96702a4 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.cpp
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.cpp
@@ -6,6 +6,7 @@
namespace proton {
using HandlerSnapshot = PersistenceHandlerMap::HandlerSnapshot;
+using UnsafeHandlerSnapshot = PersistenceHandlerMap::UnsafeHandlerSnapshot;
PersistenceHandlerMap::PersistenceHandlerMap()
: _map()
@@ -51,8 +52,7 @@ PersistenceHandlerMap::getHandlerSnapshot() const
handlers.push_back(handlerItr.second);
}
}
- size_t handlersSize = handlers.size();
- return HandlerSnapshot(DocTypeToHandlerMap::Snapshot(std::move(handlers)), handlersSize);
+ return HandlerSnapshot(DocTypeToHandlerMap::Snapshot(std::move(handlers)));
}
HandlerSnapshot
@@ -60,9 +60,22 @@ PersistenceHandlerMap::getHandlerSnapshot(document::BucketSpace bucketSpace) con
{
auto itr = _map.find(bucketSpace);
if (itr != _map.end()) {
- return HandlerSnapshot(itr->second.snapshot(), itr->second.size());
+ return HandlerSnapshot(itr->second.snapshot());
}
return HandlerSnapshot();
}
+UnsafeHandlerSnapshot
+PersistenceHandlerMap::getUnsafeHandlerSnapshot(document::BucketSpace bucketSpace) const
+{
+ auto itr = _map.find(bucketSpace);
+ if (itr != _map.end()) {
+ return UnsafeHandlerSnapshot(itr->second.unsafeSnapshot());
+ }
+ return UnsafeHandlerSnapshot();
+}
+
+PersistenceHandlerMap::HandlerSnapshot::~HandlerSnapshot() = default;
+PersistenceHandlerMap::UnsafeHandlerSnapshot::~UnsafeHandlerSnapshot() = default;
+
}
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.h b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.h
index 749f044ff0e..807b0f0b5d7 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.h
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistence_handler_map.h
@@ -21,30 +21,42 @@ class IPersistenceHandler;
class PersistenceHandlerMap {
public:
using DocTypeToHandlerMap = HandlerMap<IPersistenceHandler>;
- using PersistenceHandlerSequence = DocTypeToHandlerMap::Snapshot;
using PersistenceHandlerSP = std::shared_ptr<IPersistenceHandler>;
class HandlerSnapshot {
private:
- PersistenceHandlerSequence _handlers;
- size_t _size;
+ DocTypeToHandlerMap::Snapshot _handlers;
public:
- HandlerSnapshot() : _handlers(), _size(0) {}
- HandlerSnapshot(DocTypeToHandlerMap::Snapshot handlers_, size_t size_)
- : _handlers(std::move(handlers_)),
- _size(size_)
+ HandlerSnapshot() : _handlers() {}
+ HandlerSnapshot(DocTypeToHandlerMap::Snapshot handlers_)
+ : _handlers(std::move(handlers_))
{}
HandlerSnapshot(const HandlerSnapshot &) = delete;
HandlerSnapshot & operator = (const HandlerSnapshot &) = delete;
+ ~HandlerSnapshot();
- size_t size() const { return _size; }
- PersistenceHandlerSequence &handlers() { return _handlers; }
- static PersistenceHandlerSequence release(HandlerSnapshot &&rhs) { return std::move(rhs._handlers); }
+ size_t size() const { return _handlers.size(); }
+ vespalib::Sequence<IPersistenceHandler*> &handlers() { return _handlers; }
+ static DocTypeToHandlerMap::Snapshot release(HandlerSnapshot &&rhs) { return std::move(rhs._handlers); }
};
-private:
+ class UnsafeHandlerSnapshot {
+ private:
+ DocTypeToHandlerMap::UnsafeSnapshot _handlers;
+ public:
+ UnsafeHandlerSnapshot() : _handlers() {}
+ UnsafeHandlerSnapshot(DocTypeToHandlerMap::UnsafeSnapshot handlers_)
+ : _handlers(std::move(handlers_))
+ {}
+ UnsafeHandlerSnapshot(const UnsafeHandlerSnapshot &) = delete;
+ UnsafeHandlerSnapshot & operator = (const UnsafeHandlerSnapshot &) = delete;
+ ~UnsafeHandlerSnapshot();
+ size_t size() const { return _handlers.size(); }
+ vespalib::Sequence<IPersistenceHandler*> &handlers() { return _handlers; }
+ };
+private:
struct BucketSpaceHash {
std::size_t operator() (const document::BucketSpace &bucketSpace) const { return bucketSpace.getId(); }
};
@@ -59,6 +71,7 @@ public:
IPersistenceHandler * getHandler(document::BucketSpace bucketSpace, const DocTypeName &docType) const;
HandlerSnapshot getHandlerSnapshot() const;
HandlerSnapshot getHandlerSnapshot(document::BucketSpace bucketSpace) const;
+ UnsafeHandlerSnapshot getUnsafeHandlerSnapshot(document::BucketSpace bucketSpace) const;
};
}
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
index 81a7244ba1d..df3dcdcf66a 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
@@ -5,7 +5,6 @@
#include "transport_latch.h"
#include <vespa/persistence/spi/bucketexecutor.h>
#include <vespa/persistence/spi/catchresult.h>
-#include <vespa/vespalib/stllike/hash_set.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/datatype/documenttype.h>
#include <vespa/document/update/documentupdate.h>
@@ -200,11 +199,16 @@ PersistenceEngine::getHandlerSnapshot(const WriteGuard &) const
}
PersistenceEngine::HandlerSnapshot
-PersistenceEngine::getHandlerSnapshot(const ReadGuard &, document::BucketSpace bucketSpace) const
+PersistenceEngine::getSafeHandlerSnapshot(const ReadGuard &, document::BucketSpace bucketSpace) const
{
return _handlers.getHandlerSnapshot(bucketSpace);
}
+PersistenceEngine::UnsafeHandlerSnapshot
+PersistenceEngine::getHandlerSnapshot(const ReadGuard &, document::BucketSpace bucketSpace) const {
+ return _handlers.getUnsafeHandlerSnapshot(bucketSpace);
+}
+
PersistenceEngine::HandlerSnapshot
PersistenceEngine::getHandlerSnapshot(const WriteGuard &, document::BucketSpace bucketSpace) const
{
@@ -279,7 +283,7 @@ PersistenceEngine::listBuckets(BucketSpace bucketSpace) const
// Runs in SPI thread.
// No handover to write threads in persistence handlers.
ReadGuard rguard(_rwMutex);
- HandlerSnapshot snap = getHandlerSnapshot(rguard, bucketSpace);
+ auto snap = getHandlerSnapshot(rguard, bucketSpace);
BucketIdListResultHandler resultHandler;
for (; snap.handlers().valid(); snap.handlers().next()) {
IPersistenceHandler *handler = snap.handlers().get();
@@ -294,7 +298,7 @@ PersistenceEngine::setClusterState(BucketSpace bucketSpace, const ClusterState &
{
ReadGuard rguard(_rwMutex);
saveClusterState(bucketSpace, calc);
- HandlerSnapshot snap = getHandlerSnapshot(rguard, bucketSpace);
+ auto snap = getHandlerSnapshot(rguard, bucketSpace);
auto catchResult = std::make_unique<storage::spi::CatchResult>();
auto futureResult = catchResult->future_result();
GenericResultHandler resultHandler(snap.size(), std::move(catchResult));
@@ -312,7 +316,7 @@ void
PersistenceEngine::setActiveStateAsync(const Bucket & bucket, BucketInfo::ActiveState newState, OperationComplete::UP onComplete)
{
ReadGuard rguard(_rwMutex);
- HandlerSnapshot snap = getHandlerSnapshot(rguard, bucket.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, bucket.getBucketSpace());
auto resultHandler = std::make_shared<GenericResultHandler>(snap.size(), std::move(onComplete));
while (snap.handlers().valid()) {
IPersistenceHandler *handler = snap.handlers().get();
@@ -332,7 +336,7 @@ PersistenceEngine::getBucketInfo(const Bucket& b) const
// Runs in SPI thread.
// No handover to write threads in persistence handlers.
ReadGuard rguard(_rwMutex);
- HandlerSnapshot snap = getHandlerSnapshot(rguard, b.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, b.getBucketSpace());
BucketInfoResultHandler resultHandler;
for (; snap.handlers().valid(); snap.handlers().next()) {
IPersistenceHandler *handler = snap.handlers().get();
@@ -482,10 +486,10 @@ PersistenceEngine::GetResult
PersistenceEngine::get(const Bucket& b, const document::FieldSet& fields, const DocumentId& did, Context& context) const
{
ReadGuard rguard(_rwMutex);
- HandlerSnapshot snapshot = getHandlerSnapshot(rguard, b.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, b.getBucketSpace());
- for (PersistenceHandlerSequence & handlers = snapshot.handlers(); handlers.valid(); handlers.next()) {
- IPersistenceHandler::RetrieversSP retrievers = handlers.get()->getDocumentRetrievers(context.getReadConsistency());
+ for (;snap.handlers().valid(); snap.handlers().next()) {
+ IPersistenceHandler::RetrieversSP retrievers = snap.handlers().get()->getDocumentRetrievers(context.getReadConsistency());
for (size_t i = 0; i < retrievers->size(); ++i) {
IDocumentRetriever &retriever = *(*retrievers)[i];
search::DocumentMetaData meta = retriever.getDocumentMetaData(did);
@@ -514,17 +518,17 @@ PersistenceEngine::createIterator(const Bucket &bucket, FieldSetSP fields, const
IncludedVersions versions, Context &context)
{
ReadGuard rguard(_rwMutex);
- HandlerSnapshot snapshot = getHandlerSnapshot(rguard, bucket.getBucketSpace());
+ auto snap = getSafeHandlerSnapshot(rguard, bucket.getBucketSpace());
auto entry = std::make_unique<IteratorEntry>(context.getReadConsistency(), bucket, std::move(fields), selection,
versions, _defaultSerializedSize, _ignoreMaxBytes);
- for (PersistenceHandlerSequence & handlers = snapshot.handlers(); handlers.valid(); handlers.next()) {
- IPersistenceHandler::RetrieversSP retrievers = handlers.get()->getDocumentRetrievers(context.getReadConsistency());
+ for (; snap.handlers().valid(); snap.handlers().next()) {
+ IPersistenceHandler::RetrieversSP retrievers = snap.handlers().get()->getDocumentRetrievers(context.getReadConsistency());
for (const auto & retriever : *retrievers) {
entry->it.add(retriever);
}
}
- entry->handler_sequence = HandlerSnapshot::release(std::move(snapshot));
+ entry->handler_sequence = HandlerSnapshot::release(std::move(snap));
std::lock_guard<std::mutex> guard(_iterators_lock);
static std::atomic<IteratorId::Type> id_counter(0);
@@ -591,7 +595,7 @@ PersistenceEngine::createBucketAsync(const Bucket &b, OperationComplete::UP onCo
{
ReadGuard rguard(_rwMutex);
LOG(spam, "createBucket(%s)", b.toString().c_str());
- HandlerSnapshot snap = getHandlerSnapshot(rguard, b.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, b.getBucketSpace());
auto transportContext = std::make_shared<AsyncTransportContext>(snap.size(), std::move(onComplete));
while (snap.handlers().valid()) {
@@ -611,7 +615,7 @@ PersistenceEngine::deleteBucketAsync(const Bucket& b, OperationComplete::UP onCo
{
ReadGuard rguard(_rwMutex);
LOG(spam, "deleteBucket(%s)", b.toString().c_str());
- HandlerSnapshot snap = getHandlerSnapshot(rguard, b.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, b.getBucketSpace());
auto transportContext = std::make_shared<AsyncTransportContext>(snap.size(), std::move(onComplete));
while (snap.handlers().valid()) {
@@ -636,7 +640,7 @@ PersistenceEngine::getModifiedBuckets(BucketSpace bucketSpace) const
std::lock_guard<std::mutex> guard(_lock);
extraModifiedBuckets.swap(_extraModifiedBuckets[bucketSpace]);
}
- HandlerSnapshot snap = getHandlerSnapshot(rguard, bucketSpace);
+ auto snap = getHandlerSnapshot(rguard, bucketSpace);
auto catchResult = std::make_unique<storage::spi::CatchResult>();
auto futureResult = catchResult->future_result();
SynchronizedBucketIdListResultHandler resultHandler(snap.size() + extraModifiedBuckets.size(), std::move(catchResult));
@@ -658,7 +662,7 @@ PersistenceEngine::split(const Bucket& source, const Bucket& target1, const Buck
LOG(spam, "split(%s, %s, %s)", source.toString().c_str(), target1.toString().c_str(), target2.toString().c_str());
assert(source.getBucketSpace() == target1.getBucketSpace());
assert(source.getBucketSpace() == target2.getBucketSpace());
- HandlerSnapshot snap = getHandlerSnapshot(rguard, source.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, source.getBucketSpace());
TransportLatch latch(snap.size());
for (; snap.handlers().valid(); snap.handlers().next()) {
IPersistenceHandler *handler = snap.handlers().get();
@@ -676,7 +680,7 @@ PersistenceEngine::join(const Bucket& source1, const Bucket& source2, const Buck
LOG(spam, "join(%s, %s, %s)", source1.toString().c_str(), source2.toString().c_str(), target.toString().c_str());
assert(source1.getBucketSpace() == target.getBucketSpace());
assert(source2.getBucketSpace() == target.getBucketSpace());
- HandlerSnapshot snap = getHandlerSnapshot(rguard, target.getBucketSpace());
+ auto snap = getHandlerSnapshot(rguard, target.getBucketSpace());
TransportLatch latch(snap.size());
for (; snap.handlers().valid(); snap.handlers().next()) {
IPersistenceHandler *handler = snap.handlers().get();
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.h b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.h
index c16cc6e6a83..2581d35064e 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.h
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.h
@@ -19,8 +19,9 @@ class IDiskMemUsageNotifier;
class PersistenceEngine : public storage::spi::AbstractPersistenceProvider,
public storage::spi::BucketExecutor {
private:
- using PersistenceHandlerSequence = PersistenceHandlerMap::PersistenceHandlerSequence;
+ using PersistenceHandlerSequence = PersistenceHandlerMap::DocTypeToHandlerMap::Snapshot;
using HandlerSnapshot = PersistenceHandlerMap::HandlerSnapshot;
+ using UnsafeHandlerSnapshot = PersistenceHandlerMap::UnsafeHandlerSnapshot;
using DocumentUpdate = document::DocumentUpdate;
using Bucket = storage::spi::Bucket;
using BucketIdListResult = storage::spi::BucketIdListResult;
@@ -38,7 +39,6 @@ private:
using Result = storage::spi::Result;
using Selection = storage::spi::Selection;
using Timestamp = storage::spi::Timestamp;
- using TimestampList = storage::spi::TimestampList;
using UpdateResult = storage::spi::UpdateResult;
using OperationComplete = storage::spi::OperationComplete;
using BucketExecutor = storage::spi::BucketExecutor;
@@ -82,7 +82,8 @@ private:
IPersistenceHandler * getHandler(const ReadGuard & guard, document::BucketSpace bucketSpace, const DocTypeName &docType) const;
HandlerSnapshot getHandlerSnapshot(const WriteGuard & guard) const;
- HandlerSnapshot getHandlerSnapshot(const ReadGuard & guard, document::BucketSpace bucketSpace) const;
+ HandlerSnapshot getSafeHandlerSnapshot(const ReadGuard & guard, document::BucketSpace bucketSpace) const;
+ UnsafeHandlerSnapshot getHandlerSnapshot(const ReadGuard & guard, document::BucketSpace bucketSpace) const;
HandlerSnapshot getHandlerSnapshot(const WriteGuard & guard, document::BucketSpace bucketSpace) const;
void saveClusterState(BucketSpace bucketSpace, const ClusterState &calc);
diff --git a/searchlib/src/tests/fef/object_passing/object_passing_test.cpp b/searchlib/src/tests/fef/object_passing/object_passing_test.cpp
index 3639da05b9e..d7e5fd2600e 100644
--- a/searchlib/src/tests/fef/object_passing/object_passing_test.cpp
+++ b/searchlib/src/tests/fef/object_passing/object_passing_test.cpp
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/stllike/string.h>
-#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/searchlib/features/valuefeature.h>
#include <vespa/searchlib/fef/blueprintfactory.h>
#include <vespa/searchlib/fef/test/indexenvironment.h>
@@ -102,7 +101,8 @@ struct Fixture {
}
bool verify(const vespalib::string &feature) {
- return verifyFeature(factory, indexEnv, feature, "unit test");
+ std::vector<search::fef::Message> errors;
+ return verifyFeature(factory, indexEnv, feature, "unit test", errors);
}
};
diff --git a/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp b/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp
index 24358db8f3a..6d49704e7c1 100644
--- a/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp
+++ b/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp
@@ -4,10 +4,37 @@
#include <vespa/searchlib/fef/test/indexenvironment.h>
#include <vespa/searchlib/fef/test/plugin/setup.h>
#include <vespa/searchlib/features/valuefeature.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <regex>
using namespace search::features;
using namespace search::fef::test;
using namespace search::fef;
+using vespalib::make_string_short::fmt;
+
+typedef bool (*cmp)(const vespalib::string & a, const vespalib::string &b);
+
+namespace search::fef {
+std::ostream &operator<<(std::ostream &os, Level level) {
+ if (level == Level::INFO) {
+ return os << "info";
+ }
+ if (level == Level::WARNING) {
+ return os << "warning";
+ }
+ return os << "error";
+}
+}
+
+bool equal(const vespalib::string & a, const vespalib::string &b) {
+ return a == b;
+}
+
+bool regex(const vespalib::string & a, const vespalib::string &b) {
+ std::regex exp(b.c_str());
+ return std::regex_match(a.c_str(), exp);
+}
struct RankFixture {
BlueprintFactory factory;
@@ -15,43 +42,89 @@ struct RankFixture {
RankFixture() : factory(), indexEnv() {
setup_fef_test_plugin(factory);
- factory.addPrototype(Blueprint::SP(new ValueBlueprint()));
+ factory.addPrototype(std::make_shared<ValueBlueprint>());
}
- bool verify(const std::string &feature) const {
- return verifyFeature(factory, indexEnv, feature, "feature verification test");
+ bool verify(const std::string &feature, std::vector<std::pair<cmp, Message>> expected) const {
+ std::vector<Message> errors;
+ bool ok = verifyFeature(factory, indexEnv, feature, "feature verification test", errors);
+ EXPECT_EQUAL(errors.size(), expected.size());
+ for (size_t i(0); i < std::min(errors.size(), expected.size()); i++) {
+ EXPECT_EQUAL(errors[i].first, expected[i].second.first);
+ EXPECT_TRUE(expected[i].first(errors[i].second, expected[i].second.second));
+ }
+ return ok;
}
};
TEST_F("verify valid rank feature", RankFixture) {
- EXPECT_TRUE(f1.verify("value(1, 2, 3).0"));
- EXPECT_TRUE(f1.verify("value(1, 2, 3).1"));
- EXPECT_TRUE(f1.verify("value(1, 2, 3).2"));
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).0", {}));
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).1", {}));
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).2", {}));
}
TEST_F("verify unknown feature", RankFixture) {
- EXPECT_FALSE(f1.verify("unknown"));
+ EXPECT_FALSE(f1.verify("unknown",
+ {{equal, {Level::WARNING, "invalid rank feature 'unknown': unknown basename: 'unknown'"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'unknown' (feature verification test)"}}}));
}
TEST_F("verify unknown output", RankFixture) {
- EXPECT_FALSE(f1.verify("value(1, 2, 3).3"));
+ EXPECT_FALSE(f1.verify("value(1, 2, 3).3",
+ {{equal, {Level::WARNING, "invalid rank feature 'value(1,2,3).3': unknown output: '3'"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'value(1, 2, 3).3' (feature verification test)"}}}));
}
TEST_F("verify illegal input parameters", RankFixture) {
- EXPECT_FALSE(f1.verify("value.0"));
+ EXPECT_FALSE(f1.verify("value.0",
+ {{equal, {Level::WARNING, "invalid rank feature 'value.0':"
+ " The parameter list used for setting up rank feature value is not valid:"
+ " Expected 1+1x parameter(s), but got 0"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'value.0' (feature verification test)"}}}));
}
TEST_F("verify illegal feature name", RankFixture) {
- EXPECT_FALSE(f1.verify("value(2)."));
+ EXPECT_FALSE(f1.verify("value(2).",
+ {{equal, {Level::WARNING, "invalid rank feature 'value(2).': malformed name"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'value(2).' (feature verification test)"}}}));
}
TEST_F("verify too deep dependency graph", RankFixture) {
- EXPECT_TRUE(f1.verify("chain(basic, 255, 4)"));
- EXPECT_FALSE(f1.verify("chain(basic, 256, 4)"));
+ EXPECT_TRUE(f1.verify("chain(basic, 255, 4)", {}));
+ EXPECT_FALSE(f1.verify("chain(basic, 256, 4)",
+ {{equal,
+ {Level::WARNING,
+ "invalid rank feature 'value(4)': dependency graph too deep\n"
+ " ... needed by rank feature 'chain(basic,1,4)'\n"
+ " ... needed by rank feature 'chain(basic,2,4)'\n"
+ " ... needed by rank feature 'chain(basic,3,4)'\n"
+ " ... needed by rank feature 'chain(basic,4,4)'\n"
+ " ... needed by rank feature 'chain(basic,5,4)'\n"
+ " ... needed by rank feature 'chain(basic,6,4)'\n"
+ " ... needed by rank feature 'chain(basic,7,4)'\n"
+ " ... needed by rank feature 'chain(basic,8,4)'\n"
+ " ... needed by rank feature 'chain(basic,9,4)'\n"
+ " ... needed by rank feature 'chain(basic,10,4)'\n"
+ " (skipped 241 entries)\n"
+ " ... needed by rank feature 'chain(basic,252,4)'\n"
+ " ... needed by rank feature 'chain(basic,253,4)'\n"
+ " ... needed by rank feature 'chain(basic,254,4)'\n"
+ " ... needed by rank feature 'chain(basic,255,4)'\n"
+ " ... needed by rank feature 'chain(basic,256,4)'\n"}},
+ {regex, {Level::WARNING, "high stack usage: [0-9]+ bytes"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'chain(basic, 256, 4)' (feature verification test)"}}}));
}
TEST_F("verify dependency cycle", RankFixture) {
- EXPECT_FALSE(f1.verify("chain(cycle, 4, 2)"));
+ EXPECT_FALSE(f1.verify("chain(cycle, 4, 2)",
+ {{equal,
+ {Level::WARNING,
+ "invalid rank feature 'chain(cycle,2,2)': dependency cycle detected\n"
+ " ... needed by rank feature 'chain(cycle,1,2)'\n"
+ " ... needed by rank feature 'chain(cycle,2,2)'\n"
+ " ... needed by rank feature 'chain(cycle,3,2)'\n"
+ " ... needed by rank feature 'chain(cycle,4,2)'\n"}},
+ {equal, {Level::ERROR, "verification failed: rank feature 'chain(cycle, 4, 2)' (feature verification test)"}}}));
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.cpp b/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.cpp
index 90386dffd51..4b2d67c933d 100644
--- a/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.cpp
+++ b/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.cpp
@@ -79,13 +79,13 @@ resolve_attribute_for_field(const fef::IQueryEnvironment& env,
}
-DistanceCalculatorBundle::Element::Element(fef::TermFieldHandle handle_in)
+DistanceCalculatorBundle::Element::Element(fef::TermFieldHandle handle_in) noexcept
: handle(handle_in),
calc()
{
}
-DistanceCalculatorBundle::Element::Element(fef::TermFieldHandle handle_in, std::unique_ptr<search::tensor::DistanceCalculator> calc_in)
+DistanceCalculatorBundle::Element::Element(fef::TermFieldHandle handle_in, std::unique_ptr<search::tensor::DistanceCalculator> calc_in) noexcept
: handle(handle_in),
calc(std::move(calc_in))
{
diff --git a/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.h b/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.h
index dd3fc521d96..35295c771a6 100644
--- a/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.h
+++ b/searchlib/src/vespa/searchlib/features/distance_calculator_bundle.h
@@ -27,8 +27,8 @@ public:
fef::TermFieldHandle handle;
std::unique_ptr<search::tensor::DistanceCalculator> calc;
Element(Element&& rhs) noexcept = default; // Needed as std::vector::reserve() is used.
- Element(fef::TermFieldHandle handle_in);
- Element(fef::TermFieldHandle handle_in, std::unique_ptr<search::tensor::DistanceCalculator> calc_in);
+ Element(fef::TermFieldHandle handle_in) noexcept;
+ Element(fef::TermFieldHandle handle_in, std::unique_ptr<search::tensor::DistanceCalculator> calc_in) noexcept;
~Element();
};
private:
diff --git a/searchlib/src/vespa/searchlib/fef/blueprint.cpp b/searchlib/src/vespa/searchlib/fef/blueprint.cpp
index 58094121e24..72e4021986a 100644
--- a/searchlib/src/vespa/searchlib/fef/blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/fef/blueprint.cpp
@@ -5,9 +5,6 @@
#include <cassert>
#include <vespa/vespalib/util/stringfmt.h>
-#include <vespa/log/log.h>
-LOG_SETUP(".fef.blueprint");
-
namespace search::fef {
std::optional<FeatureType>
diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
index 731306d1bea..b672d754b72 100644
--- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
+++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp
@@ -7,14 +7,10 @@
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/util/size_literals.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
-#include <stack>
#include <cassert>
#include <set>
#include <thread>
-#include <vespa/log/log.h>
-LOG_SETUP(".fef.blueprintresolver");
-
using vespalib::make_string_short::fmt;
using vespalib::ThreadStackExecutor;
using vespalib::makeLambdaTask;
@@ -63,6 +59,7 @@ struct Compiler : public Blueprint::DependencyHandler {
: spec(std::move(blueprint)), parser(parser_in) {}
};
using Stack = std::vector<Frame>;
+ using Errors = std::vector<vespalib::string>;
struct FrameGuard {
Stack &stack;
@@ -73,15 +70,16 @@ struct Compiler : public Blueprint::DependencyHandler {
}
};
- const BlueprintFactory &factory;
- const IIndexEnvironment &index_env;
- Stack resolve_stack;
- ExecutorSpecList &spec_list;
- FeatureMap &feature_map;
- std::set<vespalib::string> setup_set;
- std::set<vespalib::string> failed_set;
- const char *min_stack;
- const char *max_stack;
+ const BlueprintFactory &factory;
+ const IIndexEnvironment &index_env;
+ Stack resolve_stack;
+ Errors errors;
+ ExecutorSpecList &spec_list;
+ FeatureMap &feature_map;
+ std::set<vespalib::string> setup_set;
+ std::set<vespalib::string> failed_set;
+ const char *min_stack;
+ const char *max_stack;
Compiler(const BlueprintFactory &factory_in,
const IIndexEnvironment &index_env_in,
@@ -90,6 +88,7 @@ struct Compiler : public Blueprint::DependencyHandler {
: factory(factory_in),
index_env(index_env_in),
resolve_stack(),
+ errors(),
spec_list(spec_list_out),
feature_map(feature_map_out),
setup_set(),
@@ -138,11 +137,13 @@ struct Compiler : public Blueprint::DependencyHandler {
if (failed_set.count(feature_name) == 0) {
failed_set.insert(feature_name);
auto trace = make_trace(skip_self);
+ vespalib::string msg;
if (trace.empty()) {
- LOG(warning, "invalid %s: %s", describe(feature_name).c_str(), reason.c_str());
+ msg = fmt("invalid %s: %s", describe(feature_name).c_str(), reason.c_str());
} else {
- LOG(warning, "invalid %s: %s\n%s", describe(feature_name).c_str(), reason.c_str(), trace.c_str());
+ msg = fmt("invalid %s: %s\n%s", describe(feature_name).c_str(), reason.c_str(), trace.c_str());
}
+ errors.emplace_back(msg);
}
probe_stack();
return FeatureRef();
@@ -264,7 +265,8 @@ BlueprintResolver::BlueprintResolver(const BlueprintFactory &factory,
_seeds(),
_executorSpecs(),
_featureMap(),
- _seedMap()
+ _seedMap(),
+ _warnings()
{
}
@@ -300,6 +302,7 @@ BlueprintResolver::compile()
for (const auto &seed: _seeds) {
auto ref = compiler.resolve_feature(seed, Blueprint::AcceptInput::ANY);
if (compiler.failed()) {
+ _warnings = std::move(compiler.errors);
return;
}
_seedMap.emplace(FeatureNameParser(seed).featureName(), ref);
@@ -311,27 +314,9 @@ BlueprintResolver::compile()
executor.shutdown();
size_t stack_usage = compiler.stack_usage();
if (stack_usage > (128_Ki)) {
- LOG(warning, "high stack usage: %zu bytes", stack_usage);
+ _warnings.emplace_back(fmt("high stack usage: %zu bytes", stack_usage));
}
return !compiler.failed();
}
-const BlueprintResolver::ExecutorSpecList &
-BlueprintResolver::getExecutorSpecs() const
-{
- return _executorSpecs;
-}
-
-const BlueprintResolver::FeatureMap &
-BlueprintResolver::getFeatureMap() const
-{
- return _featureMap;
-}
-
-const BlueprintResolver::FeatureMap &
-BlueprintResolver::getSeedMap() const
-{
- return _seedMap;
-}
-
}
diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.h b/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
index 3e3b5879518..459082b4534 100644
--- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
+++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.h
@@ -24,7 +24,8 @@ class FeatureNameParser;
class BlueprintResolver
{
public:
- typedef std::shared_ptr<BlueprintResolver> SP;
+ using SP = std::shared_ptr<BlueprintResolver>;
+ using Warnings = std::vector<vespalib::string>;
/**
* Low-level reference to a single output from a feature
@@ -44,7 +45,7 @@ public:
: executor(executor_in), output(output_in) {}
bool valid() { return (executor != undef); }
};
- typedef std::map<vespalib::string, FeatureRef> FeatureMap;
+ using FeatureMap = std::map<vespalib::string, FeatureRef>;
/**
* Thin blueprint wrapper with additional information about how
@@ -59,7 +60,7 @@ public:
ExecutorSpec(Blueprint::SP blueprint_in);
~ExecutorSpec();
};
- typedef std::vector<ExecutorSpec> ExecutorSpecList;
+ using ExecutorSpecList = std::vector<ExecutorSpec>;
/**
* The maximum dependency depth. This value is defined to protect
@@ -84,6 +85,7 @@ private:
ExecutorSpecList _executorSpecs;
FeatureMap _featureMap;
FeatureMap _seedMap;
+ Warnings _warnings;
public:
BlueprintResolver(const BlueprintResolver &) = delete;
@@ -134,7 +136,7 @@ public:
*
* @return feature executor assembly directions
**/
- const ExecutorSpecList &getExecutorSpecs() const;
+ const ExecutorSpecList &getExecutorSpecs() const { return _executorSpecs; }
/**
* Obtain the location of all named features known to this
@@ -145,7 +147,7 @@ public:
*
* @return feature locations
**/
- const FeatureMap &getFeatureMap() const;
+ const FeatureMap &getFeatureMap() const { return _featureMap; }
/**
* Obtain the location of all seeds used by this resolver. This
@@ -156,7 +158,13 @@ public:
*
* @return seed locations
**/
- const FeatureMap &getSeedMap() const;
+ const FeatureMap &getSeedMap() const { return _seedMap; }
+
+ /**
+ * Will return any accumulated warnings during compile
+ * @return list of warnings
+ **/
+ const Warnings & getWarnings() const { return _warnings; }
};
}
diff --git a/searchlib/src/vespa/searchlib/fef/ranksetup.cpp b/searchlib/src/vespa/searchlib/fef/ranksetup.cpp
index ed841ae24b3..30e220b0a34 100644
--- a/searchlib/src/vespa/searchlib/fef/ranksetup.cpp
+++ b/searchlib/src/vespa/searchlib/fef/ranksetup.cpp
@@ -3,9 +3,10 @@
#include "ranksetup.h"
#include "indexproperties.h"
#include "featurenameparser.h"
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/stllike/asciistream.h>
-#include <vespa/log/log.h>
-LOG_SETUP(".fef.ranksetup");
+using vespalib::make_string_short::fmt;
namespace {
class VisitorAdapter : public search::fef::IDumpFeatureVisitor
@@ -53,6 +54,7 @@ RankSetup::RankSetup(const BlueprintFactory &factory, const IIndexEnvironment &i
_match_features(),
_summaryFeatures(),
_dumpFeatures(),
+ _warnings(),
_feature_rename_map(),
_ignoreDefaultRankFeatures(false),
_compiled(false),
@@ -134,50 +136,59 @@ RankSetup::configure()
void
RankSetup::setFirstPhaseRank(const vespalib::string &featureName)
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
_firstPhaseRankFeature = featureName;
}
void
RankSetup::setSecondPhaseRank(const vespalib::string &featureName)
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
_secondPhaseRankFeature = featureName;
}
void
RankSetup::add_match_feature(const vespalib::string &match_feature)
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
_match_features.push_back(match_feature);
}
void
RankSetup::addSummaryFeature(const vespalib::string &summaryFeature)
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
_summaryFeatures.push_back(summaryFeature);
}
void
RankSetup::addDumpFeature(const vespalib::string &dumpFeature)
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
_dumpFeatures.push_back(dumpFeature);
}
+void
+RankSetup::compileAndCheckForErrors(BlueprintResolver &bpr) {
+ bool ok = bpr.compile();
+ if ( ! ok ) {
+ _compileError = true;
+ const auto & warnings = bpr.getWarnings();
+ _warnings.insert(_warnings.end(), warnings.begin(), warnings.end());
+ }
+}
bool
RankSetup::compile()
{
- LOG_ASSERT(!_compiled);
+ assert(!_compiled);
if (!_firstPhaseRankFeature.empty()) {
FeatureNameParser parser(_firstPhaseRankFeature);
if (parser.valid()) {
_firstPhaseRankFeature = parser.featureName();
_first_phase_resolver->addSeed(_firstPhaseRankFeature);
} else {
- LOG(warning, "invalid feature name for initial rank: '%s'",
- _firstPhaseRankFeature.c_str());
+ vespalib::string e = fmt("invalid feature name for initial rank: '%s'", _firstPhaseRankFeature.c_str());
+ _warnings.emplace_back(e);
_compileError = true;
}
}
@@ -187,8 +198,8 @@ RankSetup::compile()
_secondPhaseRankFeature = parser.featureName();
_second_phase_resolver->addSeed(_secondPhaseRankFeature);
} else {
- LOG(warning, "invalid feature name for final rank: '%s'",
- _secondPhaseRankFeature.c_str());
+ vespalib::string e = fmt("invalid feature name for final rank: '%s'", _secondPhaseRankFeature.c_str());
+ _warnings.emplace_back(e);
_compileError = true;
}
}
@@ -206,12 +217,12 @@ RankSetup::compile()
_dumpResolver->addSeed(feature);
}
_indexEnv.hintFeatureMotivation(IIndexEnvironment::RANK);
- _compileError |= !_first_phase_resolver->compile();
- _compileError |= !_second_phase_resolver->compile();
- _compileError |= !_match_resolver->compile();
- _compileError |= !_summary_resolver->compile();
+ compileAndCheckForErrors(*_first_phase_resolver);
+ compileAndCheckForErrors(*_second_phase_resolver);
+ compileAndCheckForErrors(*_match_resolver);
+ compileAndCheckForErrors(*_summary_resolver);
_indexEnv.hintFeatureMotivation(IIndexEnvironment::DUMP);
- _compileError |= !_dumpResolver->compile();
+ compileAndCheckForErrors(*_dumpResolver);
_compiled = true;
return !_compileError;
}
@@ -219,7 +230,7 @@ RankSetup::compile()
void
RankSetup::prepareSharedState(const IQueryEnvironment &queryEnv, IObjectStore &objectStore) const
{
- LOG_ASSERT(_compiled && !_compileError);
+ assert(_compiled && !_compileError);
for (const auto &spec : _first_phase_resolver->getExecutorSpecs()) {
spec.blueprint->prepareSharedState(queryEnv, objectStore);
}
@@ -234,4 +245,13 @@ RankSetup::prepareSharedState(const IQueryEnvironment &queryEnv, IObjectStore &o
}
}
+vespalib::string
+RankSetup::getJoinedWarnings() const {
+ vespalib::asciistream os;
+ for (const auto & m : _warnings) {
+ os << m << "\n";
+ }
+ return os.str();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/fef/ranksetup.h b/searchlib/src/vespa/searchlib/fef/ranksetup.h
index 6a2871827ab..1ad2fe0a77a 100644
--- a/searchlib/src/vespa/searchlib/fef/ranksetup.h
+++ b/searchlib/src/vespa/searchlib/fef/ranksetup.h
@@ -23,6 +23,7 @@ namespace search::fef {
class RankSetup
{
public:
+ using Warnings = BlueprintResolver::Warnings;
struct MutateOperation {
public:
MutateOperation() : MutateOperation("", "") {}
@@ -62,6 +63,7 @@ private:
std::vector<vespalib::string> _match_features;
std::vector<vespalib::string> _summaryFeatures;
std::vector<vespalib::string> _dumpFeatures;
+ Warnings _warnings;
StringStringMap _feature_rename_map;
bool _ignoreDefaultRankFeatures;
bool _compiled;
@@ -80,6 +82,8 @@ private:
MutateOperation _mutateOnFirstPhase;
MutateOperation _mutateOnSecondPhase;
MutateOperation _mutateOnSummary;
+
+ void compileAndCheckForErrors(BlueprintResolver &bp);
public:
RankSetup(const RankSetup &) = delete;
RankSetup &operator=(const RankSetup &) = delete;
@@ -438,6 +442,12 @@ public:
**/
bool compile();
+ /**
+ * Will return any accumulated warnings during compile
+ * @return joined string of warnings separated by newline
+ */
+ vespalib::string getJoinedWarnings() const;
+
// These functions create rank programs for different tasks. Note
// that the setup function must be called on rank programs for
// them to be ready to use. Also keep in mind that creating a rank
diff --git a/searchlib/src/vespa/searchlib/fef/verify_feature.cpp b/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
index 85d1daffd01..b012e95c8fc 100644
--- a/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
+++ b/searchlib/src/vespa/searchlib/fef/verify_feature.cpp
@@ -2,28 +2,31 @@
#include "verify_feature.h"
#include "blueprintresolver.h"
+#include <vespa/vespalib/util/stringfmt.h>
-#include <vespa/log/log.h>
-LOG_SETUP(".fef.verify_feature");
+using vespalib::make_string_short::fmt;
-namespace search {
-namespace fef {
+namespace search::fef {
bool verifyFeature(const BlueprintFactory &factory,
const IIndexEnvironment &indexEnv,
const std::string &featureName,
- const std::string &desc)
+ const std::string &desc,
+ std::vector<Message> & errors)
{
indexEnv.hintFeatureMotivation(IIndexEnvironment::VERIFY_SETUP);
BlueprintResolver resolver(factory, indexEnv);
resolver.addSeed(featureName);
bool result = resolver.compile();
if (!result) {
- LOG(error, "verification failed: %s (%s)",
- BlueprintResolver::describe_feature(featureName).c_str(), desc.c_str());
+ const BlueprintResolver::Warnings & warnings(resolver.getWarnings());
+ for (const auto & msg : warnings) {
+ errors.emplace_back(Level::WARNING, msg);
+ }
+ vespalib::string msg = fmt("verification failed: %s (%s)",BlueprintResolver::describe_feature(featureName).c_str(), desc.c_str());
+ errors.emplace_back(Level::ERROR, msg);
}
return result;
}
-} // namespace fef
-} // namespace search
+}
diff --git a/searchlib/src/vespa/searchlib/fef/verify_feature.h b/searchlib/src/vespa/searchlib/fef/verify_feature.h
index 5c84403fc8a..f6f3924616a 100644
--- a/searchlib/src/vespa/searchlib/fef/verify_feature.h
+++ b/searchlib/src/vespa/searchlib/fef/verify_feature.h
@@ -4,10 +4,11 @@
#include "blueprintfactory.h"
#include "iindexenvironment.h"
-#include <string>
-namespace search {
-namespace fef {
+namespace search::fef {
+
+enum class Level {INFO, WARNING, ERROR};
+using Message = std::pair<Level, vespalib::string>;
/**
* Verify whether a specific feature can be computed. If the feature
@@ -23,8 +24,7 @@ namespace fef {
bool verifyFeature(const BlueprintFactory &factory,
const IIndexEnvironment &indexEnv,
const std::string &featureName,
- const std::string &desc);
-
-} // namespace fef
-} // namespace search
+ const std::string &desc,
+ std::vector<Message> & errors);
+}
diff --git a/searchsummary/src/tests/juniper/auxTest.cpp b/searchsummary/src/tests/juniper/auxTest.cpp
index 15f5ad1749e..c42cf4ccd83 100644
--- a/searchsummary/src/tests/juniper/auxTest.cpp
+++ b/searchsummary/src/tests/juniper/auxTest.cpp
@@ -136,15 +136,14 @@ AuxTest::TestDoubleWidth()
juniper::QueryParser q("\xef\xbd\x93\xef\xbd\x8f\xef\xbd\x8e\xef\xbd\x99");
juniper::QueryHandle qh(q, nullptr, juniper.getModifier());
- juniper::Result* res = juniper::Analyse(&myConfig, &qh,
- input, 17, 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(myConfig, qh,
+ input, 17, 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
(void) sum;
// this should work
// _test(sum->Length() != 0);
- juniper::ReleaseResult(res);
}
@@ -175,17 +174,15 @@ AuxTest::TestPartialUTF8()
juniper::QueryParser q("ipod");
juniper::QueryHandle qh(q, nullptr, juniper.getModifier());
- juniper::Result* res = juniper::Analyse(&myConfig, &qh,
- input, inputSize, 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(myConfig, qh,
+ input, inputSize, 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
_test(sum->Length() != 0);
// check for partial/broken utf-8
_test(countBrokenUTF8(sum->Text(), sum->Length()) == 0);
-
- juniper::ReleaseResult(res);
}
void AuxTest::TestLargeBlockChinese()
@@ -215,11 +212,11 @@ void AuxTest::TestLargeBlockChinese()
juniper::QueryParser q("希望");
juniper::QueryHandle qh(q, nullptr, juniper.getModifier());
- juniper::Result* res = juniper::Analyse(&myConfig, &qh,
- input, inputSize, 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(myConfig, qh,
+ input, inputSize, 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
_test(sum->Length() != 0);
// check that the entire block of chinese data is not returned in the summary
@@ -227,8 +224,6 @@ void AuxTest::TestLargeBlockChinese()
// check for partial/broken utf-8
_test(countBrokenUTF8(sum->Text(), sum->Length()) == 0);
-
- juniper::ReleaseResult(res);
}
void AuxTest::TestExample()
@@ -241,17 +236,14 @@ void AuxTest::TestExample()
"&%#%&! cries the sleepy monkey and jumps down from the tree."
"the last token here is split across lines consumed";
int content_len = strlen(content);
- juniper::Result* res =
- juniper::Analyse(juniper::TestConfig,
- &qh,
- content, content_len,
- 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(*juniper::TestConfig, qh,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
res->Scan();
Matcher& m = *res->_matcher;
_test(m.TotalMatchCnt(0) == 2 && m.ExactMatchCnt(0) == 0);
- juniper::ReleaseResult(res);
}
@@ -410,8 +402,8 @@ void AuxTest::TestUTF8context()
s.append(char_from_u8(u8" beste forekomst av s\u00f8ket med s\u00f8kemotor til brukeren blir det enda bedre. "));
s.append(char_from_u8(u8"Hvis bare UTF8-kodingen virker som den skal for tegn som tar mer enn \u00e9n byte."));
- juniper::Result* res = juniper::Analyse(juniper::TestConfig, &qh, s.c_str(), s.size(), 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(*juniper::TestConfig, qh, s.c_str(), s.size(), 0, 0, 0);
+ _test(static_cast<bool>(res));
size_t charsize;
Matcher& m = *res->_matcher;
@@ -454,7 +446,6 @@ void AuxTest::TestUTF8context()
test_dump(s.c_str(), s.size());
m.dump_statistics();
}
- juniper::ReleaseResult(res);
}
@@ -491,10 +482,10 @@ void AuxTest::TestJapanese()
const char* content = testjap[i].text;
int content_len = strlen(content);
- juniper::Result* res = juniper::Analyse(juniper::TestConfig, &qh,
- content, content_len,
- 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(*juniper::TestConfig, qh,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
size_t charsize;
Matcher& m = *res->_matcher;
@@ -545,7 +536,6 @@ void AuxTest::TestJapanese()
default:
break;
}
- juniper::ReleaseResult(res);
DeleteSummaryDesc(sumdesc);
DeleteSummaryConfig(_sumconf);
}
@@ -582,15 +572,14 @@ void AuxTest::TestStartHits()
" In fact it must be much longer. And then som more text at the end. But this text at "
"the end must be much longer than this to trigger the case";
int content_len = strlen(content);
- juniper::Result* res = juniper::Analyse(juniper::TestConfig, &qh,
- content, content_len,
- 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(*juniper::TestConfig, qh,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
(void) sum;
// TODO: ReEnable _test(sum->Length() != 0);
- juniper::ReleaseResult(res);
}
@@ -607,14 +596,13 @@ void AuxTest::TestEndHit()
"surround_len bytes closer than good towardstheend�����������������������������������";
size_t content_len = strlen(content) - 55;
- juniper::Result* res = juniper::Analyse(juniper::TestConfig, &qh,
- content, content_len,
- 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(*juniper::TestConfig, qh,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
_test(sum->Length() != 0);
- juniper::ReleaseResult(res);
}
void AuxTest::TestJuniperStack()
@@ -880,14 +868,13 @@ AuxTest::TestWhiteSpacePreserved()
juniper::QueryParser q("best");
juniper::QueryHandle qh(q, nullptr, juniper.getModifier());
- juniper::Result* res = juniper::Analyse(&myConfig, &qh, input.c_str(), input.size(), 0, 0, 0);
- _test(res != nullptr);
+ auto res = juniper::Analyse(myConfig, qh, input.c_str(), input.size(), 0, 0, 0);
+ _test(static_cast<bool>(res));
- juniper::Summary* sum = juniper::GetTeaser(res, nullptr);
+ juniper::Summary* sum = juniper::GetTeaser(*res, nullptr);
vespalib::string expected = "<hi>best</hi> of \nmetallica";
vespalib::string actual(sum->Text(), sum->Length());
_test(actual == expected);
- juniper::ReleaseResult(res);
}
void AuxTest::Run(MethodContainer::iterator &itr) {
diff --git a/searchsummary/src/tests/juniper/matchobjectTest.cpp b/searchsummary/src/tests/juniper/matchobjectTest.cpp
index 07e3cf84767..d8b7724d8a5 100644
--- a/searchsummary/src/tests/juniper/matchobjectTest.cpp
+++ b/searchsummary/src/tests/juniper/matchobjectTest.cpp
@@ -28,10 +28,10 @@ void MatchObjectTest::testTerm() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig, &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig, q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. This calls accept several times
res->Scan();
@@ -42,51 +42,39 @@ void MatchObjectTest::testTerm() {
_test(ms.size() == 2);
- delete res;
// printf("%d %d\n", m.TotalHits(),ms.size());
TestQuery q1("t*t");
TestQuery q2("*ea*");
TestQuery q3("*d");
TestQuery q4("*word");
- Result* r1 = juniper::Analyse(juniper::TestConfig, &q1._qhandle, content, content_len, 0, 0, 0);
- Result* r2 = juniper::Analyse(juniper::TestConfig, &q2._qhandle, content, content_len, 0, 0, 0);
- Result* r3 = juniper::Analyse(juniper::TestConfig, &q3._qhandle, content, content_len, 0, 0, 0);
- Result* r4 = juniper::Analyse(juniper::TestConfig, &q4._qhandle, content, content_len, 0, 0, 0);
- if (r1 != 0)
- {
+ auto r1 = juniper::Analyse(*juniper::TestConfig, q1._qhandle, content, content_len, 0, 0, 0);
+ auto r2 = juniper::Analyse(*juniper::TestConfig, q2._qhandle, content, content_len, 0, 0, 0);
+ auto r3 = juniper::Analyse(*juniper::TestConfig, q3._qhandle, content, content_len, 0, 0, 0);
+ auto r4 = juniper::Analyse(*juniper::TestConfig, q4._qhandle, content, content_len, 0, 0, 0);
+ _test(static_cast<bool>(r1));
+ if (r1) {
r1->Scan();
_test(r1->_matcher->TotalHits() == 1);
- delete r1;
}
- else
- _test(r1 != 0);
-
- if (r2 != 0)
- {
+ _test(static_cast<bool>(r2));
+ if (r2) {
r2->Scan();
_test(r2->_matcher->TotalHits() == 2);
- delete r2;
}
- else
- _test(r2 != 0);
- if (r3 != 0)
- {
+ if (r3) {
r3->Scan();
_test(r3->_matcher->TotalHits() == 2);
- delete r3;
+ } else {
+ _test(static_cast<bool>(r3));
}
- else
- _test(r3 != 0);
- if (r4 != 0)
- {
+ if (r4) {
r4->Scan();
_test_equal(r4->_matcher->TotalHits(), 2);
- delete r4;
+ } else {
+ _test(static_cast<bool>(r4));
}
- else
- _test(r4 != 0);
}
/**
@@ -98,7 +86,7 @@ void MatchObjectTest::testMatch() {
juniper::QueryHandle qh(p, NULL, juniper::_Juniper->getModifier());
MatchObject* mo = qh.MatchObj(0);
- juniper::Result res(juniper::TestConfig, &qh, "", 0, 0);
+ juniper::Result res(*juniper::TestConfig, qh, "", 0, 0);
unsigned opts = 0;
match_iterator mi(mo, &res);
ucs4_t ucs4_str[10];
@@ -140,7 +128,7 @@ void MatchObjectTest::testMatch() {
"extremelylongwordhit,extremelylongwordhits,extremelylongwordhit,"
"extremelylongwordhit))");
QueryHandle& qh1(q._qhandle);
- juniper::Result res1(juniper::TestConfig, &qh1,
+ juniper::Result res1(*juniper::TestConfig, qh1,
doc.c_str(), doc.size(), 0);
juniper::Summary* sum = res1.GetTeaser(NULL);
std::string s(sum->Text());
@@ -165,7 +153,7 @@ void MatchObjectTest::testMatchAnnotated() {
" stuff";
TestQuery q("AND(big,buy)");
QueryHandle &qh1(q._qhandle);
- juniper::Result res1(juniper::TestConfig, &qh1,
+ juniper::Result res1(*juniper::TestConfig, qh1,
doc, strlen(doc), 0);
juniper::Summary *sum = res1.GetTeaser(NULL);
std::string s(sum->Text());
@@ -205,7 +193,7 @@ void MatchObjectTest::testLangid()
std::string doc("see if we can match b or c somewhere in this a3 doc. "
"Note that we should not match b1 or c1 or a somewhere..");
- juniper::Result res(juniper::TestConfig, &qh, doc.c_str(), doc.size(),0);
+ juniper::Result res(*juniper::TestConfig, qh, doc.c_str(), doc.size(),0);
juniper::Summary* sum = res.GetTeaser(NULL);
std::string s(sum->Text());
@@ -218,7 +206,7 @@ void MatchObjectTest::testLangid()
// Do another test with the same query handle (testing reuse of qh with rewriters)
std::string doc("Try to run this on another doc just to see if b or c still can be"
" matched with the same query handle");
- juniper::Result res(juniper::TestConfig, &qh,
+ juniper::Result res(*juniper::TestConfig, qh,
doc.c_str(), doc.size(), 0);
juniper::Summary* sum = res.GetTeaser(NULL);
@@ -247,7 +235,7 @@ void MatchObjectTest::testCombined()
{
std::string doc("see if we can match a3 or c somewhere in this b doc. "
"Note that we should not match b1 or c1 or a somewhere..");
- juniper::Result res(juniper::TestConfig, &qh, doc.c_str(), doc.size(), 0);
+ juniper::Result res(*juniper::TestConfig, qh, doc.c_str(), doc.size(), 0);
juniper::Summary* sum = res.GetTeaser(NULL);
std::string s(sum->Text());
diff --git a/searchsummary/src/tests/juniper/mcandTest.cpp b/searchsummary/src/tests/juniper/mcandTest.cpp
index 5a465275a80..46bd4a5196f 100644
--- a/searchsummary/src/tests/juniper/mcandTest.cpp
+++ b/searchsummary/src/tests/juniper/mcandTest.cpp
@@ -41,24 +41,23 @@ void MatchCandidateTest::testLog() {
TestQuery q("");
std::string content("Here we go hepp and then some words away hoi some silly text here");
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content.c_str(), content.size(),
- 0, 0, 0);
- _test(res); // We get a result handle
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content.c_str(), content.size(),
+ 0, 0, 0);
+ _test(static_cast<bool>(res)); // We get a result handle
_test(!res->_mo); // but it is empty
- juniper::Summary* sum = juniper::GetTeaser(res);
+ juniper::Summary* sum = juniper::GetTeaser(*res);
std::string s(sum->Text());
_test_equal(s, std::string(""));
- long relevance = juniper::GetRelevancy(res);
+ long relevance = juniper::GetRelevancy(*res);
_test_equal(relevance, PROXIMITYBOOST_NOCONSTRAINT_OFFSET);
- sum = juniper::GetLog(res);
+ sum = juniper::GetLog(*res);
s = sum->Text();
_test_equal(s, std::string(""));
- juniper::ReleaseResult(res);
}
@@ -70,56 +69,52 @@ void MatchCandidateTest::testDump() {
{
TestQuery q("NEAR/1(hepp,hoi)");
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content.c_str(), content.size(),
- 0, 0, 0);
- _test(res != NULL);
- long relevance = juniper::GetRelevancy(res);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content.c_str(), content.size(),
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
+ long relevance = juniper::GetRelevancy(*res);
// zero value since there are no hits and constraints are enabled..
_test_equal(relevance, 0);
- juniper::ReleaseResult(res);
}
{
TestQuery q("OR(NEAR/1(hepp,hoi),bananas)");
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content.c_str(), content.size(),
- 0, 0, 0);
- _test(res != NULL);
- long relevance = juniper::GetRelevancy(res);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content.c_str(), content.size(),
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
+ long relevance = juniper::GetRelevancy(*res);
// Check that X_CONSTR propagates as intended
_test_equal(relevance, 0);
- juniper::ReleaseResult(res);
}
{
TestQuery q("PHRASE(hepp,hoi)");
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content.c_str(), content.size(),
- 0, 0, 0);
- _test(res != NULL);
- long relevance = juniper::GetRelevancy(res);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content.c_str(), content.size(),
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
+ long relevance = juniper::GetRelevancy(*res);
// constant value since there are no hits but this is
// also not a constrained search..
_test_equal(relevance, PROXIMITYBOOST_NOCONSTRAINT_OFFSET);
- juniper::ReleaseResult(res);
}
{
TestQuery q("AND(hepp,hoi)");
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content.c_str(), content.size(),
- 0, 0, 0);
- _test(res != NULL);
- long relevance = juniper::GetRelevancy(res);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content.c_str(), content.size(),
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
+ long relevance = juniper::GetRelevancy(*res);
// Relevance may change, but nice to discover such changes..
// The important is that we get a nonzero value here as a hit
_test_equal(relevance, 4470);
- juniper::ReleaseResult(res);
}
}
@@ -135,11 +130,11 @@ void MatchCandidateTest::testorder() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. Scan calls accept several times
res->Scan();
@@ -150,7 +145,6 @@ void MatchCandidateTest::testorder() {
match_candidate_set& ms = m.OrderedMatchSet();
_test(ms.size() == 1);
- juniper::ReleaseResult(res);
}
@@ -165,11 +159,11 @@ void MatchCandidateTest::testMatches_limit() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. This calls accept several times
res->Scan();
@@ -182,11 +176,10 @@ void MatchCandidateTest::testMatches_limit() {
_test(ms.size() == 2); // The first (complete) match and the second starting at "test"
// Check if we get the correct teaser as well..
- juniper::Summary* sum = juniper::GetTeaser(res);
+ juniper::Summary* sum = juniper::GetTeaser(*res);
_test(strcmp(sum->Text(),
"This is a simple text where a <b>phrase</b> <b>match</b> can be found not"
" quite adjacent to a <b>test</b> <b>word</b>") == 0);
- juniper::ReleaseResult(res);
}
@@ -200,11 +193,11 @@ void MatchCandidateTest::testAccept() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. This calls accept several times
res->Scan();
@@ -218,7 +211,6 @@ void MatchCandidateTest::testAccept() {
_test(ms.size() > 0);
if (!ms.size()) {
- juniper::ReleaseResult(res);
return; // No point in continuing..
}
@@ -234,10 +226,9 @@ void MatchCandidateTest::testAccept() {
_test(mc._klist.size() == 2); // Two occurrence elements in list
// Just for the sake of it, verify that we get a proper teaser out of this also..
- juniper::Summary* sum = juniper::GetTeaser(res);
+ juniper::Summary* sum = juniper::GetTeaser(*res);
_test(strcmp(sum->Text(),
"This is a <b>simple</b> <b>test</b> where we should get a perfect match") == 0);
- juniper::ReleaseResult(res);
}
@@ -260,11 +251,11 @@ void MatchCandidateTest::testMake_keylist() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. This calls accept several times
res->Scan();
@@ -275,8 +266,6 @@ void MatchCandidateTest::testMake_keylist() {
match_candidate_set& ms = m.OrderedMatchSet();
_test_equal(static_cast<size_t>(ms.size()), 6u);
-
- juniper::ReleaseResult(res);
}
@@ -291,11 +280,11 @@ void MatchCandidateTest::testAdd_to_keylist() {
size_t content_len = strlen(content);
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Do the scanning manually. This calls accept several times
res->Scan();
@@ -308,13 +297,12 @@ void MatchCandidateTest::testAdd_to_keylist() {
_test_equal(static_cast<size_t>(ms.size()), 1u); // Single result
// Bug triggered when result is fetched..
- juniper::Summary* sum = juniper::GetTeaser(res);
+ juniper::Summary* sum = juniper::GetTeaser(*res);
std::string s(sum->Text());
_test_equal(s,
"connect truende. <b>phr1</b> <b>phr2</b> www www www <b>phr3</b>"
" <b>phr4</b> acuicola 8844");
- juniper::ReleaseResult(res);
}
@@ -331,11 +319,11 @@ void MatchCandidateTest::testLength() {
TestQuery q("NEAR/4(pattern,NEAR/1(simple,with),NEAR/2(simple,adjacent))");
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig, &q._qhandle,
- content, content_len,
- 0, 0, 0);
+ auto res = juniper::Analyse(*juniper::TestConfig, q._qhandle,
+ content, content_len,
+ 0, 0, 0);
- juniper::Summary* sum = juniper::GetTeaser(res);
+ juniper::Summary* sum = juniper::GetTeaser(*res);
Matcher& m = *res->_matcher;
match_candidate_set& ms = m.OrderedMatchSet();
_test_equal(static_cast<size_t>(ms.size()), 1u);
@@ -345,7 +333,6 @@ void MatchCandidateTest::testLength() {
"this <b>simple</b> text <b>with</b> <b>adjacent</b> words of "
"a certain <b>pattern</b> must be matched according to specific"
" rules to be detailed in this test.");
- juniper::ReleaseResult(res);
}
{
@@ -353,17 +340,16 @@ void MatchCandidateTest::testLength() {
TestQuery q("ONEAR/4(pattern,NEAR/1(simple,with),NEAR/2(simple,adjacent))");
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle
- ,content, content_len,
- 0, 0, 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle
+ ,content, content_len,
+ 0, 0, 0);
res->Scan();
Matcher& m = *res->_matcher;
match_candidate_set& ms = m.OrderedMatchSet();
_test_equal(static_cast<size_t>(ms.size()), 0u);
- juniper::ReleaseResult(res);
}
{
@@ -372,16 +358,14 @@ void MatchCandidateTest::testLength() {
TestQuery q("NEAR/4(pattern,NEAR/1(simple,with),NEAR/1(simple,adjacent))");
// Fetch a result descriptor:
- Result* res = juniper::Analyse(juniper::TestConfig, &q._qhandle,
- content, content_len,
- 0, 0, 0);
+ auto res = juniper::Analyse(*juniper::TestConfig, q._qhandle,
+ content, content_len,
+ 0, 0, 0);
res->Scan();
Matcher& m = *res->_matcher;
match_candidate_set& ms = m.OrderedMatchSet();
_test_equal(static_cast<size_t>(ms.size()), 0u);
-
- juniper::ReleaseResult(res);
}
}
@@ -416,11 +400,11 @@ void MatchCandidateTest::requireThatMaxNumberOfMatchCandidatesCanBeControlled()
const char *content = "re re re re foo re re re re bar re re re re foo re re re re bar";
size_t content_len = strlen(content);
- Result *res = juniper::Analyse(juniper::TestConfig,
- &q._qhandle,
- content, content_len,
- 0, 0, 0);
- _test(res != 0);
+ auto res = juniper::Analyse(*juniper::TestConfig,
+ q._qhandle,
+ content, content_len,
+ 0, 0, 0);
+ _test(static_cast<bool>(res));
// Deflect tokens to my processor
Matcher &m = *res->_matcher;
@@ -435,8 +419,6 @@ void MatchCandidateTest::requireThatMaxNumberOfMatchCandidatesCanBeControlled()
_test_equal(m.TotalHits(), 20);
match_candidate_set& mcs = m.OrderedMatchSet();
_test_equal(static_cast<size_t>(mcs.size()), 2u);
-
- juniper::ReleaseResult(res);
}
diff --git a/searchsummary/src/vespa/juniper/Matcher.cpp b/searchsummary/src/vespa/juniper/Matcher.cpp
index e286068038b..32f966f4571 100644
--- a/searchsummary/src/vespa/juniper/Matcher.cpp
+++ b/searchsummary/src/vespa/juniper/Matcher.cpp
@@ -38,7 +38,7 @@ Matcher::Matcher(Result* result) :
_log_text("")
{
_occ.reserve(KEY_OCC_RESERVED);
- DocsumParams& dsp = _result->_config->_docsumparams;
+ const DocsumParams& dsp = _result->_config->_docsumparams;
_winsize = _result->WinSize();
_winsizeFallback = static_cast<size_t>(_result->WinSizeFallbackMultiplier() * _winsize);
_max_match_candidates = _result->MaxMatchCandidates();
@@ -125,7 +125,6 @@ void Matcher::reset_matches()
void Matcher::reset_occurrences()
{
- delete_all(_occ);
_occ.clear();
}
@@ -162,10 +161,11 @@ bool Matcher::add_occurrence(off_t pos, off_t tpos, size_t len)
LOG(spam, "Match: %s(%" PRId64 ")", mexp->term(), static_cast<int64_t>(tpos));
// Add new occurrence to sequence of all occurrences
- key_occ_ptr k = new key_occ(mexp->term(), pos, tpos, len);
- if (!k) return false;
+ auto smart_k = std::make_unique<key_occ>(mexp->term(), pos, tpos, len);
+ if (!smart_k) return false;
- _occ.push_back(k);
+ auto k = smart_k.get();
+ _occ.emplace_back(std::move(smart_k));
if (!(_need_complete_cnt > 0)) {
size_t nodeno;
diff --git a/searchsummary/src/vespa/juniper/foreach_utils.h b/searchsummary/src/vespa/juniper/foreach_utils.h
deleted file mode 100644
index ebbf1f41049..00000000000
--- a/searchsummary/src/vespa/juniper/foreach_utils.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-#include <algorithm>
-
-/** \if utils
- * A simple general deleter object to be passed to for instance std::for_each
- * to delete pointer referenced objects
- * in STL containers.
- *
- */
-
-struct Deleter
-{
- template <typename T>
- void operator()(T* t) const
- {
- delete t;
- }
-};
-
-/* \def Handy macro to delete all pointer objects in a container
- * (using \a Deleter)
- */
-
-#define delete_all(container) \
- std::for_each(container.begin(), container.end(), Deleter())
-
-
-#define FunctionObj(name, func) \
- struct name \
- { \
- template <typename T> \
- void operator()(T* t) \
- { \
- t->func(); \
- } \
- }
-
-
-#define for_all(container, obj) \
- std::for_each(container.begin(), container.end(), obj())
-
-/** \endif (utils) */
-
diff --git a/searchsummary/src/vespa/juniper/juniperdebug.h b/searchsummary/src/vespa/juniper/juniperdebug.h
index cf6a3c971f8..d475134dae3 100644
--- a/searchsummary/src/vespa/juniper/juniperdebug.h
+++ b/searchsummary/src/vespa/juniper/juniperdebug.h
@@ -3,6 +3,7 @@
// Include something from STL so that _STLPORT_VERSION gets defined if appropriate
#include <string>
+#include <algorithm>
/* Juniper debug macro */
@@ -43,14 +44,9 @@ extern unsigned debug_level;
#endif
-
-#include "foreach_utils.h"
-
-FunctionObj(DoDump, dump);
-
template <class _container>
void dump_list(_container& __c)
{
- for_all(__c, DoDump);
+ std::for_each(__c.begin(), __c.end(), [](auto& elem) { elem->dump(); });
}
diff --git a/searchsummary/src/vespa/juniper/juniperparams.cpp b/searchsummary/src/vespa/juniper/juniperparams.cpp
index 4f25b2446ad..2ee0f3c31f6 100644
--- a/searchsummary/src/vespa/juniper/juniperparams.cpp
+++ b/searchsummary/src/vespa/juniper/juniperparams.cpp
@@ -108,18 +108,12 @@ MatcherParams& MatcherParams::SetWordFolder(Fast_WordFolder* wordfolder)
return *this;
}
-Fast_WordFolder* MatcherParams::WordFolder() { return _wordfolder; }
-
-
MatcherParams& MatcherParams::SetProximityFactor(double factor)
{
_proximity_factor = factor;
return *this;
}
-double MatcherParams::ProximityFactor() { return _proximity_factor; }
-
-
bool operator==(MatcherParams& mp1, MatcherParams& mp2)
{
return memcmp(&mp1, &mp2, sizeof(MatcherParams)) == 0;
diff --git a/searchsummary/src/vespa/juniper/juniperparams.h b/searchsummary/src/vespa/juniper/juniperparams.h
index f4f17779f2d..415c254b3f0 100644
--- a/searchsummary/src/vespa/juniper/juniperparams.h
+++ b/searchsummary/src/vespa/juniper/juniperparams.h
@@ -48,8 +48,10 @@ class MatcherParams
{
public:
MatcherParams();
- MatcherParams(MatcherParams &) = delete;
- MatcherParams &operator=(MatcherParams &) = delete;
+ MatcherParams(const MatcherParams&) = delete;
+ MatcherParams(MatcherParams&&) = delete;
+ MatcherParams &operator=(const MatcherParams&) = delete;
+ MatcherParams &operator=(MatcherParams&&) = delete;
MatcherParams& SetMatchWindowSize(size_t winsize);
size_t MatchWindowSize() const;
@@ -66,10 +68,10 @@ public:
size_t StemMaxExtend() const;
MatcherParams& SetWordFolder(Fast_WordFolder* wordfolder);
- Fast_WordFolder* WordFolder();
+ const Fast_WordFolder* WordFolder() const noexcept { return _wordfolder; }
MatcherParams& SetProximityFactor(double factor);
- double ProximityFactor();
+ double ProximityFactor() const noexcept { return _proximity_factor; };
private:
size_t _match_winsize;
diff --git a/searchsummary/src/vespa/juniper/keyocc.h b/searchsummary/src/vespa/juniper/keyocc.h
index 8c79e51e3a3..f211b5bdf61 100644
--- a/searchsummary/src/vespa/juniper/keyocc.h
+++ b/searchsummary/src/vespa/juniper/keyocc.h
@@ -3,9 +3,9 @@
#include "matchelem.h"
#include "querynode.h"
+#include <memory>
-typedef key_occ* key_occ_ptr;
-typedef std::vector<key_occ_ptr> key_occ_vector;
+using key_occ_vector = std::vector<std::unique_ptr<key_occ>>;
class key_occ : public MatchElement
{
diff --git a/searchsummary/src/vespa/juniper/querymodifier.cpp b/searchsummary/src/vespa/juniper/querymodifier.cpp
index 797617b605a..da751c52a92 100644
--- a/searchsummary/src/vespa/juniper/querymodifier.cpp
+++ b/searchsummary/src/vespa/juniper/querymodifier.cpp
@@ -2,7 +2,6 @@
#include "juniperdebug.h"
#include "querymodifier.h"
-#include "foreach_utils.h"
#include "querynode.h"
#include <vespa/log/log.h>
diff --git a/searchsummary/src/vespa/juniper/result.cpp b/searchsummary/src/vespa/juniper/result.cpp
index 15ad9aa2a98..fddc5d65c86 100644
--- a/searchsummary/src/vespa/juniper/result.cpp
+++ b/searchsummary/src/vespa/juniper/result.cpp
@@ -27,14 +27,14 @@ public:
};
-Result::Result(Config* config, QueryHandle* qhandle,
+Result::Result(const Config& config, QueryHandle& qhandle,
const char* docsum, size_t docsum_len, uint32_t langid) :
- _qhandle(qhandle),
- _mo(qhandle->MatchObj(langid)),
+ _qhandle(&qhandle),
+ _mo(qhandle.MatchObj(langid)),
_docsum(docsum),
_docsum_len(docsum_len),
_langid(langid),
- _config(config),
+ _config(&config),
_matcher(),
_tokenizer(),
_summaries(),
@@ -50,8 +50,8 @@ Result::Result(Config* config, QueryHandle* qhandle,
{
if (!_mo) return; // The empty result..
- MatcherParams& mp = _config->_matcherparams;
- Fast_WordFolder* wordfolder = mp.WordFolder();
+ const MatcherParams& mp = _config->_matcherparams;
+ const Fast_WordFolder* wordfolder = mp.WordFolder();
if (_qhandle->_stem_min < 0)
_stem_min = mp.StemMinLength();
@@ -87,8 +87,8 @@ Result::Result(Config* config, QueryHandle* qhandle,
_registry = std::make_unique<SpecialTokenRegistry>(_matcher->getQuery());
- if (qhandle->_log_mask)
- _matcher->set_log(qhandle->_log_mask);
+ if (qhandle._log_mask)
+ _matcher->set_log(qhandle._log_mask);
_tokenizer->SetSuccessor(_matcher.get());
if (!_registry->getSpecialTokens().empty()) {
@@ -98,7 +98,6 @@ Result::Result(Config* config, QueryHandle* qhandle,
Result::~Result()
{
- delete_all(_summaries);
}
@@ -121,7 +120,7 @@ Summary* Result::GetTeaser(const Config* alt_config)
_dynsum_len = dsp.Length();
else
_dynsum_len = _qhandle->_dynsum_len;
- SummaryImpl *sum = NULL;
+ std::unique_ptr<SummaryImpl> sum;
// Avoid overhead when being called with an empty stack
if (_mo && _mo->Query()) {
Scan();
@@ -139,13 +138,13 @@ Summary* Result::GetTeaser(const Config* alt_config)
if (sdesc) {
size_t char_size;
- sum = new SummaryImpl(BuildSummary(_docsum, _docsum_len, sdesc, cfg->_sumconf, char_size));
+ sum = std::make_unique<SummaryImpl>(BuildSummary(_docsum, _docsum_len, sdesc, cfg->_sumconf, char_size));
DeleteSummaryDesc(sdesc);
}
}
- if (sum == NULL) {
- sum = new SummaryImpl();
+ if (!sum) {
+ sum = std::make_unique<SummaryImpl>();
}
if (sum->_text.empty() && dsp.Fallback() == DocsumParams::FALLBACK_PREFIX)
@@ -157,7 +156,7 @@ Summary* Result::GetTeaser(const Config* alt_config)
const char *src_end = _docsum + _docsum_len;
ucs4_t *dst = buf;
ucs4_t *dst_end = dst + TOKEN_DSTLEN;
- Fast_WordFolder *folder = _config->_matcherparams.WordFolder();
+ const Fast_WordFolder *folder = _config->_matcherparams.WordFolder();
text.reserve(_dynsum_len*2);
if (src_end - src <= _dynsum_len) {
@@ -186,25 +185,26 @@ Summary* Result::GetTeaser(const Config* alt_config)
}
sum->_text = std::string(&text[0], text.size());
}
- _summaries.push_back(sum);
- return sum;
+ _summaries.emplace_back(std::move(sum));
+ return _summaries.back().get();
}
Summary* Result::GetLog()
{
// Avoid overhead when being called with an empty stack
- Summary* sum = NULL;
+ std::unique_ptr<Summary> sum;
if (_mo && _mo->Query())
{
LOG(debug, "juniper::GetLog");
Scan();
- sum = new SummaryImpl(_matcher->GetLog());
+ sum = std::make_unique<SummaryImpl>(_matcher->GetLog());
}
- else
- sum = new SummaryImpl();
- _summaries.push_back(sum);
- return sum;
+ else {
+ sum = std::make_unique<SummaryImpl>();
+ }
+ _summaries.emplace_back(std::move(sum));
+ return _summaries.back().get();
}
diff --git a/searchsummary/src/vespa/juniper/result.h b/searchsummary/src/vespa/juniper/result.h
index f0dcf3d4335..dcbb89fb1dc 100644
--- a/searchsummary/src/vespa/juniper/result.h
+++ b/searchsummary/src/vespa/juniper/result.h
@@ -14,7 +14,7 @@ namespace juniper
class Result
{
public:
- Result(Config* config, QueryHandle* qhandle,
+ Result(const Config& config, QueryHandle& qhandle,
const char* docsum, size_t docsum_len, uint32_t langid);
~Result();
@@ -42,12 +42,12 @@ public:
const char* _docsum;
size_t _docsum_len;
uint32_t _langid;
- Config* _config;
+ const Config* _config;
std::unique_ptr<Matcher> _matcher;
std::unique_ptr<SpecialTokenRegistry> _registry;
std::unique_ptr<JuniperTokenizer> _tokenizer;
private:
- std::vector<Summary*> _summaries; // Active summaries for this result
+ std::vector<std::unique_ptr<Summary>> _summaries; // Active summaries for this result
bool _scan_done; // State of the result - is text scan done?
/* Option storage */
diff --git a/searchsummary/src/vespa/juniper/rpinterface.cpp b/searchsummary/src/vespa/juniper/rpinterface.cpp
index f9e91073a9b..de4b4cd3ef0 100644
--- a/searchsummary/src/vespa/juniper/rpinterface.cpp
+++ b/searchsummary/src/vespa/juniper/rpinterface.cpp
@@ -2,7 +2,6 @@
#include "rpinterface.h"
#include "juniperparams.h"
-#include "foreach_utils.h"
#include "queryvisitor.h"
#include "queryhandle.h"
#include "propreader.h"
@@ -79,9 +78,9 @@ std::unique_ptr<Config> Juniper::CreateConfig(const char* config_name)
return std::unique_ptr<Config>(new Config(config_name, *this));
}
-QueryHandle* Juniper::CreateQueryHandle(const IQuery& fquery, const char* juniperoptions)
+std::unique_ptr<QueryHandle> Juniper::CreateQueryHandle(const IQuery& fquery, const char* juniperoptions)
{
- return new QueryHandle(fquery, juniperoptions, *_modifier);
+ return std::make_unique<QueryHandle>(fquery, juniperoptions, *_modifier);
}
void Juniper::AddRewriter(const char* index_name, IRewriter* rewriter, bool for_query, bool for_document)
@@ -95,44 +94,29 @@ void Juniper::FlushRewriters()
}
-void ReleaseQueryHandle(QueryHandle*& handle)
-{
- delete handle;
- handle = NULL;
-}
-
-
-Result* Analyse(const Config* config, QueryHandle* qhandle,
+std::unique_ptr<Result> Analyse(const Config& config, QueryHandle& qhandle,
const char* docsum, size_t docsum_len,
uint32_t docid, uint32_t /* inputfield_id */,
uint32_t langid)
{
LOG(debug, "juniper::Analyse(): docId(%u), docsumLen(%zu), docsum(%s), langId(%u)",
docid, docsum_len, docsum, langid);
- Result* res = new Result(const_cast<Config*>(config), qhandle, docsum, docsum_len, langid);
- return res;
-}
-
-long GetRelevancy(Result* result_handle)
-{
- return result_handle->GetRelevancy();
+ return std::make_unique<Result>(config, qhandle, docsum, docsum_len, langid);
}
-Summary* GetTeaser(Result* result_handle, const Config* alt_config)
+long GetRelevancy(Result& result_handle)
{
- return result_handle->GetTeaser(alt_config);
+ return result_handle.GetRelevancy();
}
-Summary* GetLog(Result* result_handle)
+Summary* GetTeaser(Result& result_handle, const Config* alt_config)
{
- return result_handle->GetLog();
+ return result_handle.GetTeaser(alt_config);
}
-void ReleaseResult(Result*& result_handle)
+Summary* GetLog(Result& result_handle)
{
- LOG(debug, "juniper::ReleaseResult");
- delete result_handle;
- result_handle = NULL;
+ return result_handle.GetLog();
}
} // end namespace juniper
diff --git a/searchsummary/src/vespa/juniper/rpinterface.h b/searchsummary/src/vespa/juniper/rpinterface.h
index 6cda324ae5c..d4ef5c5ebed 100644
--- a/searchsummary/src/vespa/juniper/rpinterface.h
+++ b/searchsummary/src/vespa/juniper/rpinterface.h
@@ -120,9 +120,9 @@ public:
* behaviour such as user customization of teaser parameters, selectively
* enabling of Juniper debugging/tracing features and to support Juniper extensions
* to the query language.
- * @return An allocated handle to be subsequently released by ReleaseQueryHandle()
+ * @return A unique pointer to a QueryHandle.
*/
- QueryHandle* CreateQueryHandle(const IQuery& query, const char* juniperoptions);
+ std::unique_ptr<QueryHandle> CreateQueryHandle(const IQuery& query, const char* juniperoptions);
/** Add an rewriter for all terms that are prefixed with the given index.
* When Juniper encounter a term in the query tagged with this index,
@@ -150,11 +150,6 @@ private:
*/
bool AnalyseCompatible(Config* conf1, Config* conf2);
-/** Release a QueryHandle as previously allocated by CreateQueryHandle.
- * @param handle The QueryHandle object to release
- */
-void ReleaseQueryHandle(QueryHandle*& handle);
-
/** Perform initial content analysis on a query/content pair.
* Note that the content may either be a simple UTF-8 encoded string or a
* more advanced representation including document structure elements, as provided
@@ -170,10 +165,9 @@ void ReleaseQueryHandle(QueryHandle*& handle);
within the document that contains the provided document summary.
* @param langid A unique 32 bit id representing the language which
this document summary is to be analysed in context of.
- * @return A pointer to an allocated handle to be used in subsequent specific result
- * requests (must later be released with ReleaseResult())
+ * @return A unique pointer to a Result
*/
-Result* Analyse(const Config* config, QueryHandle* query,
+std::unique_ptr<Result> Analyse(const Config& config, QueryHandle& query,
const char* docsum, size_t docsum_len,
uint32_t docid, uint32_t inputfield_id,
uint32_t langid);
@@ -182,17 +176,16 @@ Result* Analyse(const Config* config, QueryHandle* query,
* @param result_handle The result to retrieve from
* @return The relevancy (proximitymetric) of the processed content.
*/
-long GetRelevancy(Result* result_handle);
+long GetRelevancy(Result& result_handle);
/** Generate a teaser based on the provided analysis result
* @param result_handle a handle obtained by a previous call to Analyse
* @param alt_config An optional alternate config to use for this teaser generation
* The purpose of alt_config is to allow generation of multiple teasers
* based on the same content and analysis.
- * @return The generated Teaser object. This object is valid until ReleaseResult
- * is called for result_handle
+ * @return The generated Teaser object. This object is valid until result_handle is deleted.
*/
-Summary* GetTeaser(Result* result_handle, const Config* alt_config = NULL);
+Summary* GetTeaser(Result& result_handle, const Config* alt_config = NULL);
/** Retrieve log information based on the previous calls to this result handle.
* Note that for the log to be complete, the juniper log override entry in
@@ -200,15 +193,9 @@ Summary* GetTeaser(Result* result_handle, const Config* alt_config = NULL);
* @param result_handle a handle obtained by a previous call to Analyse.
* @return value: a summary description containing the Juniper log as a text field
* if any log information is available, or else an empty summary.
- * This object is valid until ReleaseResult is called for result_handle
- */
-Summary* GetLog(Result* result_handle);
-
-/** Release all resources associated with the handle given including the
- * summaries created by this result handle.
- * @param result_handle The handle to release
+ * This object is valid until result_handle is deleted.
*/
-void ReleaseResult(Result*& result_handle);
+Summary* GetLog(Result& result_handle);
} // end namespace juniper
diff --git a/searchsummary/src/vespa/juniper/tokenizer.cpp b/searchsummary/src/vespa/juniper/tokenizer.cpp
index db6e1ecfccd..9253f81cf25 100644
--- a/searchsummary/src/vespa/juniper/tokenizer.cpp
+++ b/searchsummary/src/vespa/juniper/tokenizer.cpp
@@ -7,7 +7,7 @@
#include <vespa/log/log.h>
LOG_SETUP(".juniper.tokenizer");
-JuniperTokenizer::JuniperTokenizer(Fast_WordFolder* wordfolder,
+JuniperTokenizer::JuniperTokenizer(const Fast_WordFolder* wordfolder,
const char* text, size_t len, ITokenProcessor* successor,
const juniper::SpecialTokenRegistry * registry) :
_wordfolder(wordfolder), _text(text), _len(len), _successor(successor), _registry(registry),
diff --git a/searchsummary/src/vespa/juniper/tokenizer.h b/searchsummary/src/vespa/juniper/tokenizer.h
index 34ed1dba5bb..bf0c9452665 100644
--- a/searchsummary/src/vespa/juniper/tokenizer.h
+++ b/searchsummary/src/vespa/juniper/tokenizer.h
@@ -11,7 +11,7 @@ class Fast_WordFolder;
class JuniperTokenizer
{
public:
- JuniperTokenizer(Fast_WordFolder* wordfolder,
+ JuniperTokenizer(const Fast_WordFolder* wordfolder,
const char* text, size_t len, ITokenProcessor* = NULL,
const juniper::SpecialTokenRegistry * registry = NULL);
inline void SetSuccessor(ITokenProcessor* successor) { _successor = successor; }
@@ -22,7 +22,7 @@ public:
// Scan the input and dispatch to the successor
void scan();
private:
- Fast_WordFolder* _wordfolder;
+ const Fast_WordFolder* _wordfolder;
const char* _text; // The current input text
size_t _len; // Length of the text input
ITokenProcessor* _successor;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.cpp
index e6c8d0b6ab8..71800eb2128 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.cpp
@@ -16,7 +16,7 @@ DocsumStoreDocument::DocsumStoreDocument(std::unique_ptr<document::Document> doc
DocsumStoreDocument::~DocsumStoreDocument() = default;
-std::unique_ptr<document::FieldValue>
+DocsumStoreFieldValue
DocsumStoreDocument::get_field_value(const vespalib::string& field_name) const
{
if (_document) {
@@ -24,11 +24,11 @@ DocsumStoreDocument::get_field_value(const vespalib::string& field_name) const
auto value(field.getDataType().createFieldValue());
if (value) {
if (_document->getValue(field, *value)) {
- return value;
+ return DocsumStoreFieldValue(std::move(value));
}
}
}
- return {};
+ return DocsumStoreFieldValue();
}
void
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.h b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.h
index 3b0bea6e721..26cccc9970f 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_document.h
@@ -17,7 +17,7 @@ class DocsumStoreDocument : public IDocsumStoreDocument
public:
DocsumStoreDocument(std::unique_ptr<document::Document> document);
~DocsumStoreDocument() override;
- std::unique_ptr<document::FieldValue> get_field_value(const vespalib::string& field_name) const override;
+ DocsumStoreFieldValue get_field_value(const vespalib::string& field_name) const override;
void insert_summary_field(const vespalib::string& field_name, vespalib::slime::Inserter& inserter) const override;
void insert_document_id(vespalib::slime::Inserter& inserter) const override;
};
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_field_value.h b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_field_value.h
new file mode 100644
index 00000000000..d06a2ab8287
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsum_store_field_value.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/document/fieldvalue/fieldvalue.h>
+#include <memory>
+
+namespace search::docsummary {
+
+/*
+ * Class containing a field value returned from an IDocsumStoreDocument.
+ */
+class DocsumStoreFieldValue {
+ const document::FieldValue* _value;
+ std::unique_ptr<document::FieldValue> _value_store;
+public:
+ explicit DocsumStoreFieldValue(std::unique_ptr<document::FieldValue> value) noexcept
+ : _value(value.get()),
+ _value_store(std::move(value))
+ {
+ }
+ explicit DocsumStoreFieldValue(const document::FieldValue* value) noexcept
+ : _value(value),
+ _value_store()
+ {
+ }
+ DocsumStoreFieldValue()
+ : DocsumStoreFieldValue(nullptr)
+ {
+ }
+ ~DocsumStoreFieldValue() = default;
+ const document::FieldValue& operator*() const noexcept { return *_value; }
+ const document::FieldValue* operator->() const noexcept { return _value; }
+ const document::FieldValue* get() const noexcept { return _value; }
+ operator bool () const noexcept { return _value != nullptr; }
+};
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
index 16575a2e9dc..9c810386703 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
@@ -4,6 +4,7 @@
#include "docsum_field_writer_state.h"
#include <vespa/juniper/rpinterface.h>
#include <vespa/document/datatype/positiondatatype.h>
+#include <vespa/juniper/queryhandle.h>
#include <vespa/searchcommon/attribute/iattributecontext.h>
#include <vespa/searchlib/common/geo_location.h>
#include <vespa/searchlib/common/geo_location_parser.h>
@@ -36,24 +37,10 @@ GetDocsumsState::GetDocsumsState(GetDocsumsStateCallback &callback)
_rankFeatures(nullptr),
_matching_elements()
{
- _dynteaser._docid = static_cast<uint32_t>(-1);
- _dynteaser._input = static_cast<uint32_t>(-1);
- _dynteaser._lang = static_cast<uint32_t>(-1);
- _dynteaser._config = nullptr;
- _dynteaser._query = nullptr;
- _dynteaser._result = nullptr;
}
-GetDocsumsState::~GetDocsumsState()
-{
- if (_dynteaser._result != nullptr) {
- juniper::ReleaseResult(_dynteaser._result);
- }
- if (_dynteaser._query != nullptr) {
- juniper::ReleaseQueryHandle(_dynteaser._query);
- }
-}
+GetDocsumsState::~GetDocsumsState() = default;
const MatchingElements &
GetDocsumsState::get_matching_elements(const MatchingElementsFields &matching_elems_fields)
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
index 438a8a6d847..adcd47c098c 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
@@ -57,12 +57,7 @@ public:
GetDocsumsStateCallback &_callback;
struct DynTeaserState {
- uint32_t _docid; // document id ('cache key')
- uint32_t _input; // input field ('cache key')
- uint32_t _lang; // lang field ('cache key')
- juniper::Config *_config; // juniper config ('cache key')
- juniper::QueryHandle *_query; // juniper query representation
- juniper::Result *_result; // juniper analyze result
+ std::unique_ptr<juniper::QueryHandle> _query; // juniper query representation
} _dynteaser;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
index d9878cd2057..58dde39c336 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/dynamicteaserdfw.cpp
@@ -9,6 +9,8 @@
#include <vespa/searchlib/queryeval/split_float.h>
#include <vespa/vespalib/objects/hexdump.h>
#include <vespa/juniper/config.h>
+#include <vespa/juniper/queryhandle.h>
+#include <vespa/juniper/result.h>
#include <sstream>
#include <vespa/log/log.h>
@@ -368,27 +370,17 @@ DynamicTeaserDFW::getJuniperInput(GeneralResult *gres) {
vespalib::string
DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid, vespalib::stringref input, GetDocsumsState *state)
{
- if (state->_dynteaser._query == nullptr) {
+ if (!state->_dynteaser._query) {
JuniperQueryAdapter iq(state->_kwExtractor,
state->_args.getStackDump(),
&state->_args.highlightTerms());
state->_dynteaser._query = _juniper->CreateQueryHandle(iq, nullptr);
}
- LOG(debug, "makeDynamicTeaser: docid (%d,%d), fieldenum (%d,%d), lang (%d,%d) analyse %s",
- docid, state->_dynteaser._docid,
- _inputFieldEnumValue, state->_dynteaser._input,
- _langFieldEnumValue, state->_dynteaser._lang,
- (juniper::AnalyseCompatible(_juniperConfig.get(), state->_dynteaser._config) ? "no" : "yes"));
+ LOG(debug, "makeDynamicTeaser: docid (%d), fieldenum (%d), lang (%d)",
+ docid, _inputFieldEnumValue, _langFieldEnumValue);
- if (state->_dynteaser._result != nullptr)
- juniper::ReleaseResult(state->_dynteaser._result);
-
- state->_dynteaser._docid = docid;
- state->_dynteaser._input = _inputFieldEnumValue;
- state->_dynteaser._lang = _langFieldEnumValue;
- state->_dynteaser._config = _juniperConfig.get();
- state->_dynteaser._result = nullptr;
+ std::unique_ptr<juniper::Result> result;
if (state->_dynteaser._query != nullptr) {
@@ -401,13 +393,12 @@ DynamicTeaserDFW::makeDynamicTeaser(uint32_t docid, vespalib::stringref input, G
auto langid = static_cast<uint32_t>(-1);
- state->_dynteaser._result =
- juniper::Analyse(_juniperConfig.get(), state->_dynteaser._query,
- input.data(), input.length(), docid, _inputFieldEnumValue, langid);
+ result = juniper::Analyse(*_juniperConfig, *state->_dynteaser._query,
+ input.data(), input.length(), docid, _inputFieldEnumValue, langid);
}
- juniper::Summary *teaser = (state->_dynteaser._result != nullptr)
- ? juniper::GetTeaser(state->_dynteaser._result, _juniperConfig.get())
+ juniper::Summary *teaser = result
+ ? juniper::GetTeaser(*result, _juniperConfig.get())
: nullptr;
if (LOG_WOULD_LOG(debug)) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/general_result.cpp b/searchsummary/src/vespa/searchsummary/docsummary/general_result.cpp
index 825c3b39c1b..359959391fd 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/general_result.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/general_result.cpp
@@ -62,13 +62,13 @@ GeneralResult::GetPresentEntryFromEnumValue(uint32_t value)
return GetPresentEntry(idx);
}
-std::unique_ptr<document::FieldValue>
+DocsumStoreFieldValue
GeneralResult::get_field_value(const vespalib::string& field_name) const
{
if (_document != nullptr) {
return _document->get_field_value(field_name);
}
- return {};
+ return DocsumStoreFieldValue();
}
bool
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/general_result.h b/searchsummary/src/vespa/searchsummary/docsummary/general_result.h
index cff27a496e3..8f7a1377502 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/general_result.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/general_result.h
@@ -4,10 +4,7 @@
#include "resultclass.h"
#include "docsumstorevalue.h"
-
-namespace document {
-class FieldValue;
-}
+#include "docsum_store_field_value.h"
namespace search::docsummary {
@@ -42,7 +39,7 @@ public:
}
ResEntry *GetPresentEntry(const char *name);
ResEntry *GetPresentEntryFromEnumValue(uint32_t val);
- std::unique_ptr<document::FieldValue> get_field_value(const vespalib::string& field_name) const;
+ DocsumStoreFieldValue get_field_value(const vespalib::string& field_name) const;
bool unpack(const char *buf, const size_t buflen);
bool inplaceUnpack(const DocsumStoreValue &value) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/i_docsum_store_document.h b/searchsummary/src/vespa/searchsummary/docsummary/i_docsum_store_document.h
index c177568c467..b9c2ae76e7a 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/i_docsum_store_document.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/i_docsum_store_document.h
@@ -2,10 +2,8 @@
#pragma once
+#include "docsum_store_field_value.h"
#include <vespa/vespalib/stllike/string.h>
-#include <memory>
-
-namespace document { class FieldValue; }
namespace vespalib::slime { struct Inserter; }
@@ -20,7 +18,7 @@ class IDocsumStoreDocument
{
public:
virtual ~IDocsumStoreDocument() = default;
- virtual std::unique_ptr<document::FieldValue> get_field_value(const vespalib::string& field_name) const = 0;
+ virtual DocsumStoreFieldValue get_field_value(const vespalib::string& field_name) const = 0;
virtual void insert_summary_field(const vespalib::string& field_name, vespalib::slime::Inserter& inserter) const = 0;
virtual void insert_document_id(vespalib::slime::Inserter& inserter) const = 0;
};
diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.h b/storage/src/vespa/storage/bucketdb/bucketmanager.h
index eacd0c8ca6a..124acf1864c 100644
--- a/storage/src/vespa/storage/bucketdb/bucketmanager.h
+++ b/storage/src/vespa/storage/bucketdb/bucketmanager.h
@@ -34,7 +34,6 @@ class BucketManager : public StorageLink,
{
public:
/** Type used for message queues */
- using CommandList = std::list<std::shared_ptr<api::StorageCommand>>;
using BucketInfoRequestList = std::list<std::shared_ptr<api::RequestBucketInfoCommand>>;
using BucketInfoRequestMap = std::unordered_map<document::BucketSpace, BucketInfoRequestList, document::BucketSpace::hash>;
@@ -55,8 +54,7 @@ private:
mutable std::mutex _queueProcessingLock;
using ReplyQueue = std::vector<api::StorageReply::SP>;
- using ConflictingBuckets = std::unordered_set<document::BucketId,
- document::BucketId::hash>;
+ using ConflictingBuckets = std::unordered_set<document::BucketId, document::BucketId::hash>;
ReplyQueue _queuedReplies;
ConflictingBuckets _conflictingBuckets;
/**
@@ -80,9 +78,6 @@ private:
framework::Thread::UP _thread;
std::chrono::milliseconds _simulated_processing_delay;
- BucketManager(const BucketManager&);
- BucketManager& operator=(const BucketManager&);
-
class ScopedQueueDispatchGuard {
BucketManager& _mgr;
public:
@@ -94,8 +89,9 @@ private:
};
public:
- explicit BucketManager(const config::ConfigUri&,
- ServiceLayerComponentRegister&);
+ BucketManager(const config::ConfigUri&, ServiceLayerComponentRegister&);
+ BucketManager(const BucketManager&) = delete;
+ BucketManager& operator=(const BucketManager&) = delete;
~BucketManager();
void startWorkerThread();
@@ -127,8 +123,7 @@ private:
void updateMinUsedBits();
bool onRequestBucketInfo(const std::shared_ptr<api::RequestBucketInfoCommand>&) override;
- bool processRequestBucketInfoCommands(document::BucketSpace bucketSpace,
- BucketInfoRequestList &reqs);
+ bool processRequestBucketInfoCommands(document::BucketSpace bucketSpace, BucketInfoRequestList &reqs);
/**
* Enqueue reply and add its bucket to the set of conflicting buckets iff
@@ -199,42 +194,26 @@ private:
*
* Not thread safe.
*/
- bool replyConflictsWithConcurrentOperation(
- const api::BucketReply& reply) const;
-
+ bool replyConflictsWithConcurrentOperation(const api::BucketReply& reply) const;
bool enqueueIfBucketHasConflicts(const api::BucketReply::SP& reply);
-
bool onUp(const std::shared_ptr<api::StorageMessage>&) override;
- bool onSetSystemState(
- const std::shared_ptr<api::SetSystemStateCommand>&) override;
- bool onCreateBucket(
- const std::shared_ptr<api::CreateBucketCommand>&) override;
- bool onMergeBucket(
- const std::shared_ptr<api::MergeBucketCommand>&) override;
- bool onRemove(
- const std::shared_ptr<api::RemoveCommand>&) override;
- bool onRemoveReply(
- const std::shared_ptr<api::RemoveReply>&) override;
- bool onPut(
- const std::shared_ptr<api::PutCommand>&) override;
- bool onPutReply(
- const std::shared_ptr<api::PutReply>&) override;
- bool onUpdate(
- const std::shared_ptr<api::UpdateCommand>&) override;
- bool onUpdateReply(
- const std::shared_ptr<api::UpdateReply>&) override;
- bool onNotifyBucketChangeReply(
- const std::shared_ptr<api::NotifyBucketChangeReply>&) override;
+ bool onSetSystemState(const std::shared_ptr<api::SetSystemStateCommand>&) override;
+ bool onCreateBucket(const std::shared_ptr<api::CreateBucketCommand>&) override;
+ bool onMergeBucket(const std::shared_ptr<api::MergeBucketCommand>&) override;
+ bool onRemove(const std::shared_ptr<api::RemoveCommand>&) override;
+ bool onRemoveReply(const std::shared_ptr<api::RemoveReply>&) override;
+ bool onPut(const std::shared_ptr<api::PutCommand>&) override;
+ bool onPutReply(const std::shared_ptr<api::PutReply>&) override;
+ bool onUpdate(const std::shared_ptr<api::UpdateCommand>&) override;
+ bool onUpdateReply(const std::shared_ptr<api::UpdateReply>&) override;
+ bool onNotifyBucketChangeReply(const std::shared_ptr<api::NotifyBucketChangeReply>&) override;
bool verifyAndUpdateLastModified(api::StorageCommand& cmd,
const document::Bucket& bucket,
uint64_t lastModified);
- bool onSplitBucketReply(
- const std::shared_ptr<api::SplitBucketReply>&) override;
- bool onJoinBucketsReply(
- const std::shared_ptr<api::JoinBucketsReply>&) override;
- bool onDeleteBucketReply(
- const std::shared_ptr<api::DeleteBucketReply>&) override;
+ bool onSplitBucketReply(const std::shared_ptr<api::SplitBucketReply>&) override;
+ bool onJoinBucketsReply(const std::shared_ptr<api::JoinBucketsReply>&) override;
+ bool onDeleteBucketReply(const std::shared_ptr<api::DeleteBucketReply>&) override;
};
} // storage
diff --git a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
index 13e5ad1c84b..706325a0f7a 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
@@ -21,6 +21,7 @@ using search::fef::RankSetup;
using vsm::VsmfieldsHandle;
using vsm::VSMAdapter;
using vsm::FieldIdTList;
+using vespalib::make_string_short::fmt;
namespace streaming {
@@ -115,10 +116,10 @@ RankManager::Snapshot::initRankSetup(const BlueprintFactory & factory)
for (uint32_t i = 0; i < _indexEnv.size(); ++i) {
IndexEnvironment & ie = _indexEnv[i];
- RankSetup::SP rs(new RankSetup(factory, ie));
+ auto rs = std::make_shared<RankSetup>(factory, ie);
rs->configure(); // reads config values from the property map
if (!rs->compile()) {
- LOG(warning, "Could not compile rank setup for rank profile '%u'.", i);
+ LOG(warning, "Could not compile rank setup for rank profile '%u'. Errors = %s", i, rs->getJoinedWarnings().c_str());
return false;
}
_rankSetup.push_back(rs);
@@ -127,7 +128,7 @@ RankManager::Snapshot::initRankSetup(const BlueprintFactory & factory)
LOG(debug, "Number of index environments and rank setups: %u", (uint32_t)_indexEnv.size());
LOG_ASSERT(_properties.size() == _rankSetup.size());
for (uint32_t i = 0; i < _properties.size(); ++i) {
- vespalib::string number = vespalib::make_string("%u", i);
+ vespalib::string number = fmt("%u", i);
_rpmap[number] = i;
}
for (uint32_t i = 0; i < _properties.size(); ++i) {
diff --git a/streamingvisitors/src/vespa/vsm/vsm/docsumfilter.cpp b/streamingvisitors/src/vespa/vsm/vsm/docsumfilter.cpp
index 5bcead79f97..3d0f8ed6e37 100644
--- a/streamingvisitors/src/vespa/vsm/vsm/docsumfilter.cpp
+++ b/streamingvisitors/src/vespa/vsm/vsm/docsumfilter.cpp
@@ -133,7 +133,7 @@ class DocsumStoreVsmDocument : public IDocsumStoreDocument
public:
DocsumStoreVsmDocument(const document::Document* document);
~DocsumStoreVsmDocument() override;
- std::unique_ptr<document::FieldValue> get_field_value(const vespalib::string& field_name) const override;
+ DocsumStoreFieldValue get_field_value(const vespalib::string& field_name) const override;
void insert_summary_field(const vespalib::string& field_name, vespalib::slime::Inserter& inserter) const override;
void insert_document_id(vespalib::slime::Inserter& inserter) const override;
};
@@ -145,7 +145,7 @@ DocsumStoreVsmDocument::DocsumStoreVsmDocument(const document::Document* documen
DocsumStoreVsmDocument::~DocsumStoreVsmDocument() = default;
-std::unique_ptr<document::FieldValue>
+DocsumStoreFieldValue
DocsumStoreVsmDocument::get_field_value(const vespalib::string& field_name) const
{
if (_document != nullptr) {
@@ -153,11 +153,11 @@ DocsumStoreVsmDocument::get_field_value(const vespalib::string& field_name) cons
auto value(field.getDataType().createFieldValue());
if (value) {
if (_document->getValue(field, *value)) {
- return value;
+ return DocsumStoreFieldValue(std::move(value));
}
}
}
- return {};
+ return DocsumStoreFieldValue();
}
void
diff --git a/vespalog/CMakeLists.txt b/vespalog/CMakeLists.txt
index 45410a1d29d..cc419681445 100644
--- a/vespalog/CMakeLists.txt
+++ b/vespalog/CMakeLists.txt
@@ -17,6 +17,5 @@ vespa_define_module(
src/test/threads
)
-vespa_install_script(src/vespa-logfmt/vespa-logfmt.pl vespa-logfmt bin)
install(FILES src/vespa-logfmt/vespa-logfmt.1 DESTINATION man/man1)
install(DIRECTORY DESTINATION var/db/vespa/logcontrol)