aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/go/auth/auth.go175
-rw-r--r--client/go/auth/secrets.go24
-rw-r--r--client/go/auth/token.go68
-rw-r--r--client/go/cli/cli.go355
-rw-r--r--client/go/cmd/config.go4
-rw-r--r--client/go/cmd/helpers.go37
-rw-r--r--client/go/cmd/login.go34
-rw-r--r--client/go/go.mod15
-rw-r--r--client/go/go.sum58
-rw-r--r--client/go/util/spinner.go63
-rw-r--r--client/go/vespa/deploy.go20
-rw-r--r--client/go/vespa/target.go79
-rw-r--r--client/go/vespa/target_test.go7
-rw-r--r--config/src/main/java/com/yahoo/config/subscription/ConfigSubscriber.java5
-rwxr-xr-xcontainer-disc/src/main/sh/vespa-start-container-daemon.sh9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java62
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java2
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java4
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java2
-rw-r--r--searchcore/src/tests/proton/bucketdb/bucketdb/bucketdb_test.cpp67
-rw-r--r--searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp26
-rw-r--r--searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp18
-rw-r--r--searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h36
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp12
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp6
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp10
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp8
-rw-r--r--searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h6
-rw-r--r--searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp34
-rw-r--r--searchlib/src/tests/grouping/grouping_test.cpp2
-rw-r--r--searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp2
-rw-r--r--searchlib/src/tests/groupingengine/groupingengine_test.cpp2
-rw-r--r--searchlib/src/tests/hitcollector/hitcollector_test.cpp4
-rw-r--r--searchlib/src/tests/sortresults/sorttest.cpp6
-rw-r--r--searchlib/src/tests/sortspec/multilevelsort.cpp10
-rw-r--r--searchlib/src/vespa/searchlib/aggregation/grouping.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/loadedvalue.h3
-rw-r--r--searchlib/src/vespa/searchlib/common/sortresults.cpp20
-rw-r--r--searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h1
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h4
47 files changed, 1169 insertions, 167 deletions
diff --git a/client/go/auth/auth.go b/client/go/auth/auth.go
new file mode 100644
index 00000000000..397e410924d
--- /dev/null
+++ b/client/go/auth/auth.go
@@ -0,0 +1,175 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package auth
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+const (
+ audiencePath = "/api/v2/"
+ waitThresholdInSeconds = 3
+ // SecretsNamespace namespace used to set/get values from the keychain
+ SecretsNamespace = "vespa-cli"
+)
+
+var requiredScopes = []string{"openid", "offline_access"}
+
+type Authenticator struct {
+ Audience string
+ ClientID string
+ DeviceCodeEndpoint string
+ OauthTokenEndpoint string
+}
+
+// SecretStore provides access to stored sensitive data.
+type SecretStore interface {
+ // Get gets the secret
+ Get(namespace, key string) (string, error)
+ // Delete removes the secret
+ Delete(namespace, key string) error
+}
+
+type Result struct {
+ Tenant string
+ Domain string
+ RefreshToken string
+ AccessToken string
+ ExpiresIn int64
+}
+
+type State struct {
+ DeviceCode string `json:"device_code"`
+ UserCode string `json:"user_code"`
+ VerificationURI string `json:"verification_uri_complete"`
+ ExpiresIn int `json:"expires_in"`
+ Interval int `json:"interval"`
+}
+
+// RequiredScopes returns the scopes used for login.
+func RequiredScopes() []string { return requiredScopes }
+
+func (s *State) IntervalDuration() time.Duration {
+ return time.Duration(s.Interval+waitThresholdInSeconds) * time.Second
+}
+
+// Start kicks-off the device authentication flow
+// by requesting a device code from Auth0,
+// The returned state contains the URI for the next step of the flow.
+func (a *Authenticator) Start(ctx context.Context) (State, error) {
+ s, err := a.getDeviceCode(ctx)
+ if err != nil {
+ return State{}, fmt.Errorf("cannot get device code: %w", err)
+ }
+ return s, nil
+}
+
+// Wait waits until the user is logged in on the browser.
+func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) {
+ t := time.NewTicker(state.IntervalDuration())
+ for {
+ select {
+ case <-ctx.Done():
+ return Result{}, ctx.Err()
+ case <-t.C:
+ data := url.Values{
+ "client_id": {a.ClientID},
+ "grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
+ "device_code": {state.DeviceCode},
+ }
+ r, err := http.PostForm(a.OauthTokenEndpoint, data)
+ if err != nil {
+ return Result{}, fmt.Errorf("cannot get device code: %w", err)
+ }
+ defer r.Body.Close()
+
+ var res struct {
+ AccessToken string `json:"access_token"`
+ IDToken string `json:"id_token"`
+ RefreshToken string `json:"refresh_token"`
+ Scope string `json:"scope"`
+ ExpiresIn int64 `json:"expires_in"`
+ TokenType string `json:"token_type"`
+ Error *string `json:"error,omitempty"`
+ ErrorDescription string `json:"error_description,omitempty"`
+ }
+
+ err = json.NewDecoder(r.Body).Decode(&res)
+ if err != nil {
+ return Result{}, fmt.Errorf("cannot decode response: %w", err)
+ }
+
+ if res.Error != nil {
+ if *res.Error == "authorization_pending" {
+ continue
+ }
+ return Result{}, errors.New(res.ErrorDescription)
+ }
+
+ ten, domain, err := parseTenant(res.AccessToken)
+ if err != nil {
+ return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err)
+ }
+
+ return Result{
+ RefreshToken: res.RefreshToken,
+ AccessToken: res.AccessToken,
+ ExpiresIn: res.ExpiresIn,
+ Tenant: ten,
+ Domain: domain,
+ }, nil
+ }
+ }
+}
+
+func (a *Authenticator) getDeviceCode(ctx context.Context) (State, error) {
+ data := url.Values{
+ "client_id": {a.ClientID},
+ "scope": {strings.Join(requiredScopes, " ")},
+ "audience": {a.Audience},
+ }
+ r, err := http.PostForm(a.DeviceCodeEndpoint, data)
+ if err != nil {
+ return State{}, fmt.Errorf("cannot get device code: %w", err)
+ }
+ defer r.Body.Close()
+ var res State
+ err = json.NewDecoder(r.Body).Decode(&res)
+ if err != nil {
+ return State{}, fmt.Errorf("cannot decode response: %w", err)
+ }
+ return res, nil
+}
+
+func parseTenant(accessToken string) (tenant, domain string, err error) {
+ parts := strings.Split(accessToken, ".")
+ v, err := base64.RawURLEncoding.DecodeString(parts[1])
+ if err != nil {
+ return "", "", err
+ }
+ var payload struct {
+ AUDs []string `json:"aud"`
+ }
+ if err := json.Unmarshal(v, &payload); err != nil {
+ return "", "", err
+ }
+ for _, aud := range payload.AUDs {
+ u, err := url.Parse(aud)
+ if err != nil {
+ return "", "", err
+ }
+ if u.Path == audiencePath {
+ parts := strings.Split(u.Host, ".")
+ return parts[0], u.Host, nil
+ }
+ }
+ return "", "", fmt.Errorf("audience not found for %s", audiencePath)
+}
diff --git a/client/go/auth/secrets.go b/client/go/auth/secrets.go
new file mode 100644
index 00000000000..e38d8c56595
--- /dev/null
+++ b/client/go/auth/secrets.go
@@ -0,0 +1,24 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package auth
+
+import (
+ "github.com/zalando/go-keyring"
+)
+
+type Keyring struct{}
+
+// Set sets the given key/value pair with the given namespace.
+func (k *Keyring) Set(namespace, key, value string) error {
+ return keyring.Set(namespace, key, value)
+}
+
+// Get gets a value for the given namespace and key.
+func (k *Keyring) Get(namespace, key string) (string, error) {
+ return keyring.Get(namespace, key)
+}
+
+// Delete deletes a value for the given namespace and key.
+func (k *Keyring) Delete(namespace, key string) error {
+ return keyring.Delete(namespace, key)
+}
diff --git a/client/go/auth/token.go b/client/go/auth/token.go
new file mode 100644
index 00000000000..e9b90b8994e
--- /dev/null
+++ b/client/go/auth/token.go
@@ -0,0 +1,68 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package auth
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+)
+
+type TokenResponse struct {
+ AccessToken string `json:"access_token"`
+ IDToken string `json:"id_token"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int `json:"expires_in"`
+}
+
+type TokenRetriever struct {
+ Authenticator *Authenticator
+ Secrets SecretStore
+ Client *http.Client
+}
+
+// Delete deletes the given tenant from the secrets' storage.
+func (t *TokenRetriever) Delete(tenant string) error {
+ return t.Secrets.Delete(SecretsNamespace, tenant)
+}
+
+// Refresh gets a new access token from the provided refresh token,
+// The request is used the default client_id and endpoint for device authentication.
+func (t *TokenRetriever) Refresh(ctx context.Context, tenant string) (TokenResponse, error) {
+ // get stored refresh token:
+ refreshToken, err := t.Secrets.Get(SecretsNamespace, tenant)
+ if err != nil {
+ return TokenResponse{}, fmt.Errorf("cannot get the stored refresh token: %w", err)
+ }
+ if refreshToken == "" {
+ return TokenResponse{}, errors.New("cannot use the stored refresh token: the token is empty")
+ }
+ // get access token:
+ r, err := t.Client.PostForm(t.Authenticator.OauthTokenEndpoint, url.Values{
+ "grant_type": {"refresh_token"},
+ "client_id": {t.Authenticator.ClientID},
+ "refresh_token": {refreshToken},
+ })
+ if err != nil {
+ return TokenResponse{}, fmt.Errorf("cannot get a new access token from the refresh token: %w", err)
+ }
+
+ defer r.Body.Close()
+ if r.StatusCode != http.StatusOK {
+ b, _ := ioutil.ReadAll(r.Body)
+ bodyStr := string(b)
+ return TokenResponse{}, fmt.Errorf("cannot get a new access token from the refresh token: %s", bodyStr)
+ }
+
+ var res TokenResponse
+ err = json.NewDecoder(r.Body).Decode(&res)
+ if err != nil {
+ return TokenResponse{}, fmt.Errorf("cannot decode response: %w", err)
+ }
+
+ return res, nil
+}
diff --git a/client/go/cli/cli.go b/client/go/cli/cli.go
new file mode 100644
index 00000000000..e1dde387b89
--- /dev/null
+++ b/client/go/cli/cli.go
@@ -0,0 +1,355 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package cli
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/joeshaw/envdecode"
+ "github.com/pkg/browser"
+ "github.com/vespa-engine/vespa/client/go/util"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "sort"
+ "sync"
+ "time"
+
+ "github.com/lestrrat-go/jwx/jwt"
+ "github.com/vespa-engine/vespa/client/go/auth"
+)
+
+const accessTokenExpThreshold = 5 * time.Minute
+
+var errUnauthenticated = errors.New("not logged in. Try 'vespa login'")
+
+type config struct {
+ InstallID string `json:"install_id,omitempty"`
+ DefaultTenant string `json:"default_tenant"`
+ Tenants map[string]Tenant `json:"tenants"`
+}
+
+// Tenant is an auth0 Tenant.
+type Tenant struct {
+ Name string `json:"name"`
+ Domain string `json:"domain"`
+ AccessToken string `json:"access_token,omitempty"`
+ Scopes []string `json:"scopes,omitempty"`
+ ExpiresAt time.Time `json:"expires_at"`
+ ClientID string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+}
+
+type Cli struct {
+ Authenticator *auth.Authenticator
+ tenant string
+ initOnce sync.Once
+ errOnce error
+ Path string
+ config config
+}
+
+// IsLoggedIn encodes the domain logic for determining whether we're
+// logged in. This might check our config storage, or just in memory.
+func (c *Cli) IsLoggedIn() bool {
+ // No need to check errors for initializing context.
+ _ = c.init()
+
+ if c.tenant == "" {
+ return false
+ }
+
+ // Parse the access token for the tenant.
+ t, err := jwt.ParseString(c.config.Tenants[c.tenant].AccessToken)
+ if err != nil {
+ return false
+ }
+
+ // Check if token is valid.
+ if err = jwt.Validate(t, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
+ return false
+ }
+
+ return true
+}
+
+// default to vespa-cd.auth0.com
+var (
+ authCfg struct {
+ Audience string `env:"AUTH0_AUDIENCE,default=https://vespa-cd.auth0.com/api/v2/"`
+ ClientID string `env:"AUTH0_CLIENT_ID,default=4wYWA496zBP28SLiz0PuvCt8ltL11DZX"`
+ DeviceCodeEndpoint string `env:"AUTH0_DEVICE_CODE_ENDPOINT,default=https://vespa-cd.auth0.com/oauth/device/code"`
+ OauthTokenEndpoint string `env:"AUTH0_OAUTH_TOKEN_ENDPOINT,default=https://vespa-cd.auth0.com/oauth/token"`
+ }
+)
+
+func ContextWithCancel() context.Context {
+ ctx, cancel := context.WithCancel(context.Background())
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, os.Interrupt)
+ go func() {
+ <-ch
+ defer cancel()
+ os.Exit(0)
+ }()
+ return ctx
+}
+
+// Setup will try to initialize the config context, as well as figure out if
+// there's a readily available tenant.
+func GetCli(configPath string) (*Cli, error) {
+ c := Cli{}
+ c.Path = configPath
+ if err := envdecode.StrictDecode(&authCfg); err != nil {
+ return nil, fmt.Errorf("could not decode env: %w", err)
+ }
+ c.Authenticator = &auth.Authenticator{
+ Audience: authCfg.Audience,
+ ClientID: authCfg.ClientID,
+ DeviceCodeEndpoint: authCfg.DeviceCodeEndpoint,
+ OauthTokenEndpoint: authCfg.OauthTokenEndpoint,
+ }
+ return &c, nil
+}
+
+// prepareTenant loads the Tenant, refreshing its token if necessary.
+// The Tenant access token needs a refresh if:
+// 1. the Tenant scopes are different from the currently required scopes.
+// 2. the access token is expired.
+func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) {
+ if err := c.init(); err != nil {
+ return Tenant{}, err
+ }
+ t, err := c.getTenant()
+ if err != nil {
+ return Tenant{}, err
+ }
+
+ if t.ClientID != "" && t.ClientSecret != "" {
+ return t, nil
+ }
+
+ if t.AccessToken == "" || scopesChanged(t) {
+ t, err = RunLogin(ctx, c, true)
+ if err != nil {
+ return Tenant{}, err
+ }
+ } else if isExpired(t.ExpiresAt, accessTokenExpThreshold) {
+ // check if the stored access token is expired:
+ // use the refresh token to get a new access token:
+ tr := &auth.TokenRetriever{
+ Authenticator: c.Authenticator,
+ Secrets: &auth.Keyring{},
+ Client: http.DefaultClient,
+ }
+
+ res, err := tr.Refresh(ctx, t.Domain)
+ if err != nil {
+ // ask and guide the user through the login process:
+ fmt.Println(fmt.Errorf("failed to renew access token, %s", err))
+ t, err = RunLogin(ctx, c, true)
+ if err != nil {
+ return Tenant{}, err
+ }
+ } else {
+ // persist the updated tenant with renewed access token
+ t.AccessToken = res.AccessToken
+ t.ExpiresAt = time.Now().Add(
+ time.Duration(res.ExpiresIn) * time.Second,
+ )
+
+ err = c.AddTenant(t)
+ if err != nil {
+ return Tenant{}, err
+ }
+ }
+ }
+
+ return t, nil
+}
+
+// isExpired is true if now() + a threshold is after the given date
+func isExpired(t time.Time, threshold time.Duration) bool {
+ return time.Now().Add(threshold).After(t)
+}
+
+// scopesChanged compare the Tenant scopes
+// with the currently required scopes.
+func scopesChanged(t Tenant) bool {
+ want := auth.RequiredScopes()
+ got := t.Scopes
+
+ sort.Strings(want)
+ sort.Strings(got)
+
+ if (want == nil) != (got == nil) {
+ return true
+ }
+
+ if len(want) != len(got) {
+ return true
+ }
+
+ for i := range t.Scopes {
+ if want[i] != got[i] {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (c *Cli) getTenant() (Tenant, error) {
+ if err := c.init(); err != nil {
+ return Tenant{}, err
+ }
+
+ t, ok := c.config.Tenants[c.tenant]
+ if !ok {
+ return Tenant{}, fmt.Errorf("unable to find tenant: %s; run 'vespa login' to configure a new tenant", c.tenant)
+ }
+
+ return t, nil
+}
+
+// AddTenant assigns an existing, or new Tenant. This is expected to be called
+// after a login has completed.
+func (c *Cli) AddTenant(ten Tenant) error {
+ _ = c.init()
+
+ if c.config.DefaultTenant == "" {
+ c.config.DefaultTenant = ten.Domain
+ }
+
+ // If we're dealing with an empty file, we'll need to initialize this map.
+ if c.config.Tenants == nil {
+ c.config.Tenants = map[string]Tenant{}
+ }
+
+ c.config.Tenants[ten.Domain] = ten
+
+ if err := c.persistConfig(); err != nil {
+ return fmt.Errorf("unexpected error persisting config: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Cli) persistConfig() error {
+ dir := filepath.Dir(c.Path)
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ return err
+ }
+ }
+
+ buf, err := json.MarshalIndent(c.config, "", " ")
+ if err != nil {
+ return err
+ }
+
+ if err := ioutil.WriteFile(c.Path, buf, 0600); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Cli) init() error {
+ c.initOnce.Do(func() {
+ if c.errOnce = c.initContext(); c.errOnce != nil {
+ return
+ }
+ })
+ return c.errOnce
+}
+
+func (c *Cli) initContext() (err error) {
+ if _, err := os.Stat(c.Path); os.IsNotExist(err) {
+ return errUnauthenticated
+ }
+
+ var buf []byte
+ if buf, err = ioutil.ReadFile(c.Path); err != nil {
+ return err
+ }
+
+ if err := json.Unmarshal(buf, &c.config); err != nil {
+ return err
+ }
+
+ if c.tenant == "" && c.config.DefaultTenant == "" {
+ return errUnauthenticated
+ }
+
+ if c.tenant == "" {
+ c.tenant = c.config.DefaultTenant
+ }
+
+ return nil
+}
+
+// RunLogin runs the login flow guiding the user through the process
+// by showing the login instructions, opening the browser.
+// Use `expired` to run the login from other commands setup:
+// this will only affect the messages.
+func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) {
+ if expired {
+ fmt.Println("Please sign in to re-authorize the CLI.")
+ }
+
+ state, err := cli.Authenticator.Start(ctx)
+ if err != nil {
+ return Tenant{}, fmt.Errorf("could not start the authentication process: %w", err)
+ }
+
+ fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)
+ fmt.Println("Press Enter to open the browser to log in or ^C to quit...")
+ fmt.Scanln()
+
+ err = browser.OpenURL(state.VerificationURI)
+
+ if err != nil {
+ fmt.Printf("Couldn't open the URL, please do it manually: %s.", state.VerificationURI)
+ }
+
+ var res auth.Result
+ err = util.Spinner("Waiting for login to complete in browser", func() error {
+ res, err = cli.Authenticator.Wait(ctx, state)
+ return err
+ })
+
+ if err != nil {
+ return Tenant{}, fmt.Errorf("login error: %w", err)
+ }
+
+ fmt.Print("\n")
+ fmt.Println("Successfully logged in.")
+ fmt.Print("\n")
+
+ // store the refresh token
+ secretsStore := &auth.Keyring{}
+ err = secretsStore.Set(auth.SecretsNamespace, res.Domain, res.RefreshToken)
+ if err != nil {
+ // log the error but move on
+ fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
+ }
+
+ t := Tenant{
+ Name: res.Tenant,
+ Domain: res.Domain,
+ AccessToken: res.AccessToken,
+ ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
+ Scopes: auth.RequiredScopes(),
+ }
+ err = cli.AddTenant(t)
+ if err != nil {
+ return Tenant{}, fmt.Errorf("could not add tenant to config: %w", err)
+ }
+
+ return t, nil
+}
diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go
index d10f66c83c6..0b08a2dc28d 100644
--- a/client/go/cmd/config.go
+++ b/client/go/cmd/config.go
@@ -148,6 +148,10 @@ func (c *Config) ReadAPIKey(tenantName string) ([]byte, error) {
return ioutil.ReadFile(c.APIKeyPath(tenantName))
}
+func (c *Config) AuthConfigPath() string {
+ return filepath.Join(c.Home, "auth", "config.json")
+}
+
func (c *Config) ReadSessionID(app vespa.ApplicationID) (int64, error) {
sessionPath, err := c.applicationFilePath(app, "session_id")
if err != nil {
diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go
index 4768290e33e..54d8798b71d 100644
--- a/client/go/cmd/helpers.go
+++ b/client/go/cmd/helpers.go
@@ -153,10 +153,17 @@ func getConsoleURL() string {
}
func getApiURL() string {
- if getSystem() == "publiccd" {
- return "https://api.vespa-external-cd.aws.oath.cloud:4443"
+ if vespa.Auth0AccessTokenEnabled() {
+ if getSystem() == "publiccd" {
+ return "https://api.vespa-external-cd.aws.oath.cloud:443"
+ }
+ return "https://api.vespa-external.aws.oath.cloud:443"
+ } else {
+ if getSystem() == "publiccd" {
+ return "https://api.vespa-external-cd.aws.oath.cloud:4443"
+ }
+ return "https://api.vespa-external.aws.oath.cloud:4443"
}
- return "https://api.vespa-external.aws.oath.cloud:4443"
}
func getTarget() vespa.Target {
@@ -174,9 +181,12 @@ func getTarget() vespa.Target {
fatalErr(err, "Could not load config")
return nil
}
- apiKey, err := ioutil.ReadFile(cfg.APIKeyPath(deployment.Application.Tenant))
- if err != nil {
- fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
+ var apiKey []byte = nil
+ if !vespa.Auth0AccessTokenEnabled() {
+ apiKey, err = ioutil.ReadFile(cfg.APIKeyPath(deployment.Application.Tenant))
+ if err != nil {
+ fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
+ }
}
privateKeyFile, err := cfg.PrivateKeyPath(deployment.Application)
if err != nil {
@@ -201,7 +211,8 @@ func getTarget() vespa.Target {
vespa.LogOptions{
Writer: stdout,
Level: vespa.LogLevel(logLevelArg),
- })
+ },
+ cfg.AuthConfigPath())
}
fatalErrHint(fmt.Errorf("Invalid target: %s", targetType), "Valid targets are 'local', 'cloud' or an URL")
return nil
@@ -232,11 +243,13 @@ func getDeploymentOpts(cfg *Config, pkg vespa.ApplicationPackage, target vespa.T
fatalErrHint(fmt.Errorf("Missing certificate in application package"), "Applications in Vespa Cloud require a certificate", "Try 'vespa cert'")
return opts
}
- var err error
- opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
- if err != nil {
- fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
- return opts
+ if !vespa.Auth0AccessTokenEnabled() {
+ var err error
+ opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
+ if err != nil {
+ fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
+ return opts
+ }
}
opts.Deployment = deployment
}
diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go
new file mode 100644
index 00000000000..767d462b0be
--- /dev/null
+++ b/client/go/cmd/login.go
@@ -0,0 +1,34 @@
+package cmd
+
+import (
+ "github.com/spf13/cobra"
+ "github.com/vespa-engine/vespa/client/go/cli"
+ "github.com/vespa-engine/vespa/client/go/vespa"
+)
+
+func init() {
+ if vespa.Auth0AccessTokenEnabled() {
+ rootCmd.AddCommand(loginCmd)
+ }
+}
+
+var loginCmd = &cobra.Command{
+ Use: "login",
+ Args: cobra.NoArgs,
+ Short: "Authenticate the Vespa CLI",
+ Example: "$ vespa login",
+ DisableAutoGenTag: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+ cfg, err := LoadConfig()
+ if err != nil {
+ return err
+ }
+ c, err := cli.GetCli(cfg.AuthConfigPath())
+ if err != nil {
+ return err
+ }
+ _, err = cli.RunLogin(ctx, c, false)
+ return err
+ },
+}
diff --git a/client/go/go.mod b/client/go/go.mod
index 27faff3fd0b..70eea958933 100644
--- a/client/go/go.mod
+++ b/client/go/go.mod
@@ -3,12 +3,23 @@ module github.com/vespa-engine/vespa/client/go
go 1.15
require (
+ github.com/briandowns/spinner v1.16.0
+ github.com/fatih/color v1.10.0 // indirect
+ github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
+ github.com/lestrrat-go/jwx v1.2.9
github.com/logrusorgru/aurora/v3 v3.0.0
- github.com/mattn/go-colorable v0.0.9
- github.com/mattn/go-isatty v0.0.3
+ github.com/mattn/go-colorable v0.1.8
+ github.com/mattn/go-isatty v0.0.13
+ github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
+ github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.8.1
+ 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/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
+ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
diff --git a/client/go/go.sum b/client/go/go.sum
index 0462d0575a1..59656af2b35 100644
--- a/client/go/go.sum
+++ b/client/go/go.sum
@@ -45,6 +45,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs=
+github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -57,9 +59,14 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
+github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -68,12 +75,18 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
+github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec=
+github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -165,6 +178,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd h1:nIzoSW6OhhppWLm4yqBwZsKJlAayUu5FGozhrF3ETSM=
+github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd/go.mod h1:MEQrHur0g8VplbLOv5vXmDzacSaH9Z7XhcgsSh1xciU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -180,14 +195,31 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
+github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
+github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
+github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
+github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
+github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
+github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
+github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
+github.com/lestrrat-go/jwx v1.2.9 h1:kS8kLI4oaBYJJ6u6rpbPI0tDYVCqo0P5u8vv1zoQ49U=
+github.com/lestrrat-go/jwx v1.2.9/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
+github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
+github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
+github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -204,7 +236,11 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
+github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -235,6 +271,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -249,6 +287,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zalando/go-keyring v0.1.1 h1:w2V9lcx/Uj4l+dzAf1m9s+DJ1O8ROkEHnynonHjTcYE=
+github.com/zalando/go-keyring v0.1.1/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -269,6 +309,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -367,6 +410,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -376,9 +420,11 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -403,8 +449,11 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -575,8 +624,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/client/go/util/spinner.go b/client/go/util/spinner.go
new file mode 100644
index 00000000000..1deb4296d28
--- /dev/null
+++ b/client/go/util/spinner.go
@@ -0,0 +1,63 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package util
+
+import (
+ "os"
+ "time"
+
+ "github.com/briandowns/spinner"
+ "github.com/pkg/errors"
+)
+
+const (
+ spinnerTextEllipsis = "..."
+ spinnerTextDone = "done"
+ spinnerTextFailed = "failed"
+ spinnerColor = "blue"
+)
+
+var messages = os.Stderr
+
+func Spinner(text string, fn func() error) error {
+ initialMsg := text + spinnerTextEllipsis + " "
+ doneMsg := initialMsg + spinnerTextDone + "\n"
+ failMsg := initialMsg + spinnerTextFailed + "\n"
+
+ return loading(initialMsg, doneMsg, failMsg, fn)
+}
+
+func loading(initialMsg, doneMsg, failMsg string, fn func() error) error {
+ done := make(chan struct{})
+ errc := make(chan error)
+ go func() {
+ defer close(done)
+
+ s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(messages))
+ s.Prefix = initialMsg
+ s.FinalMSG = doneMsg
+ s.HideCursor = true
+ s.Writer = messages
+
+ if err := s.Color(spinnerColor); err != nil {
+ panic(Error(err, "failed setting spinner color"))
+ }
+
+ s.Start()
+ err := <-errc
+ if err != nil {
+ s.FinalMSG = failMsg
+ }
+
+ s.Stop()
+ }()
+
+ err := fn()
+ errc <- err
+ <-done
+ return err
+}
+
+func Error(e error, message string) error {
+ return errors.Wrap(e, message)
+}
diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go
index 3718c7d813a..9c5fb3a12ac 100644
--- a/client/go/vespa/deploy.go
+++ b/client/go/vespa/deploy.go
@@ -327,12 +327,12 @@ func Submit(opts DeploymentOpts) error {
Header: make(http.Header),
}
request.Header.Set("Content-Type", writer.FormDataContentType())
- signer := NewRequestSigner(opts.Deployment.Application.SerializedForm(), opts.APIKey)
- if err := signer.SignRequest(request); err != nil {
+ serviceDescription := "Submit service"
+ sigKeyId := opts.Deployment.Application.SerializedForm()
+ if err := opts.Target.PrepareApiRequest(request, sigKeyId); err != nil {
return err
}
- serviceDescription := "Submit service"
- response, err := util.HttpDo(request, time.Minute*10, serviceDescription)
+ response, err := util.HttpDo(request, time.Minute*10, sigKeyId)
if err != nil {
return err
}
@@ -344,7 +344,7 @@ func checkDeploymentOpts(opts DeploymentOpts) error {
if !opts.ApplicationPackage.HasCertificate() {
return fmt.Errorf("%s: missing certificate in package", opts)
}
- if opts.APIKey == nil {
+ if !Auth0AccessTokenEnabled() && opts.APIKey == nil {
return fmt.Errorf("%s: missing api key", opts.String())
}
return nil
@@ -363,13 +363,11 @@ func uploadApplicationPackage(url *url.URL, opts DeploymentOpts) (int64, error)
Header: header,
Body: ioutil.NopCloser(zipReader),
}
- if opts.APIKey != nil {
- signer := NewRequestSigner(opts.Deployment.Application.SerializedForm(), opts.APIKey)
- if err := signer.SignRequest(request); err != nil {
- return 0, err
- }
- }
serviceDescription := "Deploy service"
+ sigKeyId := opts.Deployment.Application.SerializedForm()
+ if err := opts.Target.PrepareApiRequest(request, sigKeyId); err != nil {
+ return 0, err
+ }
response, err := util.HttpDo(request, time.Minute*10, serviceDescription)
if err != nil {
return 0, err
diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go
index 8a09440f5cc..f497bf5b3cd 100644
--- a/client/go/vespa/target.go
+++ b/client/go/vespa/target.go
@@ -6,13 +6,16 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
+ "github.com/vespa-engine/vespa/client/go/cli"
"io"
"io/ioutil"
"math"
"net/http"
"net/url"
+ "os"
"sort"
"strconv"
+ "strings"
"time"
"github.com/vespa-engine/vespa/client/go/util"
@@ -35,6 +38,7 @@ type Service struct {
BaseURL string
Name string
TLSOptions TLSOptions
+ Target *Target
}
// Target represents a Vespa platform, running named Vespa services.
@@ -47,6 +51,8 @@ type Target interface {
// PrintLog writes the logs of this deployment using given options to control output.
PrintLog(options LogOptions) error
+
+ PrepareApiRequest(req *http.Request, sigKeyId string) error
}
// TLSOptions configures the certificate to use for service requests.
@@ -66,11 +72,21 @@ type LogOptions struct {
Level int
}
+func Auth0AccessTokenEnabled() bool {
+ v, present := os.LookupEnv("VESPA_CLI_OAUTH2_DEVICE_FLOW")
+ if !present {
+ return false
+ }
+ return strings.ToLower(v) == "true" || v == "1" || v == ""
+}
+
type customTarget struct {
targetType string
baseURL string
}
+func (t *customTarget) PrepareApiRequest(req *http.Request, sigKeyId string) error { return nil }
+
// Do sends request to this service. Any required authentication happens automatically.
func (s *Service) Do(request *http.Request, timeout time.Duration) (*http.Response, error) {
if s.TLSOptions.KeyPair.Certificate != nil {
@@ -192,8 +208,9 @@ type cloudTarget struct {
tlsOptions TLSOptions
logOptions LogOptions
- queryURL string
- documentURL string
+ queryURL string
+ documentURL string
+ authConfigPath string
}
func (t *cloudTarget) Type() string { return t.targetType }
@@ -221,6 +238,30 @@ func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64) (
return nil, fmt.Errorf("unknown service: %s", name)
}
+func (t *cloudTarget) PrepareApiRequest(req *http.Request, sigKeyId string) error {
+ if Auth0AccessTokenEnabled() {
+ if err := t.addAuth0AccessToken(req); err != nil {
+ return err
+ }
+ } else if t.apiKey != nil {
+ signer := NewRequestSigner(sigKeyId, t.apiKey)
+ if err := signer.SignRequest(req); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (t *cloudTarget) addAuth0AccessToken(request *http.Request) error {
+ c, err := cli.GetCli(t.authConfigPath)
+ tenant, err := c.PrepareTenant(cli.ContextWithCancel())
+ if err != nil {
+ return err
+ }
+ request.Header.Set("Authorization", "Bearer "+tenant.AccessToken)
+ return nil
+}
+
func (t *cloudTarget) logsURL() string {
return fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s/logs",
t.apiURL,
@@ -233,7 +274,6 @@ func (t *cloudTarget) PrintLog(options LogOptions) error {
if err != nil {
return err
}
- signer := NewRequestSigner(t.deployment.Application.SerializedForm(), t.apiKey)
lastFrom := options.From
requestFunc := func() *http.Request {
fromMillis := lastFrom.Unix() * 1000
@@ -244,9 +284,7 @@ func (t *cloudTarget) PrintLog(options LogOptions) error {
q.Set("to", strconv.FormatInt(toMillis, 10))
}
req.URL.RawQuery = q.Encode()
- if err := signer.SignRequest(req); err != nil {
- panic(err)
- }
+ t.PrepareApiRequest(req, t.deployment.Application.SerializedForm())
return req
}
logFunc := func(status int, response []byte) (bool, error) {
@@ -280,16 +318,15 @@ func (t *cloudTarget) PrintLog(options LogOptions) error {
}
func (t *cloudTarget) waitForEndpoints(timeout time.Duration, runID int64) error {
- signer := NewRequestSigner(t.deployment.Application.SerializedForm(), t.apiKey)
if runID > 0 {
- if err := t.waitForRun(signer, runID, timeout); err != nil {
+ if err := t.waitForRun(runID, timeout); err != nil {
return err
}
}
- return t.discoverEndpoints(signer, timeout)
+ return t.discoverEndpoints(timeout)
}
-func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout time.Duration) error {
+func (t *cloudTarget) waitForRun(runID int64, timeout time.Duration) error {
runURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/job/%s-%s/run/%d",
t.apiURL,
t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance,
@@ -303,7 +340,7 @@ func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout tim
q := req.URL.Query()
q.Set("after", strconv.FormatInt(lastID, 10))
req.URL.RawQuery = q.Encode()
- if err := signer.SignRequest(req); err != nil {
+ if err := t.PrepareApiRequest(req, t.deployment.Application.SerializedForm()); err != nil {
panic(err)
}
return req
@@ -353,7 +390,7 @@ func (t *cloudTarget) printLog(response jobResponse, last int64) int64 {
return response.LastID
}
-func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Duration) error {
+func (t *cloudTarget) discoverEndpoints(timeout time.Duration) error {
deploymentURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s",
t.apiURL,
t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance,
@@ -362,7 +399,7 @@ func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Dura
if err != nil {
return err
}
- if err := signer.SignRequest(req); err != nil {
+ if err := t.PrepareApiRequest(req, t.deployment.Application.SerializedForm()); err != nil {
return err
}
var endpointURL string
@@ -409,14 +446,16 @@ func CustomTarget(baseURL string) Target {
}
// CloudTarget creates a Target for the Vespa Cloud platform.
-func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions) Target {
+func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions,
+ authConfigPath string) Target {
return &cloudTarget{
- apiURL: apiURL,
- targetType: cloudTargetType,
- deployment: deployment,
- apiKey: apiKey,
- tlsOptions: tlsOptions,
- logOptions: logOptions,
+ apiURL: apiURL,
+ targetType: cloudTargetType,
+ deployment: deployment,
+ apiKey: apiKey,
+ tlsOptions: tlsOptions,
+ logOptions: logOptions,
+ authConfigPath: authConfigPath,
}
}
diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go
index ed924059297..d4d23901513 100644
--- a/client/go/vespa/target_test.go
+++ b/client/go/vespa/target_test.go
@@ -143,7 +143,10 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
x509KeyPair, err := tls.X509KeyPair(kp.Certificate, kp.PrivateKey)
assert.Nil(t, err)
- apiKey, err := CreateAPIKey()
+ var apiKey []byte = nil
+ if !Auth0AccessTokenEnabled() {
+ apiKey, err = CreateAPIKey()
+ }
assert.Nil(t, err)
target := CloudTarget(
@@ -154,7 +157,7 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
},
apiKey,
TLSOptions{KeyPair: x509KeyPair},
- LogOptions{Writer: logWriter})
+ LogOptions{Writer: logWriter}, "")
if ct, ok := target.(*cloudTarget); ok {
ct.apiURL = url
} else {
diff --git a/config/src/main/java/com/yahoo/config/subscription/ConfigSubscriber.java b/config/src/main/java/com/yahoo/config/subscription/ConfigSubscriber.java
index f5ed79a1d44..07132c460f9 100644
--- a/config/src/main/java/com/yahoo/config/subscription/ConfigSubscriber.java
+++ b/config/src/main/java/com/yahoo/config/subscription/ConfigSubscriber.java
@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.FINE;
@@ -293,10 +294,13 @@ public class ConfigSubscriber implements AutoCloseable {
// Keep on polling the subscriptions until we have a new generation across the board, or it times out
for (ConfigHandle<? extends ConfigInstance> h : subscriptionHandles) {
ConfigSubscription<? extends ConfigInstance> subscription = h.subscription();
+ log.log(Level.FINEST, () -> "Calling nextConfig for " + subscription.getKey());
if ( ! subscription.nextConfig(timeLeftMillis)) {
// This subscriber has no new state and we know it has exhausted all time
+ log.log(Level.FINEST, () -> "No new config for " + subscription.getKey());
return false;
}
+ log.log(Level.FINEST, () -> "Got new generation or config for " + subscription.getKey());
throwIfExceptionSet(subscription);
ConfigSubscription.ConfigState<? extends ConfigInstance> config = subscription.getConfigState();
if (currentGen == null) currentGen = config.getGeneration();
@@ -322,6 +326,7 @@ public class ConfigSubscriber implements AutoCloseable {
if (reconfigDue) {
// This indicates the clients will possibly reconfigure their services, so "reset" changed-logic in subscriptions.
// Also if appropriate update the changed flag on the handler, which clients use.
+ log.log(Level.FINE, () -> "Reconfig will happen for generation " + generation);
markSubsChangedSeen(currentGen);
synchronized (monitor) {
generation = currentGen;
diff --git a/container-disc/src/main/sh/vespa-start-container-daemon.sh b/container-disc/src/main/sh/vespa-start-container-daemon.sh
index 8c122d3170e..ded38e9f7c9 100755
--- a/container-disc/src/main/sh/vespa-start-container-daemon.sh
+++ b/container-disc/src/main/sh/vespa-start-container-daemon.sh
@@ -129,9 +129,14 @@ configure_memory() {
available=`free -m | grep Mem | tr -s ' ' | cut -f2 -d' '`
if hash cgget 2>/dev/null; then
# TODO: Create vespa_cgget for this and remove dependency on libcgroup-tools
- available_cgroup_bytes=$(cgget -nv -r memory.limit_in_bytes /)
+ available_cgroup_bytes=$(cgget -nv -r memory.limit_in_bytes / 2>&1)
if [ $? -ne 0 ]; then
- available_cgroup_bytes=$(vespa_cg2get memory.max)
+ if [[ "$available_cgroup_bytes" =~ "Cgroup is not mounted" ]]; then
+ available_cgroup_bytes=$(vespa_cg2get memory.max)
+ else
+ echo "$available_cgroup_bytes" >&2
+ fi
+
# If command failed or returned value is 'max' assign a big value (default in CGroup v1)
if ! [[ "$available_cgroup_bytes" =~ ^[0-9]+$ ]]; then
available_cgroup_bytes=$(((1 << 63) -1))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
index be533b0c53b..16f12b3ac07 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
@@ -243,7 +243,8 @@ public class RoutingController {
Instance instance = application.require(instanceName);
boolean registerLegacyNames = requiresLegacyNames(application.deploymentSpec(), instanceName);
Set<ContainerEndpoint> containerEndpoints = new HashSet<>();
- EndpointList endpoints = declaredEndpointsOf(application);
+ DeploymentId deployment = new DeploymentId(instance.id(), zone);
+ EndpointList endpoints = declaredEndpointsOf(application).targets(deployment);
EndpointList globalEndpoints = endpoints.scope(Endpoint.Scope.global);
// Add endpoints backed by a rotation, and register them in DNS if necessary
for (var assignedRotation : instance.rotations()) {
@@ -280,9 +281,7 @@ public class RoutingController {
}
// Add endpoints not backed by a rotation (i.e. other routing methods so that the config server always knows
// about global names, even when not using rotations)
- DeploymentId deployment = new DeploymentId(instance.id(), zone);
globalEndpoints.not().requiresRotation()
- .targets(deployment)
.groupingBy(Endpoint::cluster)
.forEach((clusterId, clusterEndpoints) -> {
containerEndpoints.add(new ContainerEndpoint(clusterId.value(),
@@ -290,10 +289,8 @@ public class RoutingController {
clusterEndpoints.mapToList(Endpoint::dnsName)));
});
// Add application endpoints
- EndpointList applicationEndpoints = endpoints.scope(Endpoint.Scope.application)
- .not().direct() // These are handled by RoutingPolicies
- .targets(deployment);
- for (var endpoint : applicationEndpoints) {
+ EndpointList applicationEndpoints = endpoints.scope(Endpoint.Scope.application);
+ for (var endpoint : applicationEndpoints.shared()) { // DNS for non-shared endpoints is handled by RoutingPolicies
Set<ZoneId> targetZones = endpoint.targets().stream()
.map(t -> t.deployment().zoneId())
.collect(Collectors.toUnmodifiableSet());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
index f626d832b6a..f9fd02fbf56 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
@@ -78,6 +78,11 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList>
return matching(endpoint -> endpoint.routingMethod().isDirect());
}
+ /** Returns the subset of endpoints that use shared routing */
+ public EndpointList shared() {
+ return matching(endpoint -> endpoint.routingMethod().isShared());
+ }
+
public static EndpointList copyOf(Collection<Endpoint> endpoints) {
return new EndpointList(endpoints, false);
}
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 1e8e444896f..9472801ef2c 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
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudName;
@@ -233,35 +234,58 @@ public class ControllerTest {
@Test
public void testDnsUpdatesForGlobalEndpoint() {
- var context = tester.newDeploymentContext("tenant1", "app1", "default");
+ var betaContext = tester.newDeploymentContext("tenant1", "app1", "beta");
+ var defaultContext = tester.newDeploymentContext("tenant1", "app1", "default");
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("beta,default")
.endpoint("default", "foo")
.region("us-west-1")
.region("us-central-1") // Two deployments should result in each DNS alias being registered once
.build();
- context.submit(applicationPackage).deploy();
+ betaContext.submit(applicationPackage).deploy();
+
+ { // Expected rotation names are passed to beta instance deployments
+ Collection<Deployment> betaDeployments = betaContext.instance().deployments().values();
+ assertFalse(betaDeployments.isEmpty());
+ for (Deployment deployment : betaDeployments) {
+ assertEquals("Rotation names are passed to config server in " + deployment.zone(),
+ Set.of("rotation-id-01",
+ "beta--app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().containerEndpointNames(betaContext.deploymentIdIn(deployment.zone())));
+ }
+ betaContext.flushDnsUpdates();
+ }
- Collection<Deployment> deployments = context.instance().deployments().values();
- assertFalse(deployments.isEmpty());
- for (Deployment deployment : deployments) {
- assertEquals("Rotation names are passed to config server in " + deployment.zone(),
- Set.of("rotation-id-01",
- "app1--tenant1.global.vespa.oath.cloud"),
- tester.configServer().containerEndpointNames(context.deploymentIdIn(deployment.zone())));
+ { // Expected rotation names are passed to default instance deployments
+ Collection<Deployment> defaultDeployments = defaultContext.instance().deployments().values();
+ assertFalse(defaultDeployments.isEmpty());
+ for (Deployment deployment : defaultDeployments) {
+ assertEquals("Rotation names are passed to config server in " + deployment.zone(),
+ Set.of("rotation-id-02",
+ "app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().containerEndpointNames(defaultContext.deploymentIdIn(deployment.zone())));
+ }
+ defaultContext.flushDnsUpdates();
}
- context.flushDnsUpdates();
- assertEquals(1, tester.controllerTester().nameService().records().size());
+ Map<String, String> rotationCnames = Map.of("beta--app1--tenant1.global.vespa.oath.cloud", "rotation-fqdn-01.",
+ "app1--tenant1.global.vespa.oath.cloud", "rotation-fqdn-02.");
+ rotationCnames.forEach((cname, data) -> {
+ var record = tester.controllerTester().findCname(cname);
+ assertTrue(record.isPresent());
+ assertEquals(cname, record.get().name().asString());
+ assertEquals(data, record.get().data().asString());
+ });
- var record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ Map<ApplicationId, List<String>> globalDnsNamesByInstance = Map.of(betaContext.instanceId(), List.of("beta--app1--tenant1.global.vespa.oath.cloud"),
+ defaultContext.instanceId(), List.of("app1--tenant1.global.vespa.oath.cloud"));
- List<String> globalDnsNames = tester.controller().routing().readDeclaredEndpointsOf(context.instanceId())
- .scope(Endpoint.Scope.global)
- .mapToList(Endpoint::dnsName);
- assertEquals(List.of("app1--tenant1.global.vespa.oath.cloud"), globalDnsNames);
+ globalDnsNamesByInstance.forEach((instance, dnsNames) -> {
+ List<String> actualDnsNames = tester.controller().routing().readDeclaredEndpointsOf(instance)
+ .scope(Endpoint.Scope.global)
+ .mapToList(Endpoint::dnsName);
+ assertEquals("Global DNS names for " + instance, dnsNames, actualDnsNames);
+ });
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index e65f6e087c5..7619cf71f1a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -280,7 +280,7 @@ public class MetricsReporterTest {
context.submit(applicationPackage).deploy();
reporter.maintain();
- assertEquals("Deployment queues name services requests", 15, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
+ assertEquals("Deployment queues name services requests", 6, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
context.flushDnsUpdates();
reporter.maintain();
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
index fbc17293e8d..89a77599909 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
@@ -187,7 +187,7 @@ public class FileReceiver {
private static void moveFileToDestination(File tempFile, File destination) {
try {
Files.move(tempFile.toPath(), destination.toPath());
- log.log(Level.FINE, () -> "File moved from " + tempFile.getAbsolutePath()+ " to " + destination.getAbsolutePath());
+ log.log(Level.FINEST, () -> "File moved from " + tempFile.getAbsolutePath()+ " to " + destination.getAbsolutePath());
} catch (FileAlreadyExistsException e) {
// Don't fail if it already exists (we might get the file from several config servers when retrying, servers are down etc.
// so it might be written already). Delete temp file/dir in that case, to avoid filling the disk.
@@ -239,7 +239,7 @@ public class FileReceiver {
}
private void receiveFilePart(Request req) {
- log.log(Level.FINE, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(Level.FINEST, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
FileReference reference = new FileReference(req.parameters().get(0).asString());
int sessionId = req.parameters().get(1).asInt32();
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 d72582287a9..ed12529f91e 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -295,7 +295,7 @@ public class Flags {
public static final UnboundBooleanFlag DELETE_UNMAINTAINED_CERTIFICATES = defineFeatureFlag(
"delete-unmaintained-certificates", false,
- List.of("andreer"), "2021-09-23", "2021-11-11",
+ List.of("andreer"), "2021-09-23", "2021-12-11",
"Whether to delete certificates that are known by provider but not by controller",
"Takes effect on next run of EndpointCertificateMaintainer"
);
diff --git a/searchcore/src/tests/proton/bucketdb/bucketdb/bucketdb_test.cpp b/searchcore/src/tests/proton/bucketdb/bucketdb/bucketdb_test.cpp
index 94300d4abac..8f10f4b8045 100644
--- a/searchcore/src/tests/proton/bucketdb/bucketdb/bucketdb_test.cpp
+++ b/searchcore/src/tests/proton/bucketdb/bucketdb/bucketdb_test.cpp
@@ -1,8 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/base/documentid.h>
#include <vespa/searchcore/proton/bucketdb/bucket_db_explorer.h>
#include <vespa/searchcore/proton/bucketdb/bucketdb.h>
+#include <vespa/searchcore/proton/bucketdb/remove_batch_entry.h>
#include <vespa/vespalib/data/slime/slime.h>
#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/log/log.h>
@@ -29,6 +32,24 @@ constexpr uint32_t DOCSIZE_2(10000u);
typedef BucketInfo::ReadyState RS;
typedef SubDbType SDT;
+namespace {
+
+constexpr uint32_t bucket_bits = 16;
+
+uint32_t num_buckets() { return (1u << bucket_bits); }
+
+BucketId make_bucket_id(uint32_t n) {
+ return BucketId(bucket_bits, n & (num_buckets() - 1));
+}
+
+GlobalId make_gid(uint32_t n, uint32_t i)
+{
+ DocumentId id(vespalib::make_string("id::test:n=%u:%u", n & (num_buckets() - 1), i));
+ return id.getGlobalId();
+}
+
+}
+
void
assertDocCount(uint32_t ready,
uint32_t notReady,
@@ -70,12 +91,20 @@ struct Fixture
Fixture()
: _db()
{}
+ void add(const GlobalId &gid, const Timestamp &timestamp, uint32_t docSize, SubDbType subDbType) {
+ BucketId bucket(bucket_bits, gid.convertToBucketId().getRawId());
+ _db.add(gid, bucket, timestamp, docSize, subDbType);
+ }
const BucketState &add(const Timestamp &timestamp, uint32_t docSize, SubDbType subDbType) {
return _db.add(GID_1, BUCKET_1, timestamp, docSize, subDbType);
}
const BucketState &add(const Timestamp &timestamp, SubDbType subDbType) {
return add(timestamp, DOCSIZE_1, subDbType);
}
+ void remove(const GlobalId& gid, const Timestamp &timestamp, uint32_t docSize, SubDbType subDbType) {
+ BucketId bucket(bucket_bits, gid.convertToBucketId().getRawId());
+ _db.remove(gid, bucket, timestamp, docSize, subDbType);
+ }
BucketState remove(const Timestamp &timestamp, uint32_t docSize, SubDbType subDbType) {
_db.remove(GID_1, BUCKET_1, timestamp, docSize, subDbType);
return get();
@@ -83,8 +112,14 @@ struct Fixture
BucketState remove(const Timestamp &timestamp, SubDbType subDbType) {
return remove(timestamp, DOCSIZE_1, subDbType);
}
+ void remove_batch(const std::vector<RemoveBatchEntry> &removed, SubDbType sub_db_type) {
+ _db.remove_batch(removed, sub_db_type);
+ }
+ BucketState get(BucketId bucket_id) const {
+ return _db.get(bucket_id);
+ }
BucketState get() const {
- return _db.get(BUCKET_1);
+ return get(BUCKET_1);
}
BucketChecksum getChecksum(const Timestamp &timestamp, uint32_t docSize, SubDbType subDbType) {
BucketDB db;
@@ -181,6 +216,36 @@ TEST_F("require that bucket checksum ignores document sizes", Fixture)
EXPECT_EQUAL(state1.getChecksum(), state2.getChecksum());
}
+TEST_F("require that remove batch works", Fixture)
+{
+ f.add(make_gid(4, 1), Timestamp(10), 100, SDT::READY);
+ f.add(make_gid(4, 2), Timestamp(11), 104, SDT::READY);
+ f.add(make_gid(4, 3), Timestamp(12), 102, SDT::READY);
+ f.add(make_gid(5, 4), Timestamp(13), 200, SDT::READY);
+ f.add(make_gid(5, 5), Timestamp(14), 270, SDT::READY);
+ f.add(make_gid(5, 6), Timestamp(15), 1000, SDT::READY);
+ auto state1 = f.get(make_bucket_id(4));
+ EXPECT_EQUAL(306u, state1.getReadyDocSizes());
+ EXPECT_EQUAL(3u, state1.getReadyCount());
+ auto state2 = f.get(make_bucket_id(5));
+ EXPECT_EQUAL(1470u, state2.getReadyDocSizes());
+ EXPECT_EQUAL(3u, state2.getReadyCount());
+ std::vector<RemoveBatchEntry> removed;
+ removed.emplace_back(make_gid(4, 1), make_bucket_id(4), Timestamp(10), 100);
+ removed.emplace_back(make_gid(4, 3), make_bucket_id(4), Timestamp(12), 102);
+ removed.emplace_back(make_gid(5, 5), make_bucket_id(5), Timestamp(14), 270);
+ f.remove_batch(removed, SDT::READY);
+ auto state3 = f.get(make_bucket_id(4));
+ EXPECT_EQUAL(104u, state3.getReadyDocSizes());
+ EXPECT_EQUAL(1u, state3.getReadyCount());
+ auto state4 = f.get(make_bucket_id(5));
+ EXPECT_EQUAL(1200u, state4.getReadyDocSizes());
+ EXPECT_EQUAL(2u, state4.getReadyCount());
+ f.remove(make_gid(4, 2), Timestamp(11), 104, SDT::READY);
+ f.remove(make_gid(5, 4), Timestamp(13), 200, SDT::READY);
+ f.remove(make_gid(5, 6), Timestamp(15), 1000, SDT::READY);
+}
+
TEST("require that bucket db can be explored")
{
BucketDBOwner db;
diff --git a/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp b/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp
index 139288f6b6f..1fadd3993ff 100644
--- a/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp
+++ b/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp
@@ -23,7 +23,7 @@ void checkMerge(const std::vector<double> &a, const std::vector<double> &b,
EXPECT_EQUAL(a.size() + b.size(), res_a.totalHits());
ASSERT_EQUAL(expect.size(), res_a.size());
for (size_t i = 0; i < expect.size(); ++i) {
- EXPECT_EQUAL(expect[i], res_a.hit(i)._rankValue);
+ EXPECT_EQUAL(expect[i], res_a.hit(i).getRank());
}
}
@@ -70,10 +70,10 @@ TEST("require that partial results can be created without sort data") {
res.totalHits(1000);
EXPECT_EQUAL(1000u, res.totalHits());
ASSERT_EQUAL(2u, res.size());
- EXPECT_EQUAL(1u, res.hit(0)._docId);
- EXPECT_EQUAL(10.0, res.hit(0)._rankValue);
- EXPECT_EQUAL(2u, res.hit(1)._docId);
- EXPECT_EQUAL(5.0, res.hit(1)._rankValue);
+ EXPECT_EQUAL(1u, res.hit(0).getDocId());
+ EXPECT_EQUAL(10.0, res.hit(0).getRank());
+ EXPECT_EQUAL(2u, res.hit(1).getDocId());
+ EXPECT_EQUAL(5.0, res.hit(1).getRank());
}
TEST("require that partial results can be created with sort data") {
@@ -90,12 +90,12 @@ TEST("require that partial results can be created with sort data") {
res.totalHits(1000);
EXPECT_EQUAL(1000u, res.totalHits());
ASSERT_EQUAL(2u, res.size());
- EXPECT_EQUAL(1u, res.hit(0)._docId);
- EXPECT_EQUAL(10.0, res.hit(0)._rankValue);
+ EXPECT_EQUAL(1u, res.hit(0).getDocId());
+ EXPECT_EQUAL(10.0, res.hit(0).getRank());
EXPECT_EQUAL(str1.data(), res.sortData(0).first);
EXPECT_EQUAL(str1.size(), res.sortData(0).second);
- EXPECT_EQUAL(2u, res.hit(1)._docId);
- EXPECT_EQUAL(5.0, res.hit(1)._rankValue);
+ EXPECT_EQUAL(2u, res.hit(1).getDocId());
+ EXPECT_EQUAL(5.0, res.hit(1).getRank());
EXPECT_EQUAL(str2.data(), res.sortData(1).first);
EXPECT_EQUAL(str2.size(), res.sortData(1).second);
}
@@ -133,10 +133,10 @@ TEST("require that lower docid is preferred when sorting on rank") {
res_c.add(search::RankedHit(1, 1.0));
res_a.merge(res_b);
ASSERT_EQUAL(1u, res_a.size());
- EXPECT_EQUAL(2u, res_a.hit(0)._docId);
+ EXPECT_EQUAL(2u, res_a.hit(0).getDocId());
res_a.merge(res_c);
ASSERT_EQUAL(1u, res_a.size());
- EXPECT_EQUAL(1u, res_a.hit(0)._docId);
+ EXPECT_EQUAL(1u, res_a.hit(0).getDocId());
}
TEST("require that lower docid is preferred when using sortspec") {
@@ -149,10 +149,10 @@ TEST("require that lower docid is preferred when using sortspec") {
res_c.add(search::RankedHit(1, 1.0), PartialResult::SortRef(foo.data(), foo.size()));
res_a.merge(res_b);
ASSERT_EQUAL(1u, res_a.size());
- EXPECT_EQUAL(2u, res_a.hit(0)._docId);
+ EXPECT_EQUAL(2u, res_a.hit(0).getDocId());
res_a.merge(res_c);
ASSERT_EQUAL(1u, res_a.size());
- EXPECT_EQUAL(1u, res_a.hit(0)._docId);
+ EXPECT_EQUAL(1u, res_a.hit(0).getDocId());
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
index 998da6b5789..c7a3103af22 100644
--- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
+++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp
@@ -1,14 +1,18 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "bucketdb.h"
+#include "remove_batch_entry.h"
#include <cassert>
#include <algorithm>
+#include <optional>
using document::GlobalId;
using storage::spi::BucketChecksum;
namespace proton {
+using bucketdb::RemoveBatchEntry;
+
BucketDB::BucketDB()
: _map(),
_cachedBucketId(),
@@ -65,6 +69,20 @@ BucketDB::remove(const GlobalId &gid,
}
void
+BucketDB::remove_batch(const std::vector<RemoveBatchEntry> &removed, SubDbType sub_db_type)
+{
+ std::optional<BucketId> prev_bucket_id;
+ BucketState* state = nullptr;
+ for (auto &entry : removed) {
+ if (!prev_bucket_id.has_value() || prev_bucket_id.value() != entry.get_bucket_id()) {
+ state = &_map[entry.get_bucket_id()];
+ prev_bucket_id = entry.get_bucket_id();
+ }
+ state->remove(entry.get_gid(), entry.get_timestamp(), entry.get_doc_size(), sub_db_type);
+ }
+}
+
+void
BucketDB::modify(const GlobalId &gid,
const BucketId &oldBucketId, const Timestamp &oldTimestamp, uint32_t oldDocSize,
const BucketId &newBucketId, const Timestamp &newTimestamp, uint32_t newDocSize,
diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
index 1723609e48e..2ea7594bde1 100644
--- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
+++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h
@@ -7,6 +7,8 @@
#include <vespa/persistence/spi/result.h>
#include <map>
+namespace proton::bucketdb { class RemoveBatchEntry; }
+
namespace proton {
class BucketDB
@@ -42,6 +44,8 @@ public:
const BucketId &bucketId, const Timestamp &timestamp, uint32_t docSize,
SubDbType subDbType);
+ void remove_batch(const std::vector<bucketdb::RemoveBatchEntry> &removed, SubDbType sub_db_type);
+
void modify(const GlobalId &gid,
const BucketId &oldBucketId, const Timestamp &oldTimestamp, uint32_t oldDocSize,
const BucketId &newBucketId, const Timestamp &newTimestamp, uint32_t newDocSize,
diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h b/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h
new file mode 100644
index 00000000000..1ab1adb1add
--- /dev/null
+++ b/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h
@@ -0,0 +1,36 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/document/base/globalid.h>
+#include <vespa/document/bucket/bucketid.h>
+#include <persistence/spi/types.h>
+
+
+namespace proton::bucketdb {
+
+/*
+ * Class containing meta data for a single document being removed from
+ * bucket db.
+ */
+class RemoveBatchEntry {
+ document::GlobalId _gid;
+ document::BucketId _bucket_id;
+ storage::spi::Timestamp _timestamp;
+ uint32_t _doc_size;
+public:
+ RemoveBatchEntry(const document::GlobalId& gid, const document::BucketId& bucket_id, const storage::spi::Timestamp& timestamp, uint32_t doc_size) noexcept
+ : _gid(gid),
+ _bucket_id(bucket_id),
+ _timestamp(timestamp),
+ _doc_size(doc_size)
+ {
+ }
+
+ const document::GlobalId& get_gid() const noexcept { return _gid; }
+ const document::BucketId& get_bucket_id() const noexcept { return _bucket_id; }
+ const storage::spi::Timestamp& get_timestamp() const noexcept { return _timestamp; }
+ uint32_t get_doc_size() const noexcept { return _doc_size; }
+};
+
+}
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
index 3d7e1c1c774..13d4a39c8b1 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
@@ -9,6 +9,7 @@
#include <vespa/persistence/spi/bucket_limits.h>
#include <vespa/searchcore/proton/bucketdb/bucketsessionbase.h>
#include <vespa/searchcore/proton/bucketdb/joinbucketssession.h>
+#include <vespa/searchcore/proton/bucketdb/remove_batch_entry.h>
#include <vespa/searchcore/proton/bucketdb/splitbucketsession.h>
#include <vespa/searchlib/attribute/load_utils.h>
#include <vespa/searchlib/attribute/readerbase.h>
@@ -30,6 +31,7 @@ LOG_SETUP(".proton.documentmetastore");
using document::BucketId;
using document::GlobalId;
using proton::bucketdb::BucketState;
+using proton::bucketdb::RemoveBatchEntry;
using proton::documentmetastore::GidToLidMapKey;
using search::AttributeVector;
using search::FileReader;
@@ -681,13 +683,15 @@ DocumentMetaStore::removeBatch(const std::vector<DocId> &lidsToRemove, const uin
remove_batch_internal_btree(removed);
_lidAlloc.unregister_lids(lidsToRemove);
{
- bucketdb::Guard bucketGuard = _bucketDB->takeGuard();
- // TODO: add remove_batch() method to BucketDB
+ std::vector<RemoveBatchEntry> bdb_removed;
+ bdb_removed.reserve(removed.size());
for (const auto& lid_and_meta : removed) {
auto& meta = lid_and_meta.second;
- bucketGuard->remove(meta.getGid(), meta.getBucketId().stripUnused(),
- meta.getTimestamp(), meta.getDocSize(), _subDbType);
+ bdb_removed.emplace_back(meta.getGid(), meta.getBucketId().stripUnused(),
+ meta.getTimestamp(), meta.getDocSize());
}
+ bucketdb::Guard bucketGuard = _bucketDB->takeGuard();
+ bucketGuard->remove_batch(bdb_removed, _subDbType);
}
incGeneration();
if (_op_listener) {
diff --git a/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp b/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp
index 6ae97a125ad..432752d69d0 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp
@@ -7,10 +7,10 @@ namespace proton::matching {
namespace {
bool before(const search::RankedHit &a, const search::RankedHit &b) {
- if (a._rankValue != b._rankValue) {
- return (a._rankValue > b._rankValue);
+ if (a.getRank() != b.getRank()) {
+ return (a.getRank() > b.getRank());
}
- return (a._docId < b._docId);
+ return (a.getDocId() < b.getDocId());
}
void mergeHits(size_t maxHits,
diff --git a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
index 341bd3bb855..f332ca5ec26 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
@@ -108,7 +108,7 @@ ResultProcessor::extract_docid_ordering(const PartialResult &result) const
std::vector<std::pair<uint32_t,uint32_t>> list;
list.reserve(est_size);
for (size_t i = _offset; i < result.size(); ++i) {
- list.emplace_back(result.hit(i)._docId, list.size());
+ list.emplace_back(result.hit(i).getDocId(), list.size());
}
std::sort(list.begin(), list.end(), [](const auto &a, const auto &b){ return (a.first < b.first); });
return list;
@@ -142,11 +142,11 @@ ResultProcessor::makeReply(PartialResultUP full_result)
for (size_t i = 0; i < hitcnt; ++i) {
search::engine::SearchReply::Hit &dst = r.hits[i];
const search::RankedHit &src = result.hit(hitOffset + i);
- uint32_t docId = src._docId;
+ uint32_t docId = src.getDocId();
if (metaStore.getGidEvenIfMoved(docId, gid)) {
dst.gid = gid;
}
- dst.metric = src._rankValue;
+ dst.metric = src.getRank();
LOG(debug, "convertLidToGid: hit[%zu]: lid(%u) -> gid(%s)", i, docId, dst.gid.toString().c_str());
}
if (result.hasSortData() && (hitcnt > 0)) {
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
index 2e1fc74037c..04d91b9c028 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp
@@ -364,7 +364,7 @@ PersistenceEngine::putAsync(const Bucket &bucket, Timestamp ts, storage::spi::Do
return onComplete->onComplete(std::make_unique<Result>(Result::ErrorType::PERMANENT_ERROR,
make_string("No handler for document type '%s'", docType.toString().c_str())));
}
- auto transportContext = std::make_shared<AsyncTranportContext>(1, std::move(onComplete));
+ auto transportContext = std::make_shared<AsyncTransportContext>(1, std::move(onComplete));
handler->handlePut(feedtoken::make(std::move(transportContext)), bucket, ts, std::move(doc));
}
@@ -384,7 +384,7 @@ PersistenceEngine::removeAsync(const Bucket& b, Timestamp t, const DocumentId& d
return onComplete->onComplete(std::make_unique<RemoveResult>(Result::ErrorType::PERMANENT_ERROR,
make_string("No handler for document type '%s'", docType.toString().c_str())));
}
- auto transportContext = std::make_shared<AsyncTranportContext>(1, std::move(onComplete));
+ auto transportContext = std::make_shared<AsyncTransportContext>(1, std::move(onComplete));
handler->handleRemove(feedtoken::make(std::move(transportContext)), b, t, did);
}
@@ -436,7 +436,7 @@ PersistenceEngine::updateAsync(const Bucket& b, Timestamp t, DocumentUpdate::SP
if (handler == nullptr) {
return onComplete->onComplete(std::make_unique<UpdateResult>(Result::ErrorType::PERMANENT_ERROR, make_string("No handler for document type '%s'", docType.toString().c_str())));
}
- auto transportContext = std::make_shared<AsyncTranportContext>(1, std::move(onComplete));
+ auto transportContext = std::make_shared<AsyncTransportContext>(1, std::move(onComplete));
handler->handleUpdate(feedtoken::make(std::move(transportContext)), b, t, std::move(upd));
}
@@ -555,7 +555,7 @@ PersistenceEngine::createBucketAsync(const Bucket &b, Context &, OperationComple
LOG(spam, "createBucket(%s)", b.toString().c_str());
HandlerSnapshot snap = getHandlerSnapshot(rguard, b.getBucketSpace());
- auto transportContext = std::make_shared<AsyncTranportContext>(snap.size(), std::move(onComplete));
+ auto transportContext = std::make_shared<AsyncTransportContext>(snap.size(), std::move(onComplete));
while (snap.handlers().valid()) {
IPersistenceHandler *handler = snap.handlers().get();
snap.handlers().next();
@@ -575,7 +575,7 @@ PersistenceEngine::deleteBucketAsync(const Bucket& b, Context&, OperationComplet
LOG(spam, "deleteBucket(%s)", b.toString().c_str());
HandlerSnapshot snap = getHandlerSnapshot(rguard, b.getBucketSpace());
- auto transportContext = std::make_shared<AsyncTranportContext>(snap.size(), std::move(onComplete));
+ auto transportContext = std::make_shared<AsyncTransportContext>(snap.size(), std::move(onComplete));
while (snap.handlers().valid()) {
IPersistenceHandler *handler = snap.handlers().get();
snap.handlers().next();
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
index 61c240f0d6a..8a0955b3147 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp
@@ -67,7 +67,7 @@ TransportLatch::send(ResultUP result, bool documentWasFound)
_latch.countDown();
}
-AsyncTranportContext::AsyncTranportContext(uint32_t cnt, OperationComplete::UP onComplete)
+AsyncTransportContext::AsyncTransportContext(uint32_t cnt, OperationComplete::UP onComplete)
: TransportMerger(cnt > 1),
_countDown(cnt),
_onComplete(std::move(onComplete))
@@ -78,17 +78,17 @@ AsyncTranportContext::AsyncTranportContext(uint32_t cnt, OperationComplete::UP o
}
void
-AsyncTranportContext::completeIfDone() {
+AsyncTransportContext::completeIfDone() {
_countDown--;
if (_countDown == 0) {
_onComplete->onComplete(std::move(_result));
}
}
-AsyncTranportContext::~AsyncTranportContext() = default;
+AsyncTransportContext::~AsyncTransportContext() = default;
void
-AsyncTranportContext::send(ResultUP result, bool documentWasFound)
+AsyncTransportContext::send(ResultUP result, bool documentWasFound)
{
mergeResult(std::move(result), documentWasFound);
}
diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
index 084ad0e8d10..91cadfedcd5 100644
--- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
+++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h
@@ -63,7 +63,7 @@ public:
* Implementation of FeedToken::ITransport for async handling of the async reply for an operation.
* Uses an internal count to keep track the number of the outstanding replies.
*/
-class AsyncTranportContext : public TransportMerger {
+class AsyncTransportContext : public TransportMerger {
private:
using Result = storage::spi::Result;
using OperationComplete = storage::spi::OperationComplete;
@@ -72,8 +72,8 @@ private:
OperationComplete::UP _onComplete;
void completeIfDone() override;
public:
- AsyncTranportContext(uint32_t cnt, OperationComplete::UP);
- ~AsyncTranportContext() override;
+ AsyncTransportContext(uint32_t cnt, OperationComplete::UP);
+ ~AsyncTransportContext() override;
void send(ResultUP result, bool documentWasFound) override;
};
diff --git a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
index 4076194542a..de54386e4af 100644
--- a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
+++ b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
@@ -487,7 +487,7 @@ SearchContextTest::checkResultSet(const ResultSet & rs, const DocSet & expected,
ASSERT_TRUE(array != nullptr);
uint32_t i = 0;
for (auto iter = expected.begin(); iter != expected.end(); ++iter, ++i) {
- EXPECT_TRUE(array[i]._docId == *iter);
+ EXPECT_TRUE(array[i].getDocId() == *iter);
}
}
}
@@ -1517,10 +1517,10 @@ SearchContextTest::requireThatSearchIsWorkingAfterClearDoc(const vespalib::strin
EXPECT_EQUAL(4u, rs->getNumHits());
ASSERT_TRUE(4u == rs->getNumHits());
const RankedHit * array = rs->getArray();
- EXPECT_EQUAL(1u, array[0]._docId);
- EXPECT_EQUAL(2u, array[1]._docId);
- EXPECT_EQUAL(3u, array[2]._docId);
- EXPECT_EQUAL(4u, array[3]._docId);
+ EXPECT_EQUAL(1u, array[0].getDocId());
+ EXPECT_EQUAL(2u, array[1].getDocId());
+ EXPECT_EQUAL(3u, array[2].getDocId());
+ EXPECT_EQUAL(4u, array[3].getDocId());
}
a->clearDoc(1);
a->clearDoc(3);
@@ -1529,8 +1529,8 @@ SearchContextTest::requireThatSearchIsWorkingAfterClearDoc(const vespalib::strin
ResultSetPtr rs = performSearch(v, term);
EXPECT_EQUAL(2u, rs->getNumHits());
const RankedHit * array = rs->getArray();
- EXPECT_EQUAL(2u, array[0]._docId);
- EXPECT_EQUAL(4u, array[1]._docId);
+ EXPECT_EQUAL(2u, array[0].getDocId());
+ EXPECT_EQUAL(4u, array[1].getDocId());
}
}
@@ -1578,9 +1578,9 @@ SearchContextTest::requireThatSearchIsWorkingAfterLoadAndClearDoc(const vespalib
const RankedHit * array = rs->getArray();
for (uint32_t i = 0; i < 14; ++i) {
if (i < 5) {
- EXPECT_EQUAL(i + 1, array[i]._docId);
+ EXPECT_EQUAL(i + 1, array[i].getDocId());
} else
- EXPECT_EQUAL(i + 2, array[i]._docId);
+ EXPECT_EQUAL(i + 2, array[i].getDocId());
}
}
ValueType buf;
@@ -1682,15 +1682,15 @@ SearchContextTest::requireThatFlagAttributeIsWorkingWhenNewDocsAreAdded()
{
ResultSetPtr rs = performSearch(fa, "<24");
EXPECT_EQUAL(2u, rs->getNumHits());
- EXPECT_EQUAL(1u, rs->getArray()[0]._docId);
- EXPECT_EQUAL(2u, rs->getArray()[1]._docId);
+ EXPECT_EQUAL(1u, rs->getArray()[0].getDocId());
+ EXPECT_EQUAL(2u, rs->getArray()[1].getDocId());
}
{
ResultSetPtr rs = performSearch(fa, "24");
EXPECT_EQUAL(3u, rs->getNumHits());
- EXPECT_EQUAL(1u, rs->getArray()[0]._docId);
- EXPECT_EQUAL(2u, rs->getArray()[1]._docId);
- EXPECT_EQUAL(4u, rs->getArray()[2]._docId);
+ EXPECT_EQUAL(1u, rs->getArray()[0].getDocId());
+ EXPECT_EQUAL(2u, rs->getArray()[1].getDocId());
+ EXPECT_EQUAL(4u, rs->getArray()[2].getDocId());
}
}
{
@@ -1717,15 +1717,15 @@ SearchContextTest::requireThatFlagAttributeIsWorkingWhenNewDocsAreAdded()
EXPECT_EQUAL(exp50.size(), rs1->getNumHits());
EXPECT_EQUAL(exp50.size(), rs2->getNumHits());
for (size_t j = 0; j < exp50.size(); ++j) {
- EXPECT_EQUAL(exp50[j], rs1->getArray()[j]._docId);
- EXPECT_EQUAL(exp50[j], rs2->getArray()[j]._docId);
+ EXPECT_EQUAL(exp50[j], rs1->getArray()[j].getDocId());
+ EXPECT_EQUAL(exp50[j], rs2->getArray()[j].getDocId());
}
}
{
ResultSetPtr rs = performSearch(fa, "60");
EXPECT_EQUAL(exp60.size(), rs->getNumHits());
for (size_t j = 0; j < exp60.size(); ++j) {
- EXPECT_EQUAL(exp60[j], rs->getArray()[j]._docId);
+ EXPECT_EQUAL(exp60[j], rs->getArray()[j].getDocId());
}
}
}
diff --git a/searchlib/src/tests/grouping/grouping_test.cpp b/searchlib/src/tests/grouping/grouping_test.cpp
index ef4930de8ce..2eab66cb3b7 100644
--- a/searchlib/src/tests/grouping/grouping_test.cpp
+++ b/searchlib/src/tests/grouping/grouping_test.cpp
@@ -105,7 +105,7 @@ public:
hit._rankValue = rank;
_hits.push_back(hit);
for (uint32_t pos = (_hits.size() - 1);
- pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue);
+ pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank());
--pos)
{
std::swap(_hits[pos], _hits[pos - 1]);
diff --git a/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp b/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp
index 66fa359f1a3..e82079073e7 100644
--- a/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp
+++ b/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp
@@ -88,7 +88,7 @@ public:
hit._rankValue = rank;
_hits.push_back(hit);
for (uint32_t pos = (_hits.size() - 1);
- pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue);
+ pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank());
--pos)
{
std::swap(_hits[pos], _hits[pos - 1]);
diff --git a/searchlib/src/tests/groupingengine/groupingengine_test.cpp b/searchlib/src/tests/groupingengine/groupingengine_test.cpp
index a0179c36c23..d54b68388e4 100644
--- a/searchlib/src/tests/groupingengine/groupingengine_test.cpp
+++ b/searchlib/src/tests/groupingengine/groupingengine_test.cpp
@@ -87,7 +87,7 @@ public:
hit._rankValue = rank;
_hits.push_back(hit);
for (uint32_t pos = (_hits.size() - 1);
- pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue);
+ pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank());
--pos)
{
std::swap(_hits[pos], _hits[pos - 1]);
diff --git a/searchlib/src/tests/hitcollector/hitcollector_test.cpp b/searchlib/src/tests/hitcollector/hitcollector_test.cpp
index 617e0e85824..ed68c47ea23 100644
--- a/searchlib/src/tests/hitcollector/hitcollector_test.cpp
+++ b/searchlib/src/tests/hitcollector/hitcollector_test.cpp
@@ -70,8 +70,8 @@ void checkResult(const ResultSet & rs, const std::vector<RankedHit> & exp)
ASSERT_EQUAL(rs.getArrayUsed(), exp.size());
for (uint32_t i = 0; i < exp.size(); ++i) {
- EXPECT_EQUAL(rh[i]._docId, exp[i]._docId);
- EXPECT_EQUAL(rh[i]._rankValue + 1.0, exp[i]._rankValue + 1.0);
+ EXPECT_EQUAL(rh[i].getDocId(), exp[i].getDocId());
+ EXPECT_EQUAL(rh[i].getRank() + 1.0, exp[i].getRank() + 1.0);
}
} else {
ASSERT_TRUE(rs.getArray() == nullptr);
diff --git a/searchlib/src/tests/sortresults/sorttest.cpp b/searchlib/src/tests/sortresults/sorttest.cpp
index cd892800ca5..bbd6d0b72ce 100644
--- a/searchlib/src/tests/sortresults/sorttest.cpp
+++ b/searchlib/src/tests/sortresults/sorttest.cpp
@@ -41,17 +41,17 @@ test_sort(unsigned int caseNum, unsigned int n, unsigned int ntop)
}
FastS_SortResults(array, n, ntop);
- minmax = array[ntop - 1]._rankValue;
+ minmax = array[ntop - 1].getRank();
for(i = 0; i < n; i++) {
if (i < ntop && i > 0
- && array[i]._rankValue > array[i - 1]._rankValue) {
+ && array[i].getRank() > array[i - 1].getRank()) {
printf("ERROR: rank(%d) > rank(%d)\n",
i, i - 1);
ok = false;
break;
}
if (i >= ntop &&
- array[i]._rankValue > minmax) {
+ array[i].getRank() > minmax) {
printf("ERROR: rank(%d) > rank(%d)\n",
i, ntop - 1);
ok = false;
diff --git a/searchlib/src/tests/sortspec/multilevelsort.cpp b/searchlib/src/tests/sortspec/multilevelsort.cpp
index f438fce0e7f..576e1d1336c 100644
--- a/searchlib/src/tests/sortspec/multilevelsort.cpp
+++ b/searchlib/src/tests/sortspec/multilevelsort.cpp
@@ -275,21 +275,21 @@ MultilevelSortTest::sortAndCheck(const std::vector<Spec> &spec, uint32_t num,
for (uint32_t j = 0; j < spec.size(); ++j) {
int cmp = 0;
if (spec[j]._type == RANK) {
- if (hits[i]._rankValue < hits[i+1]._rankValue) {
+ if (hits[i].getRank() < hits[i+1].getRank()) {
cmp = -1;
- } else if (hits[i]._rankValue > hits[i+1]._rankValue) {
+ } else if (hits[i].getRank() > hits[i+1].getRank()) {
cmp = 1;
}
} else if (spec[j]._type == DOCID) {
- if (hits[i]._docId < hits[i+1]._docId) {
+ if (hits[i].getDocId() < hits[i+1].getDocId()) {
cmp = -1;
- } else if (hits[i]._docId > hits[i+1]._docId) {
+ } else if (hits[i].getDocId() > hits[i+1].getDocId()) {
cmp = 1;
}
} else {
AttributeVector *av = vec[spec[j]._name].get();
cmp = compare(av, spec[j]._type,
- hits[i]._docId, hits[i+1]._docId);
+ hits[i].getDocId(), hits[i+1].getDocId());
}
if (spec[j]._asc) {
EXPECT_TRUE(cmp <= 0);
diff --git a/searchlib/src/vespa/searchlib/aggregation/grouping.cpp b/searchlib/src/vespa/searchlib/aggregation/grouping.cpp
index 68098d6c35a..f373b5fc0b3 100644
--- a/searchlib/src/vespa/searchlib/aggregation/grouping.cpp
+++ b/searchlib/src/vespa/searchlib/aggregation/grouping.cpp
@@ -205,13 +205,13 @@ void Grouping::postProcess()
void Grouping::aggregateWithoutClock(const RankedHit * rankedHit, unsigned int len) {
for(unsigned int i(0); i < len; i++) {
- aggregate(rankedHit[i]._docId, rankedHit[i]._rankValue);
+ aggregate(rankedHit[i].getDocId(), rankedHit[i].getRank());
}
}
void Grouping::aggregateWithClock(const RankedHit * rankedHit, unsigned int len) {
for(unsigned int i(0); (i < len) && !hasExpired(); i++) {
- aggregate(rankedHit[i]._docId, rankedHit[i]._rankValue);
+ aggregate(rankedHit[i].getDocId(), rankedHit[i].getRank());
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h b/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h
index b31e726b103..fdf9ab624ad 100644
--- a/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h
+++ b/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h
@@ -29,7 +29,7 @@ public:
uint64_t
operator()(const LoadedEnumAttribute &v)
{
- return (static_cast<uint64_t>(v._enum) << 32) | v._docId;
+ return (static_cast<uint64_t>(v._enum) << 32) | v.getDocId();
}
};
diff --git a/searchlib/src/vespa/searchlib/attribute/loadedvalue.h b/searchlib/src/vespa/searchlib/attribute/loadedvalue.h
index 701ccdc902c..b8f938838d2 100644
--- a/searchlib/src/vespa/searchlib/attribute/loadedvalue.h
+++ b/searchlib/src/vespa/searchlib/attribute/loadedvalue.h
@@ -103,9 +103,10 @@ public:
T _value;
uint32_t _eidx;
};
+
uint32_t _docId;
uint32_t _idx;
- vespalib::datastore::EntryRef _pidx;
+ vespalib::datastore::EntryRef _pidx;
private:
int32_t _weight;
Value _value;
diff --git a/searchlib/src/vespa/searchlib/common/sortresults.cpp b/searchlib/src/vespa/searchlib/common/sortresults.cpp
index 7a54de708d0..7510ae162ce 100644
--- a/searchlib/src/vespa/searchlib/common/sortresults.cpp
+++ b/searchlib/src/vespa/searchlib/common/sortresults.cpp
@@ -51,7 +51,7 @@ FastS_insertion_sort(RankedHit a[], uint32_t n)
for (i=1; i<n ; i++) {
swap = a[i];
j = i;
- while (R(swap._rankValue) > R(a[j-1]._rankValue)) {
+ while (R(swap.getRank()) > R(a[j-1].getRank())) {
a[j] = a[j-1];
if (!(--j)) break;;
}
@@ -74,13 +74,13 @@ FastS_radixsort(RankedHit a[], uint32_t n, uint32_t ntop)
memset(cnt, 0, 256*sizeof(uint32_t));
// Count occurrences [NB: will fail with n < 3]
for(i = 0; i < n - 3; i += 4) {
- cnt[(R(a[i]._rankValue) >> SHIFT) & 0xFF]++;
- cnt[(R(a[i + 1]._rankValue) >> SHIFT) & 0xFF]++;
- cnt[(R(a[i + 2]._rankValue) >> SHIFT) & 0xFF]++;
- cnt[(R(a[i + 3]._rankValue) >> SHIFT) & 0xFF]++;
+ cnt[(R(a[i].getRank()) >> SHIFT) & 0xFF]++;
+ cnt[(R(a[i + 1].getRank()) >> SHIFT) & 0xFF]++;
+ cnt[(R(a[i + 2].getRank()) >> SHIFT) & 0xFF]++;
+ cnt[(R(a[i + 3].getRank()) >> SHIFT) & 0xFF]++;
}
for(; i < n; i++)
- cnt[(R(a[i]._rankValue) >> SHIFT) & 0xFF]++;
+ cnt[(R(a[i].getRank()) >> SHIFT) & 0xFF]++;
// Accumulate cnt positions
sorted = (cnt[0]==n);
@@ -109,14 +109,14 @@ FastS_radixsort(RankedHit a[], uint32_t n, uint32_t ntop)
// Grab first element to move
j = ptr[i];
swap = a[j];
- k = (R(swap._rankValue) >> SHIFT) & 0xFF;
+ k = (R(swap.getRank()) >> SHIFT) & 0xFF;
// Swap into correct class until cycle completed
if (i!=k) {
do {
temp = a[ptr[k]];
a[ptr[k]++] = swap;
- k = (R((swap = temp)._rankValue) >> SHIFT) & 0xFF;
+ k = (R((swap = temp).getRank()) >> SHIFT) & 0xFF;
remain--;
} while (i!=k);
// Place last element in cycle
@@ -265,11 +265,11 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n)
written = sizeof(hits->_docId) + sizeof(_partitionId);
break;
case ASC_RANK:
- serializeForSort<convertForSort<search::HitRank, true> >(hits[i]._rankValue, mySortData);
+ serializeForSort<convertForSort<search::HitRank, true> >(hits[i].getRank(), mySortData);
written = sizeof(hits->_rankValue);
break;
case DESC_RANK:
- serializeForSort<convertForSort<search::HitRank, false> >(hits[i]._rankValue, mySortData);
+ serializeForSort<convertForSort<search::HitRank, false> >(hits[i].getRank(), mySortData);
written = sizeof(hits->_rankValue);
break;
case ASC_VECTOR:
diff --git a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp
index 77c2aa3072a..ea278ebf607 100644
--- a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp
+++ b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp
@@ -18,7 +18,7 @@ TermFieldMatchData::TermFieldMatchData() :
}
TermFieldMatchData::TermFieldMatchData(const TermFieldMatchData & rhs) :
- _docId(rhs._docId),
+ _docId(rhs.getDocId()),
_fieldId(rhs._fieldId),
_flags(rhs._flags),
_sz(0),
diff --git a/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h b/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h
index f8328d15289..429eea038c9 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h
+++ b/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h
@@ -21,6 +21,7 @@ private:
struct WordFieldDocTuple {
vespalib::datastore::EntryRef _wordRef;
uint32_t _docId;
+
WordFieldDocTuple() noexcept :
_wordRef(0),
_docId(0)
diff --git a/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp b/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp
index b2b1d49bae9..3293019e538 100644
--- a/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp
@@ -195,7 +195,7 @@ mergeHitsIntoResultSet(const std::vector<HitCollector::Hit> &hits, ResultSet &re
uint32_t rhCur(0);
uint32_t rhEnd(result.getArrayUsed());
for (const auto &hit : hits) {
- while (rhCur != rhEnd && result[rhCur]._docId != hit.first) {
+ while (rhCur != rhEnd && result[rhCur].getDocId() != hit.first) {
// just set the iterators right
++rhCur;
}
diff --git a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
index a94f6087a0d..d0a75930ed5 100644
--- a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
+++ b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
@@ -77,8 +77,8 @@ public:
bool operator<(const PendingOp &rhs) const {
if (_wordIdx != rhs._wordIdx)
return _wordIdx < rhs._wordIdx;
- if (_docId != rhs._docId)
- return _docId < rhs._docId;
+ if (_docId != rhs.getDocId())
+ return _docId < rhs.getDocId();
return _seq < rhs._seq;
}
};