diff options
author | Jon Bratseth <bratseth@oath.com> | 2021-09-27 23:08:47 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-27 23:08:47 +0200 |
commit | 8f3fb1a105ded07144f6de527266a438e48a1766 (patch) | |
tree | d2923a45682e91d80e7011c60cfb301e05acead3 | |
parent | 0a858cc0f3425fc792d978b6221ef4f3c3bc6067 (diff) | |
parent | 037f756caf4cfb99bcd988174839d7bc385267b9 (diff) |
Merge branch 'master' into bratseth/linguistics-components
157 files changed, 1688 insertions, 795 deletions
diff --git a/client/go/cmd/api_key.go b/client/go/cmd/api_key.go index a838f1a05c8..b3284daa993 100644 --- a/client/go/cmd/api_key.go +++ b/client/go/cmd/api_key.go @@ -77,6 +77,6 @@ func printPublicKey(apiKeyFile, tenant string) { log.Printf("\nThis is your public key:\n%s", color.Green(pemPublicKey)) log.Printf("Its fingerprint is:\n%s\n", color.Cyan(fingerprint)) log.Print("\nTo use this key in Vespa Cloud click 'Add custom key' at") - log.Printf(color.Cyan("%s/tenant/%s/keys").String(), defaultConsoleURL, tenant) + log.Printf(color.Cyan("%s/tenant/%s/keys").String(), getConsoleURL(), tenant) log.Print("and paste the entire public key including the BEGIN and END lines.") } diff --git a/client/go/cmd/api_key_test.go b/client/go/cmd/api_key_test.go index 2497568604f..1deb628c21e 100644 --- a/client/go/cmd/api_key_test.go +++ b/client/go/cmd/api_key_test.go @@ -4,20 +4,20 @@ package cmd import ( - "strings" + "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestAPIKey(t *testing.T) { - homeDir := t.TempDir() - keyFile := homeDir + "/.vespa/t1.api-key.pem" + homeDir := filepath.Join(t.TempDir(), ".vespa") + keyFile := filepath.Join(homeDir, "t1.api-key.pem") out, _ := execute(command{args: []string{"api-key", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil) - assert.True(t, strings.HasPrefix(out, "Success: API private key written to "+keyFile+"\n")) + assert.Contains(t, out, "Success: API private key written to "+keyFile+"\n") out, _ = execute(command{args: []string{"api-key", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil) - assert.True(t, strings.HasPrefix(out, "Error: File "+keyFile+" already exists\nHint: Use -f to overwrite it\n")) - assert.True(t, strings.Contains(out, "This is your public key")) + assert.Contains(t, out, "Error: File "+keyFile+" already exists\nHint: Use -f to overwrite it\n") + assert.Contains(t, out, "This is your public key") } diff --git a/client/go/cmd/cert_test.go b/client/go/cmd/cert_test.go index d93def2fa70..cd5f88764b9 100644 --- a/client/go/cmd/cert_test.go +++ b/client/go/cmd/cert_test.go @@ -14,7 +14,7 @@ import ( ) func TestCert(t *testing.T) { - homeDir := t.TempDir() + homeDir := filepath.Join(t.TempDir(), ".vespa") pkgDir := mockApplicationPackage(t, false) out, _ := execute(command{args: []string{"cert", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil) @@ -23,8 +23,8 @@ func TestCert(t *testing.T) { appDir := filepath.Join(pkgDir, "src", "main", "application") pkgCertificate := filepath.Join(appDir, "security", "clients.pem") - certificate := filepath.Join(homeDir, ".vespa", app.String(), "data-plane-public-cert.pem") - privateKey := filepath.Join(homeDir, ".vespa", app.String(), "data-plane-private-key.pem") + certificate := filepath.Join(homeDir, app.String(), "data-plane-public-cert.pem") + privateKey := filepath.Join(homeDir, app.String(), "data-plane-private-key.pem") assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Certificate written to %s\nSuccess: Private key written to %s\n", pkgCertificate, certificate, privateKey), out) @@ -33,7 +33,7 @@ func TestCert(t *testing.T) { } func TestCertCompressedPackage(t *testing.T) { - homeDir := t.TempDir() + homeDir := filepath.Join(t.TempDir(), ".vespa") pkgDir := mockApplicationPackage(t, true) zipFile := filepath.Join(pkgDir, "target", "application.zip") err := os.MkdirAll(filepath.Dir(zipFile), 0755) diff --git a/client/go/cmd/clone.go b/client/go/cmd/clone.go index 9503a81debf..508ad49438f 100644 --- a/client/go/cmd/clone.go +++ b/client/go/cmd/clone.go @@ -6,11 +6,10 @@ package cmd import ( "archive/zip" + "errors" "io" - "io/ioutil" "log" "net/http" - "net/url" "os" "path/filepath" "strings" @@ -20,22 +19,29 @@ import ( "github.com/vespa-engine/vespa/client/go/util" ) -// Set this to test without downloading this file from github -var existingSampleAppsZip string +const sampleAppsCacheTTL = time.Hour * 168 // 1 week + var listApps bool +var forceClone bool func init() { rootCmd.AddCommand(cloneCmd) cloneCmd.Flags().BoolVarP(&listApps, "list", "l", false, "List available sample applications") + cloneCmd.Flags().BoolVarP(&forceClone, "force", "f", false, "Ignore cache and force downloading the latest sample application from GitHub") } var cloneCmd = &cobra.Command{ - // TODO: "application" and "list" subcommands? Use: "clone sample-application-path target-directory", Short: "Create files and directory structure for a new Vespa application from a sample application", - Long: `Creates an application package file structure. + Long: `Create files and directory structure for a new Vespa application +from a sample application. + +Sample applications are downloaded from +https://github.com/vespa-engine/sample-apps. -The application package is copied from a sample application in https://github.com/vespa-engine/sample-apps`, +By default sample applications are cached in the user's cache directory. This +directory can be overriden by setting the VESPA_CLI_CACHE_DIR environment +variable.`, Example: "$ vespa clone vespa-cloud/album-recommendation my-app", DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { @@ -60,12 +66,7 @@ The application package is copied from a sample application in https://github.co func cloneApplication(source string, name string) { zipFile := getSampleAppsZip() - if zipFile == nil { - return - } - if existingSampleAppsZip == "" { // Indicates we created a temp file now - defer os.Remove(zipFile.Name()) - } + defer zipFile.Close() zipReader, zipOpenError := zip.OpenReader(zipFile.Name()) if zipOpenError != nil { @@ -101,45 +102,67 @@ func cloneApplication(source string, name string) { } } +func openOutputFile() (*os.File, error) { + cacheDir, err := vespaCliCacheDir() + if err != nil { + return nil, err + } + cacheFile := filepath.Join(cacheDir, "sample-apps-master.zip") + return os.OpenFile(cacheFile, os.O_RDWR|os.O_CREATE, 0755) +} + +func useCache(cacheFile *os.File) (bool, error) { + if forceClone { + return false, nil + } + stat, err := cacheFile.Stat() + if errors.Is(err, os.ErrNotExist) { + return false, nil + } else if err != nil { + return false, err + } + expiry := stat.ModTime().Add(sampleAppsCacheTTL) + return stat.Size() > 0 && time.Now().Before(expiry), nil +} + func getSampleAppsZip() *os.File { - if existingSampleAppsZip != "" { - existing, openExistingError := os.Open(existingSampleAppsZip) - if openExistingError != nil { - printErr(openExistingError, "Could not open existing sample apps zip file '", color.Cyan(existingSampleAppsZip), "'") - } - return existing + f, err := openOutputFile() + if err != nil { + fatalErr(err, "Could not determine location of cache file") + return nil + } + useCache, err := useCache(f) + if err != nil { + fatalErr(err, "Could not determine cache status", "Try ignoring the cache with the -f flag") + return nil + } + if useCache { + log.Print(color.Yellow("Using cached sample apps ...")) + return f } - // TODO: Cache it? log.Print(color.Yellow("Downloading sample apps ...")) // TODO: Spawn thread to indicate progress - zipUrl, _ := url.Parse("https://github.com/vespa-engine/sample-apps/archive/refs/heads/master.zip") - request := &http.Request{ - URL: zipUrl, - Method: "GET", + request, err := http.NewRequest("GET", "https://github.com/vespa-engine/sample-apps/archive/refs/heads/master.zip", nil) + if err != nil { + fatalErr(err, "Invalid URL") + return nil } - response, reqErr := util.HttpDo(request, time.Minute*60, "GitHub") - if reqErr != nil { - printErr(reqErr, "Could not download sample apps from GitHub") + response, err := util.HttpDo(request, time.Minute*60, "GitHub") + if err != nil { + fatalErr(err, "Could not download sample apps from GitHub") return nil } defer response.Body.Close() if response.StatusCode != 200 { - printErr(nil, "Could not download sample apps from GitHub: ", response.StatusCode) + fatalErr(nil, "Could not download sample apps from GitHub: ", response.StatusCode) return nil } - destination, tempFileError := ioutil.TempFile("", "prefix") - if tempFileError != nil { - printErr(tempFileError, "Could not create a temporary file to hold sample apps") - } - // destination, _ := os.Create("./" + name + "/sample-apps.zip") - // defer destination.Close() - _, err := io.Copy(destination, response.Body) - if err != nil { - printErr(err, "Could not download sample apps from GitHub") + if _, err := io.Copy(f, response.Body); err != nil { + fatalErr(err, "Could not write sample apps to file: ", f.Name()) return nil } - return destination + return f } func copy(f *zip.File, destinationDir string, zipEntryPrefix string) error { diff --git a/client/go/cmd/clone_test.go b/client/go/cmd/clone_test.go index 5027c5bf972..6cf11dd4d40 100644 --- a/client/go/cmd/clone_test.go +++ b/client/go/cmd/clone_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/vespa-engine/vespa/client/go/util" @@ -18,10 +19,14 @@ func TestClone(t *testing.T) { } func assertCreated(sampleAppName string, app string, t *testing.T) { - existingSampleAppsZip = "testdata/sample-apps-master.zip" - standardOut := executeCommand(t, &mockHttpClient{}, []string{"clone", sampleAppName, app}, []string{}) + testFile := filepath.Join("testdata", "sample-apps-master.zip") + now := time.Now() + if err := os.Chtimes(testFile, now, now); err != nil { // Ensure test file is considered new enough by cache mechanism + t.Fatal(err) + } + out, _ := execute(command{cacheDir: filepath.Dir(testFile), args: []string{"clone", sampleAppName, app}}, t, nil) defer os.RemoveAll(app) - assert.Equal(t, "Created "+app+"\n", standardOut) + assert.Equal(t, "Using cached sample apps ...\nCreated "+app+"\n", out) assert.True(t, util.PathExists(filepath.Join(app, "README.md"))) assert.True(t, util.PathExists(filepath.Join(app, "src", "main", "application"))) assert.True(t, util.IsDirectory(filepath.Join(app, "src", "main", "application"))) diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester.go index f455ffa9957..6929b59decb 100644 --- a/client/go/cmd/command_tester.go +++ b/client/go/cmd/command_tester.go @@ -22,6 +22,7 @@ import ( type command struct { homeDir string + cacheDir string args []string moreArgs []string } @@ -31,12 +32,16 @@ func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string) util.ActiveHttpClient = client } - // Set config dir. Use a separate one per test if none is specified + // Set Vespa CLI directories. Use a separate one per test if none is specified if cmd.homeDir == "" { - cmd.homeDir = t.TempDir() + cmd.homeDir = filepath.Join(t.TempDir(), ".vespa") viper.Reset() } - os.Setenv("VESPA_CLI_HOME", filepath.Join(cmd.homeDir, ".vespa")) + if cmd.cacheDir == "" { + cmd.cacheDir = filepath.Join(t.TempDir(), ".cache", "vespa") + } + os.Setenv("VESPA_CLI_HOME", cmd.homeDir) + os.Setenv("VESPA_CLI_CACHE_DIR", cmd.cacheDir) // Reset flags to their default value - persistent flags in Cobra persists over tests rootCmd.Flags().VisitAll(func(f *pflag.Flag) { @@ -111,5 +116,3 @@ func (c *mockHttpClient) Do(request *http.Request, timeout time.Duration) (*http } func (c *mockHttpClient) UseCertificate(certificate tls.Certificate) {} - -func convergeServices(client *mockHttpClient) { client.NextResponse(200, `{"converged":true}`) } diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go index 3753d9a9390..863f247bd7c 100644 --- a/client/go/cmd/config.go +++ b/client/go/cmd/config.go @@ -34,8 +34,16 @@ func init() { } var configCmd = &cobra.Command{ - Use: "config", - Short: "Configure default values for flags", + Use: "config", + Short: "Configure persistent values for flags", + Long: `Configure persistent values for flags. + +This command allows setting a persistent value for a given flag. On future +invocations the flag can then be omitted as it is read from the config file +instead. + +Configuration is written to $HOME/.vespa by default. This path can be +overridden by setting the VESPA_CLI_HOME environment variable.`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { // Root command does nothing @@ -100,16 +108,8 @@ type Config struct { } func LoadConfig() (*Config, error) { - home := os.Getenv("VESPA_CLI_HOME") - if home == "" { - var err error - home, err = os.UserHomeDir() - if err != nil { - return nil, err - } - home = filepath.Join(home, ".vespa") - } - if err := os.MkdirAll(home, 0700); err != nil { + home, err := vespaCliHome() + if err != nil { return nil, err } c := &Config{Home: home, createDirs: true} diff --git a/client/go/cmd/config_test.go b/client/go/cmd/config_test.go index cf50f561f0f..25ba7cc0655 100644 --- a/client/go/cmd/config_test.go +++ b/client/go/cmd/config_test.go @@ -1,13 +1,14 @@ package cmd import ( + "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestConfig(t *testing.T) { - homeDir := t.TempDir() + homeDir := filepath.Join(t.TempDir(), ".vespa") assertConfigCommand(t, "invalid option or value: \"foo\": \"bar\"\n", homeDir, "config", "set", "foo", "bar") assertConfigCommand(t, "foo = <unset>\n", homeDir, "config", "get", "foo") assertConfigCommand(t, "target = local\n", homeDir, "config", "get", "target") diff --git a/client/go/cmd/curl_test.go b/client/go/cmd/curl_test.go index 340eacd0bd3..d5021e19cf2 100644 --- a/client/go/cmd/curl_test.go +++ b/client/go/cmd/curl_test.go @@ -10,13 +10,12 @@ import ( ) func TestCurl(t *testing.T) { - homeDir := t.TempDir() + homeDir := filepath.Join(t.TempDir(), ".vespa") httpClient := &mockHttpClient{} - convergeServices(httpClient) out, _ := execute(command{homeDir: homeDir, args: []string{"curl", "-n", "-a", "t1.a1.i1", "--", "-v", "--data-urlencode", "arg=with space", "/search"}}, t, httpClient) expected := fmt.Sprintf("curl --key %s --cert %s -v --data-urlencode 'arg=with space' https://127.0.0.1:8080/search\n", - filepath.Join(homeDir, ".vespa", "t1.a1.i1", "data-plane-private-key.pem"), - filepath.Join(homeDir, ".vespa", "t1.a1.i1", "data-plane-public-cert.pem")) + filepath.Join(homeDir, "t1.a1.i1", "data-plane-private-key.pem"), + filepath.Join(homeDir, "t1.a1.i1", "data-plane-public-cert.pem")) assert.Equal(t, expected, out) } diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go index 9bf59187778..b3171d184e0 100644 --- a/client/go/cmd/deploy.go +++ b/client/go/cmd/deploy.go @@ -39,7 +39,11 @@ When this returns successfully the application package has been validated and activated on config servers. The process of applying it on individual nodes has started but may not have completed. -If application directory is not specified, it defaults to working directory.`, +If application directory is not specified, it defaults to working directory. + +When deploying to Vespa Cloud the system can be overridden by setting the +environment variable VESPA_CLI_CLOUD_SYSTEM. This is intended for internal use +only.`, Example: "$ vespa deploy .", Args: cobra.MaximumNArgs(1), DisableAutoGenTag: true, @@ -78,7 +82,7 @@ If application directory is not specified, it defaults to working directory.`, if opts.IsCloud() { log.Printf("\nUse %s for deployment status, or follow this deployment at", color.Cyan("vespa status")) log.Print(color.Cyan(fmt.Sprintf("%s/tenant/%s/application/%s/dev/instance/%s/job/%s-%s/run/%d", - defaultConsoleURL, + getConsoleURL(), opts.Deployment.Application.Tenant, opts.Deployment.Application.Application, opts.Deployment.Application.Instance, opts.Deployment.Zone.Environment, opts.Deployment.Zone.Region, sessionOrRunID))) diff --git a/client/go/cmd/deploy_test.go b/client/go/cmd/deploy_test.go index 443f7e8846f..9614806b968 100644 --- a/client/go/cmd/deploy_test.go +++ b/client/go/cmd/deploy_test.go @@ -130,7 +130,7 @@ func assertActivate(applicationPackage string, arguments []string, t *testing.T) if err := cfg.WriteSessionID(vespa.DefaultApplication, 42); err != nil { t.Fatal(err) } - out, _ := execute(command{args: arguments, homeDir: homeDir}, t, client) + out, _ := execute(command{args: arguments, homeDir: cfg.Home}, t, client) assert.Equal(t, "Success: Activated "+applicationPackage+" with session 42\n", out) diff --git a/client/go/cmd/document_test.go b/client/go/cmd/document_test.go index 8aecb538f89..1f82b85f915 100644 --- a/client/go/cmd/document_test.go +++ b/client/go/cmd/document_test.go @@ -67,7 +67,6 @@ func TestDocumentRemoveWithoutIdArg(t *testing.T) { func TestDocumentSendMissingId(t *testing.T) { arguments := []string{"document", "put", "testdata/A-Head-Full-of-Dreams-Without-Operation.json"} client := &mockHttpClient{} - convergeServices(client) assert.Equal(t, "Error: No document id given neither as argument or as a 'put' key in the json file\n", executeCommand(t, client, arguments, []string{})) @@ -76,7 +75,6 @@ func TestDocumentSendMissingId(t *testing.T) { func TestDocumentSendWithDisagreeingOperations(t *testing.T) { arguments := []string{"document", "update", "testdata/A-Head-Full-of-Dreams-Put.json"} client := &mockHttpClient{} - convergeServices(client) assert.Equal(t, "Error: Wanted document operation is update but the JSON file specifies put\n", executeCommand(t, client, arguments, []string{})) @@ -140,7 +138,6 @@ func assertDocumentGet(arguments []string, documentId string, t *testing.T) { func assertDocumentError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{} - convergeServices(client) client.NextResponse(status, errorMessage) assert.Equal(t, "Error: Invalid document operation: Status "+strconv.Itoa(status)+"\n\n"+errorMessage+"\n", @@ -151,7 +148,6 @@ func assertDocumentError(t *testing.T, status int, errorMessage string) { func assertDocumentServerError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{} - convergeServices(client) client.NextResponse(status, errorMessage) assert.Equal(t, "Error: Container (document API) at 127.0.0.1:8080: Status "+strconv.Itoa(status)+"\n\n"+errorMessage+"\n", @@ -161,6 +157,5 @@ func assertDocumentServerError(t *testing.T, status int, errorMessage string) { } func documentServiceURL(client *mockHttpClient) string { - convergeServices(client) return getService("document", 0).BaseURL } diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go index f29a842aed2..98d6814d16f 100644 --- a/client/go/cmd/helpers.go +++ b/client/go/cmd/helpers.go @@ -10,14 +10,13 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "strings" "time" "github.com/vespa-engine/vespa/client/go/vespa" ) -const defaultConsoleURL = "https://console.vespa.oath.cloud" - var exitFunc = os.Exit // To allow overriding Exit in tests func fatalErrHint(err error, hints ...string) { @@ -50,6 +49,36 @@ func printSuccess(msg ...interface{}) { log.Print(color.Green("Success: "), fmt.Sprint(msg...)) } +func vespaCliHome() (string, error) { + home := os.Getenv("VESPA_CLI_HOME") + if home == "" { + userHome, err := os.UserHomeDir() + if err != nil { + return "", err + } + home = filepath.Join(userHome, ".vespa") + } + if err := os.MkdirAll(home, 0700); err != nil { + return "", err + } + return home, nil +} + +func vespaCliCacheDir() (string, error) { + cacheDir := os.Getenv("VESPA_CLI_CACHE_DIR") + if cacheDir == "" { + userCacheDir, err := os.UserCacheDir() + if err != nil { + return "", err + } + cacheDir = filepath.Join(userCacheDir, "vespa") + } + if err := os.MkdirAll(cacheDir, 0755); err != nil { + return "", err + } + return cacheDir, nil +} + func deploymentFromArgs() vespa.Deployment { zone, err := vespa.ZoneFromString(zoneArg) if err != nil { @@ -102,18 +131,32 @@ func getService(service string, sessionOrRunID int64) *vespa.Service { t := getTarget() timeout := time.Duration(waitSecsArg) * time.Second if timeout > 0 { - log.Printf("Waiting up to %d %s for services to become available ...", color.Cyan(waitSecsArg), color.Cyan("seconds")) - } - if err := t.DiscoverServices(timeout, sessionOrRunID); err != nil { - fatalErr(err, "Services unavailable") + log.Printf("Waiting up to %d %s for service to become available ...", color.Cyan(waitSecsArg), color.Cyan("seconds")) } - s, err := t.Service(service) + s, err := t.Service(service, timeout, sessionOrRunID) if err != nil { - fatalErr(err, "Invalid service") + fatalErr(err, "Invalid service: ", service) } return s } +func getConsoleURL() string { + system := os.Getenv("VESPA_CLI_CLOUD_SYSTEM") + if system == "publiccd" { + return "https://console-cd.vespa.oath.cloud" + } + return "https://console.vespa.oath.cloud" + +} + +func getApiURL() string { + system := os.Getenv("VESPA_CLI_CLOUD_SYSTEM") + if system == "publiccd" { + return "https://api.vespa-external-cd.aws.oath.cloud:4443" + } + return "https://api.vespa-external.aws.oath.cloud:4443" +} + func getTarget() vespa.Target { targetType := getTargetType() if strings.HasPrefix(targetType, "http") { @@ -147,7 +190,7 @@ func getTarget() vespa.Target { if err != nil { fatalErrHint(err, "Deployment to cloud requires a certificate. Try 'vespa cert'") } - return vespa.CloudTarget(deployment, apiKey, + return vespa.CloudTarget(getApiURL(), deployment, apiKey, vespa.TLSOptions{ KeyPair: kp, CertificateFile: certificateFile, diff --git a/client/go/cmd/query_test.go b/client/go/cmd/query_test.go index bd9ae91f24d..137ffa01cd5 100644 --- a/client/go/cmd/query_test.go +++ b/client/go/cmd/query_test.go @@ -56,7 +56,6 @@ func assertQuery(t *testing.T, expectedQuery string, query ...string) { func assertQueryError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{} - convergeServices(client) client.NextResponse(status, errorMessage) assert.Equal(t, "Error: Invalid query: Status "+strconv.Itoa(status)+"\n"+errorMessage+"\n", @@ -66,7 +65,6 @@ func assertQueryError(t *testing.T, status int, errorMessage string) { func assertQueryServiceError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{} - convergeServices(client) client.NextResponse(status, errorMessage) assert.Equal(t, "Error: Status "+strconv.Itoa(status)+" from container at 127.0.0.1:8080\n"+errorMessage+"\n", @@ -75,6 +73,5 @@ func assertQueryServiceError(t *testing.T, status int, errorMessage string) { } func queryServiceURL(client *mockHttpClient) string { - convergeServices(client) return getService("query", 0).BaseURL } diff --git a/client/go/cmd/status_test.go b/client/go/cmd/status_test.go index 8ddca71a35b..0c1c8e4e3a7 100644 --- a/client/go/cmd/status_test.go +++ b/client/go/cmd/status_test.go @@ -44,7 +44,6 @@ func TestStatusErrorResponse(t *testing.T) { func assertDeployStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} - convergeServices(client) assert.Equal(t, "Deploy API at "+target+" is ready\n", executeCommand(t, client, []string{"status", "deploy"}, args), @@ -54,14 +53,12 @@ func assertDeployStatus(target string, args []string, t *testing.T) { func assertQueryStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} - convergeServices(client) assert.Equal(t, "Container (query API) at "+target+" is ready\n", executeCommand(t, client, []string{"status", "query"}, args), "vespa status container") assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String()) - convergeServices(client) assert.Equal(t, "Container (query API) at "+target+" is ready\n", executeCommand(t, client, []string{"status"}, args), @@ -71,7 +68,6 @@ func assertQueryStatus(target string, args []string, t *testing.T) { func assertDocumentStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} - convergeServices(client) assert.Equal(t, "Container (document API) at "+target+" is ready\n", executeCommand(t, client, []string{"status", "document"}, args), @@ -81,7 +77,6 @@ func assertDocumentStatus(target string, args []string, t *testing.T) { func assertQueryStatusError(target string, args []string, t *testing.T) { client := &mockHttpClient{} - convergeServices(client) client.NextStatus(500) assert.Equal(t, "Container (query API) at "+target+" is not ready\nStatus 500\n", diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go index ece841617c0..19319724d18 100644 --- a/client/go/vespa/deploy.go +++ b/client/go/vespa/deploy.go @@ -71,7 +71,7 @@ func (d DeploymentOpts) String() string { func (d *DeploymentOpts) IsCloud() bool { return d.Target.Type() == cloudTargetType } func (d *DeploymentOpts) url(path string) (*url.URL, error) { - service, err := d.Target.Service("deploy") + service, err := d.Target.Service(deployService, 0, 0) if err != nil { return nil, err } diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go index 69dc876c1c8..df9144cd186 100644 --- a/client/go/vespa/target.go +++ b/client/go/vespa/target.go @@ -24,8 +24,6 @@ const ( queryService = "query" documentService = "document" - defaultCloudAPI = "https://api.vespa-external.aws.oath.cloud:4443" - waitRetryInterval = 2 * time.Second ) @@ -41,11 +39,8 @@ type Target interface { // Type returns this target's type, e.g. local or cloud. Type() string - // Service returns the service for given name. - Service(name string) (*Service, error) - - // DiscoverServices queries for services available on this target after the deployment run has completed. - DiscoverServices(timeout time.Duration, runID int64) error + // Service returns the service for given name. If timeout is non-zero, wait for the service to converge. + Service(name string, timeout time.Duration, sessionOrRunID int64) (*Service, error) } // TLSOptions configures the certificate to use for service requests. @@ -107,7 +102,12 @@ func (s *Service) Description() string { func (t *customTarget) Type() string { return t.targetType } -func (t *customTarget) Service(name string) (*Service, error) { +func (t *customTarget) Service(name string, timeout time.Duration, sessionID int64) (*Service, error) { + if timeout > 0 && name != deployService { + if err := t.waitForConvergence(timeout); err != nil { + return nil, err + } + } switch name { case deployService, queryService, documentService: url, err := t.urlWithPort(name) @@ -139,12 +139,12 @@ func (t *customTarget) urlWithPort(serviceName string) (string, error) { return u.String(), nil } -func (t *customTarget) DiscoverServices(timeout time.Duration, runID int64) error { - deployService, err := t.Service("deploy") +func (t *customTarget) waitForConvergence(timeout time.Duration) error { + deployer, err := t.Service(deployService, 0, 0) if err != nil { return err } - url := fmt.Sprintf("%s/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/serviceconverge", deployService.BaseURL) + url := fmt.Sprintf("%s/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/serviceconverge", deployer.BaseURL) req, err := http.NewRequest("GET", url, nil) if err != nil { return err @@ -171,7 +171,7 @@ func (t *customTarget) DiscoverServices(timeout time.Duration, runID int64) erro } type cloudTarget struct { - cloudAPI string + apiURL string targetType string deployment Deployment apiKey []byte @@ -184,27 +184,30 @@ type cloudTarget struct { func (t *cloudTarget) Type() string { return t.targetType } -func (t *cloudTarget) Service(name string) (*Service, error) { +func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64) (*Service, error) { + if timeout > 0 && name != deployService { + if err := t.waitForEndpoints(timeout, runID); err != nil { + return nil, err + } + } switch name { case deployService: - return &Service{Name: name, BaseURL: t.cloudAPI}, nil + return &Service{Name: name, BaseURL: t.apiURL}, nil case queryService: if t.queryURL == "" { - return nil, fmt.Errorf("service %s not discovered", name) + return nil, fmt.Errorf("service %s is not discovered", name) } return &Service{Name: name, BaseURL: t.queryURL, TLSOptions: t.tlsOptions}, nil case documentService: if t.documentURL == "" { - return nil, fmt.Errorf("service %s not discovered", name) + return nil, fmt.Errorf("service %s is not discovered", name) } return &Service{Name: name, BaseURL: t.documentURL, TLSOptions: t.tlsOptions}, nil } return nil, fmt.Errorf("unknown service: %s", name) } -// DiscoverServices waits for run identified by runID to complete and at least one endpoint is available, or timeout -// passes. -func (t *cloudTarget) DiscoverServices(timeout time.Duration, runID int64) 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 { @@ -216,7 +219,7 @@ func (t *cloudTarget) DiscoverServices(timeout time.Duration, runID int64) error func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout time.Duration) error { runURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/job/%s-%s/run/%d", - t.cloudAPI, + t.apiURL, t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance, t.deployment.Zone.Environment, t.deployment.Zone.Region, runID) req, err := http.NewRequest("GET", runURL, nil) @@ -234,8 +237,8 @@ func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout tim return req } jobSuccessFunc := func(status int, response []byte) (bool, error) { - if status/100 != 2 { - return false, nil + if ok, err := isOK(status); !ok { + return ok, err } var resp jobResponse if err := json.Unmarshal(response, &resp); err != nil { @@ -280,7 +283,7 @@ func (t *cloudTarget) printLog(response jobResponse, last int64) int64 { func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Duration) error { deploymentURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s", - t.cloudAPI, + t.apiURL, t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance, t.deployment.Zone.Environment, t.deployment.Zone.Region) req, err := http.NewRequest("GET", deploymentURL, nil) @@ -292,8 +295,8 @@ func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Dura } var endpointURL string endpointFunc := func(status int, response []byte) (bool, error) { - if status/100 != 2 { - return false, nil + if ok, err := isOK(status); !ok { + return ok, err } var resp deploymentResponse if err := json.Unmarshal(response, &resp); err != nil { @@ -316,6 +319,13 @@ func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Dura return nil } +func isOK(status int) (bool, error) { + if status == 401 { + return false, fmt.Errorf("status %d: invalid api key", status) + } + return status/100 == 2, nil +} + // LocalTarget creates a target for a Vespa platform running locally. func LocalTarget() Target { return &customTarget{targetType: localTargetType, baseURL: "http://127.0.0.1"} @@ -327,9 +337,9 @@ func CustomTarget(baseURL string) Target { } // CloudTarget creates a Target for the Vespa Cloud platform. -func CloudTarget(deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions) Target { +func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions) Target { return &cloudTarget{ - cloudAPI: defaultCloudAPI, + apiURL: apiURL, targetType: cloudTargetType, deployment: deployment, apiKey: apiKey, @@ -409,7 +419,8 @@ func wait(fn responseFunc, reqFn requestFunc, certificate *tls.Certificate, time return statusCode, nil } } - if loopOnce { + timeLeft := deadline.Sub(time.Now()) + if loopOnce || timeLeft < waitRetryInterval { break } time.Sleep(waitRetryInterval) diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go index 31f145f0db3..2c90baefbbc 100644 --- a/client/go/vespa/target_test.go +++ b/client/go/vespa/target_test.go @@ -74,11 +74,11 @@ func TestCustomTargetWait(t *testing.T) { defer srv.Close() target := CustomTarget(srv.URL) - err := target.DiscoverServices(0, 42) + _, err := target.Service("query", time.Millisecond, 42) assert.NotNil(t, err) vc.deploymentConverged = true - err = target.DiscoverServices(0, 42) + _, err = target.Service("query", time.Millisecond, 42) assert.Nil(t, err) assertServiceWait(t, 200, target, "deploy") @@ -102,6 +102,7 @@ func TestCloudTargetWait(t *testing.T) { var logWriter bytes.Buffer target := CloudTarget( + "https://example.com", Deployment{ Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"}, Zone: ZoneID{Environment: "dev", Region: "us-north-1"}, @@ -110,20 +111,17 @@ func TestCloudTargetWait(t *testing.T) { TLSOptions{KeyPair: x509KeyPair}, LogOptions{Writer: &logWriter}) if ct, ok := target.(*cloudTarget); ok { - ct.cloudAPI = srv.URL + ct.apiURL = srv.URL } else { t.Fatalf("Wrong target type %T", ct) } assertServiceWait(t, 200, target, "deploy") - _, err = target.Service("query") - assert.NotNil(t, err) - - err = target.DiscoverServices(0, 42) + _, err = target.Service("query", time.Millisecond, 42) assert.NotNil(t, err) vc.deploymentConverged = true - err = target.DiscoverServices(0, 42) + _, err = target.Service("query", time.Millisecond, 42) assert.Nil(t, err) assertServiceWait(t, 500, target, "query") @@ -136,13 +134,13 @@ func TestCloudTargetWait(t *testing.T) { } func assertServiceURL(t *testing.T, url string, target Target, service string) { - s, err := target.Service(service) + s, err := target.Service(service, 0, 42) assert.Nil(t, err) assert.Equal(t, url, s.BaseURL) } func assertServiceWait(t *testing.T, expectedStatus int, target Target, service string) { - s, err := target.Service(service) + s, err := target.Service(service, 0, 42) assert.Nil(t, err) status, err := s.Wait(0) diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 2870b88d105..6d5c7a27a47 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -80,13 +80,10 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useAsyncMessageHandlingOnSchedule() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default double feedConcurrency() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default int metricsproxyNumThreads() { throw new UnsupportedOperationException("TODO specify default value"); } - @ModelFeatureFlag(owners = {"baldersheim"}) default boolean enforceRankProfileInheritance() { return false; } + @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.472") default boolean enforceRankProfileInheritance() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default int largeRankExpressionLimit() { return 8192; } - @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.468") default boolean useExternalRankExpressions() { return true; } - @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.468") default boolean distributeExternalRankExpressions() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default int maxConcurrentMergesPerNode() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default int maxMergeQueueSize() { throw new UnsupportedOperationException("TODO specify default value"); } - @ModelFeatureFlag(owners = {"baldersheim"}) default boolean dryRunOnnxOnSetup() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default boolean containerDumpHeapOnShutdownTimeout() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default double containerShutdownTimeout() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"geirst"}) default boolean enableFeedBlockInDistributor() { return true; } diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index dea6b980692..e27e0e7624f 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -107,7 +107,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public double resourceLimitMemory() { return resourceLimitMemory; } @Override public double minNodeRatioPerGroup() { return minNodeRatioPerGroup; } @Override public int metricsproxyNumThreads() { return 1; } - @Override public boolean enforceRankProfileInheritance() { return enforceRankProfileInheritance; } @Override public double containerShutdownTimeout() { return containerShutdownTimeout; } @Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; } public TestProperties containerDumpHeapOnShutdownTimeout(boolean value) { @@ -118,10 +117,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea containerShutdownTimeout = value; return this; } - public TestProperties enforceRankProfileInheritance(boolean value) { - enforceRankProfileInheritance = value; - return this; - } public TestProperties largeRankExpressionLimit(int value) { largeRankExpressionLimit = value; return this; diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java index 2784c111019..decc6e98bc4 100644 --- a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java @@ -223,15 +223,11 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce didApply = parent.addUserConfig(builder); } - if (log.isLoggable(Level.FINEST)) { - log.log(Level.FINEST, "User configs is: " + userConfigs.toString()); - } + log.log(Level.FINEST, () -> "User configs is: " + userConfigs.toString()); // TODO: What do we do with md5. Currently ignored for user configs? ConfigDefinitionKey key = new ConfigDefinitionKey(builder.getDefName(), builder.getDefNamespace()); if (userConfigs.get(key) != null) { - if (log.isLoggable(Level.FINEST)) { - log.log(Level.FINEST, "Apply in " + configId); - } + log.log(Level.FINEST, () -> "Apply in " + configId); applyUserConfig(builder, userConfigs.get(key)); didApply = true; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java b/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java index 1719ea72cb0..472bc9d5413 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.Objects; public class DistributableResource { + public enum PathType { FILE, URI, BLOB }; /** The search definition-unique name of this constant */ @@ -95,10 +96,9 @@ public class DistributableResource { } } + @Override public String toString() { - StringBuilder b = new StringBuilder(); - b.append("resource '").append(name).append(" of type '").append(pathType) - .append("' with ref '").append(fileReference).append("'"); - return b.toString(); + return "resource '" + name + " of type '" + pathType + "' with ref '" + fileReference + "'"; } + } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index 9d51d39f3d0..d169760538d 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -227,12 +227,7 @@ public class RankProfile implements Cloneable { String msg = "rank-profile '" + getName() + "' inherits '" + inheritedName + "', but it does not exist anywhere in the inheritance of search '" + ((getSearch() != null) ? getSearch().getName() : " global rank profiles") + "'."; - if (search.getDeployProperties().featureFlags().enforceRankProfileInheritance()) { - throw new IllegalArgumentException(msg); - } else { - deployLogger.logApplicationPackage(Level.WARNING, msg); - inherited = resolveIndependentOfInheritance(); - } + throw new IllegalArgumentException(msg); } else { List<String> children = new ArrayList<>(); children.add(createFullyQualifiedName()); @@ -241,12 +236,7 @@ public class RankProfile implements Cloneable { } return inherited; } - private RankProfile resolveIndependentOfInheritance() { - for (RankProfile rankProfile : rankProfileRegistry.all()) { - if (rankProfile.getName().equals(inheritedName)) return rankProfile; - } - return null; - } + private String createFullyQualifiedName() { return (search != null) ? (search.getName() + "." + getName()) diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java index 46b785ccf42..ad85f68cb8a 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java @@ -100,12 +100,6 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ remaining.forEach((name, rank) -> { if (areDependenciesReady(rank, rankProfileRegistry)) ready.add(rank); }); - if (ready.isEmpty() && ! deployProperties.featureFlags().enforceRankProfileInheritance()) { - // Dirty fallback to allow incorrect rankprofile inheritance to pass for now. - // We then handle one by one. - // TODO remove ASAP - ready.add(remaining.values().iterator().next()); - } processRankProfiles(ready, queryProfiles, importedModels, search, attributeFields, deployProperties, executor); ready.forEach(rank -> remaining.remove(rank.getName())); } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java index 4332d8baea8..87fa74b92fe 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java @@ -11,6 +11,7 @@ import com.yahoo.vespa.indexinglanguage.ScriptParserContext; import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression; import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression; import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig; +import com.yahoo.yolean.Exceptions; /** * @author Einar M R Rosenvinge @@ -46,7 +47,7 @@ public class IndexingOperation implements FieldOperation { exp = new ScriptExpression(StatementExpression.newInstance(config)); } } catch (com.yahoo.vespa.indexinglanguage.parser.ParseException e) { - ParseException t = new ParseException("Error reported by IL parser: " + e.getMessage()); + ParseException t = new ParseException("Could not parse indexing statement: " + Exceptions.toMessageString(e)); t.initCause(e); throw t; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java index 937537e5f99..2c87fd5c5b3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java @@ -492,8 +492,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri boolean found = configProducer.cascadeConfig(builder); boolean foundOverride = configProducer.addUserConfig(builder); log.log(Level.FINE, () -> "Trying to get config for " + builder.getClass().getDeclaringClass().getName() + - " for config id " + quote(configProducer.getConfigId()) + - ", found=" + found + ", foundOverride=" + foundOverride); + " for config id " + quote(configProducer.getConfigId()) + + ", found=" + found + ", foundOverride=" + foundOverride); } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java index ed1dc80d71d..af88d9c008a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java @@ -23,7 +23,7 @@ import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.component.SystemBindingPattern; import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; -import com.yahoo.vespa.model.container.xml.PlatformBundles; +import com.yahoo.vespa.model.container.PlatformBundles; import java.util.Set; import java.util.TreeSet; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java index 0b2d0936235..14ead1cdece 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java @@ -38,7 +38,7 @@ import com.yahoo.vespa.model.admin.monitoring.Monitoring; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SystemBindingPattern; -import com.yahoo.vespa.model.container.xml.PlatformBundles; +import com.yahoo.vespa.model.container.PlatformBundles; import java.nio.file.Path; import java.util.Collections; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index 1dd7b35cda5..5574082e334 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -34,7 +34,6 @@ import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.Servlet; import com.yahoo.vespa.model.container.component.SystemBindingPattern; import com.yahoo.vespa.model.container.configserver.ConfigserverCluster; -import com.yahoo.vespa.model.container.xml.PlatformBundles; import com.yahoo.vespa.model.utils.FileSender; import java.util.ArrayList; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 532ac78f17e..f5b168958c0 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -171,6 +171,8 @@ public abstract class ContainerCluster<CONTAINER extends Container> componentGroup = new ComponentGroup<>(this, "component"); + addCommonVespaBundles(); + addComponent(new StatisticsComponent()); addSimpleComponent(AccessLog.class); addComponent(new DefaultThreadpoolProvider(this, deployState.featureFlags().metricsproxyNumThreads())); @@ -459,6 +461,13 @@ public abstract class ContainerCluster<CONTAINER extends Container> } /** + * Adds the Vespa bundles that are necessary for all container types. + */ + public void addCommonVespaBundles() { + PlatformBundles.commonVespaBundles().forEach(this::addPlatformBundle); + } + + /** * Adds a bundle present at a known location at the target container nodes. * Note that the set of platform bundles cannot change during the jdisc container's lifetime. * diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java index 65247f29281..25cb684932b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java @@ -10,7 +10,6 @@ import com.yahoo.vespa.config.search.core.RankingConstantsConfig; import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SystemBindingPattern; -import com.yahoo.vespa.model.container.xml.PlatformBundles; import java.nio.file.Path; import java.util.List; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java new file mode 100644 index 00000000000..24412027e63 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java @@ -0,0 +1,133 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.container; + +import com.yahoo.vespa.defaults.Defaults; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author gjoranv + * @author Ulf Lilleengen + */ +public class PlatformBundles { + + private enum JarSuffix { + JAR_WITH_DEPS("-jar-with-dependencies.jar"), + DEPLOY("-deploy.jar"); + + public final String suffix; + + JarSuffix(String suffix) { + this.suffix = suffix; + } + } + + public static final Path LIBRARY_PATH = Paths.get(Defaults.getDefaults().underVespaHome("lib/jars")); + public static final String searchAndDocprocBundle = "container-search-and-docproc"; + + public static Set<Path> commonVespaBundles() { + var bundles = new LinkedHashSet<Path>(); + commonVespaBundles.stream().map(PlatformBundles::absoluteBundlePath).forEach(bundles::add); + return Collections.unmodifiableSet(bundles); + } + + public static Path absoluteBundlePath(String fileName) { + return absoluteBundlePath(fileName, JarSuffix.JAR_WITH_DEPS); + } + + public static Path absoluteBundlePath(String fileName, JarSuffix jarSuffix) { + if (fileName == null) return null; + return LIBRARY_PATH.resolve(Paths.get(fileName + jarSuffix.suffix)); + } + + public static boolean isSearchAndDocprocClass(String className) { + return searchAndDocprocComponents.contains(className); + } + + // Bundles that must be loaded for all container types. + private static final Set<String> commonVespaBundles = Set.of( + "zkfacade", + "zookeeper-server" // TODO: not necessary in metrics-proxy. + ); + + // This is a hack to allow users to declare components from the search-and-docproc bundle without naming the bundle. + private static final Set<String> searchAndDocprocComponents = Set.of( + "com.yahoo.docproc.AbstractConcreteDocumentFactory", + "com.yahoo.docproc.DocumentProcessor", + "com.yahoo.docproc.SimpleDocumentProcessor", + "com.yahoo.docproc.util.JoinerDocumentProcessor", + "com.yahoo.docproc.util.SplitterDocumentProcessor", + "com.yahoo.example.TimingSearcher", + "com.yahoo.language.simple.SimpleLinguistics", + "com.yahoo.prelude.cluster.ClusterSearcher", + "com.yahoo.prelude.fastsearch.FastSearcher", + "com.yahoo.prelude.fastsearch.VespaBackEndSearcher", + "com.yahoo.prelude.querytransform.CJKSearcher", + "com.yahoo.prelude.querytransform.CollapsePhraseSearcher", + "com.yahoo.prelude.querytransform.LiteralBoostSearcher", + "com.yahoo.prelude.querytransform.NoRankingSearcher", + "com.yahoo.prelude.querytransform.NonPhrasingSearcher", + "com.yahoo.prelude.querytransform.NormalizingSearcher", + "com.yahoo.prelude.querytransform.PhrasingSearcher", + "com.yahoo.prelude.querytransform.RecallSearcher", + "com.yahoo.prelude.querytransform.StemmingSearcher", + "com.yahoo.prelude.searcher.BlendingSearcher", + "com.yahoo.prelude.searcher.FieldCollapsingSearcher", + "com.yahoo.prelude.searcher.FillSearcher", + "com.yahoo.prelude.searcher.JSONDebugSearcher", + "com.yahoo.prelude.searcher.JuniperSearcher", + "com.yahoo.prelude.searcher.MultipleResultsSearcher", + "com.yahoo.prelude.searcher.PosSearcher", + "com.yahoo.prelude.searcher.QuotingSearcher", + "com.yahoo.prelude.searcher.ValidateSortingSearcher", + "com.yahoo.prelude.semantics.SemanticSearcher", + "com.yahoo.prelude.statistics.StatisticsSearcher", + "com.yahoo.prelude.templates.SearchRendererAdaptor", + "com.yahoo.search.Searcher", + "com.yahoo.search.cluster.ClusterSearcher", + "com.yahoo.search.cluster.PingableSearcher", + "com.yahoo.search.federation.FederationSearcher", + "com.yahoo.search.federation.ForwardingSearcher", + "com.yahoo.search.federation.http.ConfiguredHTTPClientSearcher", + "com.yahoo.search.federation.http.ConfiguredHTTPProviderSearcher", + "com.yahoo.search.federation.http.HTTPClientSearcher", + "com.yahoo.search.federation.http.HTTPProviderSearcher", + "com.yahoo.search.federation.http.HTTPSearcher", + "com.yahoo.search.federation.news.NewsSearcher", + "com.yahoo.search.federation.vespa.VespaSearcher", + "com.yahoo.search.grouping.GroupingQueryParser", + "com.yahoo.search.grouping.GroupingValidator", + "com.yahoo.search.grouping.vespa.GroupingExecutor", + "com.yahoo.search.handler.SearchWithRendererHandler", + "com.yahoo.search.pagetemplates.PageTemplate", + "com.yahoo.search.pagetemplates.PageTemplateSearcher", + "com.yahoo.search.pagetemplates.engine.Resolver", + "com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver", + "com.yahoo.search.pagetemplates.engine.resolvers.RandomResolver", + "com.yahoo.search.pagetemplates.model.Renderer", + "com.yahoo.search.query.rewrite.QueryRewriteSearcher", + "com.yahoo.search.query.rewrite.SearchChainDispatcherSearcher", + "com.yahoo.search.query.rewrite.rewriters.GenericExpansionRewriter", + "com.yahoo.search.query.rewrite.rewriters.MisspellRewriter", + "com.yahoo.search.query.rewrite.rewriters.NameRewriter", + "com.yahoo.search.querytransform.AllLowercasingSearcher", + "com.yahoo.search.querytransform.DefaultPositionSearcher", + "com.yahoo.search.querytransform.LowercasingSearcher", + "com.yahoo.search.querytransform.NGramSearcher", + "com.yahoo.search.querytransform.VespaLowercasingSearcher", + "com.yahoo.search.rendering.Renderer", + "com.yahoo.search.rendering.SectionedRenderer", + "com.yahoo.search.searchchain.ForkingSearcher", + "com.yahoo.search.searchchain.example.ExampleSearcher", + "com.yahoo.search.searchers.CacheControlSearcher", + "com.yahoo.search.statistics.PeakQpsSearcher", + "com.yahoo.search.statistics.TimingSearcher", + "com.yahoo.vespa.streamingvisitors.MetricsSearcher", + "com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher" + ); + +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Component.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Component.java index 6d891c55075..e7f6697aecc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Component.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Component.java @@ -69,4 +69,10 @@ public class Component<CHILD extends AbstractConfigProducer<?>, MODEL extends Co return getComponentId().compareTo(other.getComponentId()); } + @Override + public String toString() { + return "component " + getClassId() + + (getClassId().toString().equals(getComponentId().toString()) ? "" : ": " + getComponentId()); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java index ef2eaeb4654..fec8dd76102 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java @@ -11,7 +11,6 @@ import com.yahoo.vespa.configdefinition.IlscriptsConfig; import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.ContainerSubsystem; -import com.yahoo.vespa.model.container.search.searchchain.LocalProvider; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; import com.yahoo.vespa.model.search.AbstractSearchCluster; import com.yahoo.vespa.model.search.IndexedSearchCluster; @@ -23,7 +22,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import static com.yahoo.vespa.model.container.xml.PlatformBundles.searchAndDocprocBundle; +import static com.yahoo.vespa.model.container.PlatformBundles.searchAndDocprocBundle; /** * @author gjoranv diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java index 232e8fcbd1a..832aede858e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/DispatcherComponent.java @@ -5,7 +5,7 @@ import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.config.search.DispatchConfig; import com.yahoo.vespa.model.container.component.Component; -import com.yahoo.vespa.model.container.xml.PlatformBundles; +import com.yahoo.vespa.model.container.PlatformBundles; import com.yahoo.vespa.model.search.IndexedSearchCluster; /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java index 248b30eafa7..8c45e5b013f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/RpcResourcePoolComponent.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.model.container.search; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.model.container.component.Component; -import com.yahoo.vespa.model.container.xml.PlatformBundles; +import com.yahoo.vespa.model.container.PlatformBundles; public class RpcResourcePoolComponent extends Component<RpcResourcePoolComponent, ComponentModel> { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java index 12d74418f9f..ea0ad371e28 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilder.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.model.container.xml; import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.component.ComponentSpecification; +import com.yahoo.vespa.model.container.PlatformBundles; import org.w3c.dom.Element; import java.util.Arrays; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 4b45979c698..c318180fd56 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -59,6 +59,7 @@ import com.yahoo.vespa.model.container.ContainerModel; import com.yahoo.vespa.model.container.ContainerModelEvaluation; import com.yahoo.vespa.model.container.ContainerThreadpool; import com.yahoo.vespa.model.container.IdentityProvider; +import com.yahoo.vespa.model.container.PlatformBundles; import com.yahoo.vespa.model.container.SecretStore; import com.yahoo.vespa.model.container.component.AccessLogComponent; import com.yahoo.vespa.model.container.component.BindingPattern; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java deleted file mode 100644 index dc2437c1834..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/PlatformBundles.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.container.xml; - -import com.yahoo.vespa.defaults.Defaults; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Set; - -/** - * @author gjoranv - * @author Ulf Lilleengen - */ -public class PlatformBundles { - - private enum JarSuffix { - JAR_WITH_DEPS("-jar-with-dependencies.jar"), - DEPLOY("-deploy.jar"); - - public final String suffix; - - JarSuffix(String suffix) { - this.suffix = suffix; - } - } - - public static final Path LIBRARY_PATH = Paths.get(Defaults.getDefaults().underVespaHome("lib/jars")); - public static final String searchAndDocprocBundle = "container-search-and-docproc"; - - private static final Set<String> searchAndDocprocComponents; - - public static boolean isSearchAndDocprocClass(String className) { - return searchAndDocprocComponents.contains(className); - } - - public static Path absoluteBundlePath(String fileName) { - if (fileName == null) return null; - return LIBRARY_PATH.resolve(Paths.get(fileName + JarSuffix.JAR_WITH_DEPS.suffix)); - } - - // This is a hack to allow users to declare components from the search-and-docproc bundle without naming the bundle. - static { - searchAndDocprocComponents = Set.of( - "com.yahoo.docproc.AbstractConcreteDocumentFactory", - "com.yahoo.docproc.DocumentProcessor", - "com.yahoo.docproc.SimpleDocumentProcessor", - "com.yahoo.docproc.util.JoinerDocumentProcessor", - "com.yahoo.docproc.util.SplitterDocumentProcessor", - "com.yahoo.example.TimingSearcher", - "com.yahoo.language.simple.SimpleLinguistics", - "com.yahoo.prelude.cluster.ClusterSearcher", - "com.yahoo.prelude.fastsearch.FastSearcher", - "com.yahoo.prelude.fastsearch.VespaBackEndSearcher", - "com.yahoo.prelude.querytransform.CJKSearcher", - "com.yahoo.prelude.querytransform.CollapsePhraseSearcher", - "com.yahoo.prelude.querytransform.LiteralBoostSearcher", - "com.yahoo.prelude.querytransform.NoRankingSearcher", - "com.yahoo.prelude.querytransform.NonPhrasingSearcher", - "com.yahoo.prelude.querytransform.NormalizingSearcher", - "com.yahoo.prelude.querytransform.PhrasingSearcher", - "com.yahoo.prelude.querytransform.RecallSearcher", - "com.yahoo.prelude.querytransform.StemmingSearcher", - "com.yahoo.prelude.searcher.BlendingSearcher", - "com.yahoo.prelude.searcher.FieldCollapsingSearcher", - "com.yahoo.prelude.searcher.FillSearcher", - "com.yahoo.prelude.searcher.JSONDebugSearcher", - "com.yahoo.prelude.searcher.JuniperSearcher", - "com.yahoo.prelude.searcher.MultipleResultsSearcher", - "com.yahoo.prelude.searcher.PosSearcher", - "com.yahoo.prelude.searcher.QuotingSearcher", - "com.yahoo.prelude.searcher.ValidateSortingSearcher", - "com.yahoo.prelude.semantics.SemanticSearcher", - "com.yahoo.prelude.statistics.StatisticsSearcher", - "com.yahoo.prelude.templates.SearchRendererAdaptor", - "com.yahoo.search.Searcher", - "com.yahoo.search.cluster.ClusterSearcher", - "com.yahoo.search.cluster.PingableSearcher", - "com.yahoo.search.federation.FederationSearcher", - "com.yahoo.search.federation.ForwardingSearcher", - "com.yahoo.search.federation.http.ConfiguredHTTPClientSearcher", - "com.yahoo.search.federation.http.ConfiguredHTTPProviderSearcher", - "com.yahoo.search.federation.http.HTTPClientSearcher", - "com.yahoo.search.federation.http.HTTPProviderSearcher", - "com.yahoo.search.federation.http.HTTPSearcher", - "com.yahoo.search.federation.news.NewsSearcher", - "com.yahoo.search.federation.vespa.VespaSearcher", - "com.yahoo.search.grouping.GroupingQueryParser", - "com.yahoo.search.grouping.GroupingValidator", - "com.yahoo.search.grouping.vespa.GroupingExecutor", - "com.yahoo.search.handler.SearchWithRendererHandler", - "com.yahoo.search.pagetemplates.PageTemplate", - "com.yahoo.search.pagetemplates.PageTemplateSearcher", - "com.yahoo.search.pagetemplates.engine.Resolver", - "com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver", - "com.yahoo.search.pagetemplates.engine.resolvers.RandomResolver", - "com.yahoo.search.pagetemplates.model.Renderer", - "com.yahoo.search.query.rewrite.QueryRewriteSearcher", - "com.yahoo.search.query.rewrite.SearchChainDispatcherSearcher", - "com.yahoo.search.query.rewrite.rewriters.GenericExpansionRewriter", - "com.yahoo.search.query.rewrite.rewriters.MisspellRewriter", - "com.yahoo.search.query.rewrite.rewriters.NameRewriter", - "com.yahoo.search.querytransform.AllLowercasingSearcher", - "com.yahoo.search.querytransform.DefaultPositionSearcher", - "com.yahoo.search.querytransform.LowercasingSearcher", - "com.yahoo.search.querytransform.NGramSearcher", - "com.yahoo.search.querytransform.VespaLowercasingSearcher", - "com.yahoo.search.rendering.Renderer", - "com.yahoo.search.rendering.SectionedRenderer", - "com.yahoo.search.searchchain.ForkingSearcher", - "com.yahoo.search.searchchain.example.ExampleSearcher", - "com.yahoo.search.searchers.CacheControlSearcher", - "com.yahoo.search.statistics.PeakQpsSearcher", - "com.yahoo.search.statistics.TimingSearcher", - "com.yahoo.vespa.streamingvisitors.MetricsSearcher", - "com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher" - ); - } - -} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java b/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java index de5eaa2278e..7d761eb07eb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java @@ -149,6 +149,7 @@ public class FileSender implements Serializable { String path = builder.getValue(); FileReference reference = sentFiles.get(path); if (reference == null) { + reference = fileRegistry.addFile(path); send(reference, services); sentFiles.put(path, reference); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java index 2c1f68e5ecc..aa068ec8f0e 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java @@ -118,13 +118,8 @@ public class RankProfileTestCase extends SchemaTestCase { @Test public void requireThatSidewaysInheritanceIsImpossible() throws ParseException { - verifySidewaysInheritance(false); - verifySidewaysInheritance(true); - } - private void verifySidewaysInheritance(boolean enforce) throws ParseException { RankProfileRegistry registry = new RankProfileRegistry(); - SearchBuilder builder = new SearchBuilder(registry, setupQueryProfileTypes(), - new TestProperties().enforceRankProfileInheritance(enforce)); + SearchBuilder builder = new SearchBuilder(registry, setupQueryProfileTypes()); builder.importString(joinLines( "schema child1 {", " document child1 {", @@ -168,15 +163,8 @@ public class RankProfileTestCase extends SchemaTestCase { "}")); try { builder.build(true); - if (enforce) { - fail("Sideways inheritance should have been enforced"); - } else { - assertNotNull(builder.getSearch("child2")); - assertNotNull(builder.getSearch("child1")); - assertTrue(registry.get("child1", "child").inherits("parent")); - } + fail("Sideways inheritance should have been enforced"); } catch (IllegalArgumentException e) { - if (!enforce) fail("Sideways inheritance should have been allowed"); assertEquals("rank-profile 'child' inherits 'parent', but it does not exist anywhere in the inheritance of search 'child1'.", e.getMessage()); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java index 413daefdf75..4359e90e8a2 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java @@ -51,8 +51,7 @@ public class MetricsProxyContainerClusterTest { var builder = new PlatformBundlesConfig.Builder(); model.getConfig(builder, CLUSTER_CONFIG_ID); PlatformBundlesConfig config = builder.build(); - assertEquals(1, config.bundlePaths().size()); - assertThat(config.bundlePaths(0), endsWith(METRICS_PROXY_BUNDLE_FILE.toString())); + assertThat(config.bundlePaths(), hasItem(endsWith(METRICS_PROXY_BUNDLE_FILE.toString()))); } @Test diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java index dd4f4c3b7d5..a66ea736a5b 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java @@ -38,8 +38,8 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -383,10 +383,7 @@ public class ContainerClusterTest { cluster.getConfig(bundleBuilder); List<String> installedBundles = bundleBuilder.build().bundlePaths(); - assertEquals(expectedBundleNames.size(), installedBundles.size()); - assertThat(installedBundles, containsInAnyOrder( - expectedBundleNames.stream().map(CoreMatchers::endsWith).collect(Collectors.toList()) - )); + expectedBundleNames.forEach(b -> assertThat(installedBundles, hasItem(CoreMatchers.endsWith(b)))); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java index 70ae6a27324..4f715375e1f 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/BundleInstantiationSpecificationBuilderTest.java @@ -5,6 +5,7 @@ import com.yahoo.component.ComponentSpecification; import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.search.grouping.GroupingValidator; +import com.yahoo.vespa.model.container.PlatformBundles; import org.junit.Test; import org.w3c.dom.Element; import org.xml.sax.SAXException; diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigVerification.java index 68a1d9b7333..56824c85413 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigVerification.java @@ -1,5 +1,5 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config; +package com.yahoo.vespa.config.proxy; import ai.vespa.util.http.hc5.VespaHttpClientBuilder; import com.yahoo.slime.ArrayTraverser; diff --git a/config-proxy/src/main/sh/vespa-config-verification.sh b/config-proxy/src/main/sh/vespa-config-verification.sh index 308d7f733a4..97201d772eb 100644 --- a/config-proxy/src/main/sh/vespa-config-verification.sh +++ b/config-proxy/src/main/sh/vespa-config-verification.sh @@ -79,4 +79,4 @@ export ROOT echo "# Using CLASSPATH=$CLASSPATH, args=$@" -java -cp $CLASSPATH:$ROOT/lib/jars/config-proxy-jar-with-dependencies.jar com.yahoo.vespa.config.ConfigVerification "$@" +java -cp $CLASSPATH:$ROOT/lib/jars/config-proxy-jar-with-dependencies.jar com.yahoo.vespa.config.proxy.ConfigVerification "$@" diff --git a/config/src/apps/vespa-get-config/getconfig.cpp b/config/src/apps/vespa-get-config/getconfig.cpp index 273a3abd1cd..679d118d0bc 100644 --- a/config/src/apps/vespa-get-config/getconfig.cpp +++ b/config/src/apps/vespa-get-config/getconfig.cpp @@ -240,7 +240,7 @@ GetConfig::Main() printf("defNamespace %s\n", rKey.getDefNamespace().c_str()); printf("configID %s\n", rKey.getConfigId().c_str()); - printf("configMD5 %s\n", rState.md5.c_str()); + printf("configXxhash64 %s\n", rState.xxhash64.c_str()); printf("generation %" PRId64 "\n", rState.generation); printf("trace %s\n", response->getTrace().toString().c_str()); diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java b/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java index 6d2ae3ef13e..7f81e937b3c 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigTransformer.java @@ -11,9 +11,12 @@ import static com.yahoo.vespa.config.ConfigPayloadApplier.IdentityPathAcquirer; /** * A utility class that can be used to transform config from one format to another. * - * @author Ulf Lilleengen, hmusum, Tony Vaagenes + * @author Ulf Lilleengen + * @author hmusum + * @author Tony Vaagenes */ public class ConfigTransformer<T extends ConfigInstance> { + /** * Workaround since FileAcquirer is in a separate module that depends on config. * Consider moving FileAcquirer into config instead. @@ -52,7 +55,7 @@ public class ConfigTransformer<T extends ConfigInstance> { /** * Create a ConfigBuilder from a payload, based on the <code>clazz</code> supplied. * - * @param payload a Payload to be transformed to builder. + * @param payload a Payload to be transformed to builder * @return a ConfigBuilder */ public ConfigInstance.Builder toConfigBuilder(ConfigPayload payload) { @@ -80,4 +83,5 @@ public class ConfigTransformer<T extends ConfigInstance> { return builder; } } + } diff --git a/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java b/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java index 123d7c22093..bc937ef57ea 100644 --- a/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java +++ b/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java @@ -5,7 +5,6 @@ import com.yahoo.config.ConfigBuilder; import com.yahoo.config.ConfigInstance; /** - * * A generic config with an internal generic builder that mimics a real config builder in order to support builders * when we don't have the schema. * diff --git a/config/src/tests/configagent/configagent.cpp b/config/src/tests/configagent/configagent.cpp index d6766cce822..e4dc94fc2de 100644 --- a/config/src/tests/configagent/configagent.cpp +++ b/config/src/tests/configagent/configagent.cpp @@ -31,12 +31,12 @@ class MyConfigResponse : public ConfigResponse { public: MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool valid, int64_t timestamp, - const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror) + const vespalib::string & xxhash64, const std::string & errorMsg, int errorC0de, bool iserror) : _key(key), _value(value), _fillCalled(false), _valid(valid), - _state(md5, timestamp, false), + _state(xxhash64, timestamp, false), _errorMessage(errorMsg), _errorCode(errorC0de), _isError(iserror) @@ -64,9 +64,9 @@ public: Trace _trace; - static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & md5 = "a") + static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & xxhash64 = "a") { - return std::make_unique<MyConfigResponse>(key, value, true, timestamp, md5, "", 0, false); + return std::make_unique<MyConfigResponse>(key, value, true, timestamp, xxhash64, "", 0, false); } static ConfigResponse::UP createServerErrorResponse(const ConfigKey & key, const ConfigValue & value) @@ -114,11 +114,11 @@ private: }; -ConfigValue createValue(const std::string & myField, const std::string & md5) +ConfigValue createValue(const std::string & myField, const std::string & xxhash64) { std::vector< vespalib::string > lines; lines.push_back("myField \"" + myField + "\""); - return ConfigValue(lines, md5); + return ConfigValue(lines, xxhash64); } static TimingValues testTimingValues( @@ -139,7 +139,7 @@ TEST("require that agent returns correct values") { ASSERT_EQUAL(500u, handler.getTimeout()); ASSERT_EQUAL(0u, handler.getWaitTime()); ConfigState cs; - ASSERT_EQUAL(cs.md5, handler.getConfigState().md5); + ASSERT_EQUAL(cs.xxhash64, handler.getConfigState().xxhash64); ASSERT_EQUAL(cs.generation, handler.getConfigState().generation); ASSERT_EQUAL(cs.applyOnRestart, handler.getConfigState().applyOnRestart); } @@ -167,7 +167,7 @@ TEST("require that important(the change) request is delivered to holder even if FRTConfigAgent handler(latch, testTimingValues); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getXxhash64())); ASSERT_TRUE(latch->poll()); ConfigUpdate::UP update(latch->provide()); ASSERT_TRUE(update); @@ -176,9 +176,9 @@ TEST("require that important(the change) request is delivered to holder even if ASSERT_EQUAL("l33t", cfg.myField); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getXxhash64())); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getXxhash64())); ASSERT_TRUE(latch->poll()); update = latch->provide(); ASSERT_TRUE(update); diff --git a/config/src/tests/failover/failover.cpp b/config/src/tests/failover/failover.cpp index 2e039081716..cb8b7121b02 100644 --- a/config/src/tests/failover/failover.cpp +++ b/config/src/tests/failover/failover.cpp @@ -60,7 +60,7 @@ struct RPCServer : public FRT_Invokable { info.setString("uncompressedSize", "0"); root.setString(RESPONSE_CONFIGID, "myId"); root.setString(RESPONSE_CLIENT_HOSTNAME, "myhost"); - root.setString(RESPONSE_CONFIG_MD5, "md5"); + root.setString(RESPONSE_CONFIG_XXHASH64, "xxhash64"); root.setLong(RESPONSE_CONFIG_GENERATION, gen); root.setObject(RESPONSE_TRACE); Slime payload; diff --git a/config/src/tests/frt/frt.cpp b/config/src/tests/frt/frt.cpp index cb09b8f7254..5af02f22ed2 100644 --- a/config/src/tests/frt/frt.cpp +++ b/config/src/tests/frt/frt.cpp @@ -72,7 +72,7 @@ namespace { FRT_RPCRequest * createOKResponse(const vespalib::string & defName="", const vespalib::string & defMd5="", const vespalib::string & configId="", - const vespalib::string & configMd5="", + const vespalib::string & configXxhash64="", int changed=0, long generation=0, const std::vector<vespalib::string> & payload = std::vector<vespalib::string>(), @@ -85,7 +85,7 @@ namespace { ret.AddString(""); ret.AddString(defMd5.c_str()); ret.AddString(configId.c_str()); - ret.AddString(configMd5.c_str()); + ret.AddString(configXxhash64.c_str()); ret.AddInt32(changed); ret.AddInt64(generation); FRT_StringValue * payload_arr = ret.AddStringArray(payload.size()); @@ -268,16 +268,16 @@ TEST_FF("require that request is config task is scheduled", SourceFixture(), FRT TEST("require that v3 request is correctly initialized") { ConnectionMock conn; ConfigKey key = ConfigKey::create<MyConfig>("foobi"); - vespalib::string md5 = "mymd5"; + vespalib::string xxhash64 = "myxxhash64"; int64_t currentGeneration = 3; vespalib::string hostName = "myhost"; int64_t timeout = 3000; Trace traceIn(3); traceIn.trace(2, "Hei"); - FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, hostName, + FRTConfigRequestV3 v3req(&conn, key, xxhash64, currentGeneration, hostName, timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4); - ASSERT_TRUE(v3req.verifyState(ConfigState(md5, 3, false))); - ASSERT_FALSE(v3req.verifyState(ConfigState(md5, 2, false))); + ASSERT_TRUE(v3req.verifyState(ConfigState(xxhash64, 3, false))); + ASSERT_FALSE(v3req.verifyState(ConfigState(xxhash64, 2, false))); ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 3, false))); ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 2, false))); @@ -297,7 +297,7 @@ TEST("require that v3 request is correctly initialized") { EXPECT_EQUAL(key.getConfigId(), root[REQUEST_CLIENT_CONFIGID].asString().make_string()); EXPECT_EQUAL(hostName, root[REQUEST_CLIENT_HOSTNAME].asString().make_string()); EXPECT_EQUAL(currentGeneration, root[REQUEST_CURRENT_GENERATION].asLong()); - EXPECT_EQUAL(md5, root[REQUEST_CONFIG_MD5].asString().make_string()); + EXPECT_EQUAL(xxhash64, root[REQUEST_CONFIG_XXHASH64].asString().make_string()); EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong()); EXPECT_EQUAL("LZ4", root[REQUEST_COMPRESSION_TYPE].asString().make_string()); EXPECT_EQUAL(root[REQUEST_VESPA_VERSION].asString().make_string(), "1.2.3"); @@ -322,7 +322,7 @@ struct V3RequestFixture { Cursor & root; FRT_RPCRequest * req; ConfigKey key; - vespalib::string md5; + vespalib::string xxhash64; int64_t generation; vespalib::string hostname; Trace traceIn; @@ -333,7 +333,7 @@ struct V3RequestFixture { root(slime.setObject()), req(conn.allocRPCRequest()), key(ConfigKey::create<BarConfig>("foobi")), - md5("mymd5"), + xxhash64("myxxhash64"), generation(3), hostname("myhhost"), traceIn(3) @@ -345,7 +345,7 @@ struct V3RequestFixture { root.setString(RESPONSE_DEF_MD5, Memory(key.getDefMd5())); root.setString(RESPONSE_CONFIGID, Memory(key.getConfigId())); root.setString(RESPONSE_CLIENT_HOSTNAME, Memory(hostname)); - root.setString(RESPONSE_CONFIG_MD5, Memory(md5)); + root.setString(RESPONSE_CONFIG_XXHASH64, Memory(xxhash64)); root.setLong(RESPONSE_CONFIG_GENERATION, generation); traceIn.serialize(root.setObject(RESPONSE_TRACE)); } @@ -379,7 +379,7 @@ struct V3RequestFixture { EXPECT_EQUAL(key.getConfigId(), responseKey.getConfigId()); EXPECT_EQUAL(hostname, response.getHostName()); ConfigState state(response.getConfigState()); - EXPECT_EQUAL(md5, state.md5); + EXPECT_EQUAL(xxhash64, state.xxhash64); EXPECT_EQUAL(generation, state.generation); ConfigValue value(response.getValue()); BarConfig::UP config(value.newInstance<BarConfig>()); diff --git a/config/src/tests/misc/misc.cpp b/config/src/tests/misc/misc.cpp index 1a8b8a59ede..55040242732 100644 --- a/config/src/tests/misc/misc.cpp +++ b/config/src/tests/misc/misc.cpp @@ -15,11 +15,11 @@ TEST("requireThatConfigUpdateWorks") { std::vector<vespalib::string> lines; lines.push_back("foo"); - ConfigUpdate up(ConfigValue(lines, "mymd5"), true, 1337); + ConfigUpdate up(ConfigValue(lines, "myxxhash"), true, 1337); ASSERT_EQUAL(1337, up.getGeneration()); ASSERT_TRUE(up.hasChanged()); - ConfigUpdate up2(ConfigValue(lines, "mymd52"), false, 1338); + ConfigUpdate up2(ConfigValue(lines, "myxxhash2"), false, 1338); ASSERT_EQUAL(1338, up2.getGeneration()); ASSERT_FALSE(up2.hasChanged()); } @@ -27,22 +27,22 @@ TEST("requireThatConfigUpdateWorks") { TEST("requireThatConfigValueWorks") { std::vector<vespalib::string> lines; lines.push_back("myFooField \"bar\""); - ConfigValue v1(lines, calculateContentMd5(lines)); - ConfigValue v2(lines, calculateContentMd5(lines)); - ConfigValue v3(lines, calculateContentMd5(lines)); + ConfigValue v1(lines, calculateContentXxhash64(lines)); + ConfigValue v2(lines, calculateContentXxhash64(lines)); + ConfigValue v3(lines, calculateContentXxhash64(lines)); lines.push_back("myFooField \"bar2\""); - ConfigValue v4(lines, calculateContentMd5(lines)); + ConfigValue v4(lines, calculateContentXxhash64(lines)); ASSERT_TRUE(v1 == v2); ASSERT_TRUE(v1 == v3); } TEST("requireThatConfigKeyWorks") { - ConfigKey key1("id1", "def1", "namespace1", "md51"); - ConfigKey key2("id1", "def1", "namespace1", "md51"); - ConfigKey key3("id2", "def1", "namespace1", "md51"); - ConfigKey key4("id1", "def2", "namespace1", "md51"); - ConfigKey key5("id1", "def1", "namespace2", "md51"); - ConfigKey key6("id1", "def1", "namespace1", "md52"); // Special case. Md5 does not matter, so should be qual to key1 and key2 + ConfigKey key1("id1", "def1", "namespace1", "xxhash1"); + ConfigKey key2("id1", "def1", "namespace1", "xxhash1"); + ConfigKey key3("id2", "def1", "namespace1", "xxhash1"); + ConfigKey key4("id1", "def2", "namespace1", "xxhash1"); + ConfigKey key5("id1", "def1", "namespace2", "xxhash1"); + ConfigKey key6("id1", "def1", "namespace1", "xxhash2"); // Special case. xxhash64 does not matter, so should be qual to key1 and key2 ASSERT_TRUE(key1 == key2); @@ -111,7 +111,7 @@ TEST("require that config key initializes schema") std::vector<vespalib::string> schema; schema.push_back("foo"); schema.push_back("bar"); - ConfigKey key("id1", "def1", "namespace1", "md51", schema); + ConfigKey key("id1", "def1", "namespace1", "xxhash1", schema); const std::vector<vespalib::string> &vref(key.getDefSchema()); for (size_t i = 0; i < schema.size(); i++) { ASSERT_EQUAL(schema[i], vref[i]); @@ -131,6 +131,7 @@ TEST("require that error codes are correctly translated to strings") { ASSERT_CONFIG(ILLEGAL_CONFIGID); ASSERT_CONFIG(ILLEGAL_DEF_MD5); ASSERT_CONFIG(ILLEGAL_CONFIG_MD5); + ASSERT_CONFIG(ILLEGAL_CONFIG_MD5); ASSERT_CONFIG(ILLEGAL_TIMEOUT); ASSERT_CONFIG(ILLEGAL_TIMESTAMP); ASSERT_CONFIG(ILLEGAL_NAME_SPACE); diff --git a/config/src/tests/subscriber/subscriber.cpp b/config/src/tests/subscriber/subscriber.cpp index d58699f26e0..5c5714a867a 100644 --- a/config/src/tests/subscriber/subscriber.cpp +++ b/config/src/tests/subscriber/subscriber.cpp @@ -20,7 +20,7 @@ namespace { { std::vector< vespalib::string > lines; lines.push_back(value); - return ConfigValue(lines, calculateContentMd5(lines)); + return ConfigValue(lines, calculateContentXxhash64(lines)); } ConfigValue createFooValue(const std::string & value) diff --git a/config/src/vespa/config/common/configstate.h b/config/src/vespa/config/common/configstate.h index 2dbea3cc30f..143d77e4ab9 100644 --- a/config/src/vespa/config/common/configstate.h +++ b/config/src/vespa/config/common/configstate.h @@ -13,17 +13,17 @@ struct ConfigState { public: ConfigState() - : md5(""), + : xxhash64(""), generation(0), applyOnRestart(false) { } - ConfigState(const vespalib::string & md5sum, int64_t gen, bool _applyOnRestart) - : md5(md5sum), + ConfigState(const vespalib::string & xxhash, int64_t gen, bool _applyOnRestart) + : xxhash64(xxhash), generation(gen), applyOnRestart(_applyOnRestart) { } - vespalib::string md5; + vespalib::string xxhash64; int64_t generation; bool applyOnRestart; @@ -32,7 +32,7 @@ public: } bool hasDifferentPayloadFrom(const ConfigState & other) const { - return (md5.compare(other.md5) != 0); + return (xxhash64.compare(other.xxhash64) != 0); } }; diff --git a/config/src/vespa/config/common/configvalue.cpp b/config/src/vespa/config/common/configvalue.cpp index d5c0c2047df..ce6fd3f20da 100644 --- a/config/src/vespa/config/common/configvalue.cpp +++ b/config/src/vespa/config/common/configvalue.cpp @@ -6,22 +6,22 @@ namespace config { -ConfigValue::ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & md5sum) +ConfigValue::ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & xxhash) : _payload(), _lines(lines), - _md5sum(md5sum) + _xxhash64(xxhash) { } ConfigValue::ConfigValue() : _payload(), _lines(), - _md5sum() + _xxhash64() { } -ConfigValue::ConfigValue(PayloadPtr payload, const vespalib::string & md5) +ConfigValue::ConfigValue(PayloadPtr payload, const vespalib::string & xxhash) : _payload(std::move(payload)), _lines(), - _md5sum(md5) + _xxhash64(xxhash) { } ConfigValue::ConfigValue(const ConfigValue &) = default; @@ -32,7 +32,7 @@ ConfigValue::~ConfigValue() = default; int ConfigValue::operator==(const ConfigValue & rhs) const { - return (_md5sum.compare(rhs._md5sum) == 0); + return (_xxhash64.compare(rhs._xxhash64) == 0); } int diff --git a/config/src/vespa/config/common/configvalue.h b/config/src/vespa/config/common/configvalue.h index a0450328f30..f5bfae00c19 100644 --- a/config/src/vespa/config/common/configvalue.h +++ b/config/src/vespa/config/common/configvalue.h @@ -21,8 +21,8 @@ typedef std::shared_ptr<const protocol::Payload> PayloadPtr; class ConfigValue { public: typedef std::unique_ptr<ConfigValue> UP; - ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & md5sum); - ConfigValue(PayloadPtr data, const vespalib::string & md5sum); + ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & xxhash); + ConfigValue(PayloadPtr data, const vespalib::string & xxhash); ConfigValue(); ConfigValue(const ConfigValue &); ConfigValue & operator = (const ConfigValue &); @@ -36,7 +36,7 @@ public: const std::vector<vespalib::string> & getLines() const { return _lines; } std::vector<vespalib::string> getLegacyFormat() const; const vespalib::string asJson() const; - const vespalib::string getMd5() const { return _md5sum; } + const vespalib::string getXxhash64() const { return _xxhash64; } void serializeV1(::vespalib::slime::Cursor & cursor) const; void serializeV2(::vespalib::slime::Cursor & cursor) const; @@ -47,7 +47,7 @@ public: private: PayloadPtr _payload; std::vector<vespalib::string> _lines; - vespalib::string _md5sum; + vespalib::string _xxhash64; }; } //namespace config diff --git a/config/src/vespa/config/common/misc.cpp b/config/src/vespa/config/common/misc.cpp index 1040962e25c..e1b4390caf5 100644 --- a/config/src/vespa/config/common/misc.cpp +++ b/config/src/vespa/config/common/misc.cpp @@ -1,7 +1,9 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "misc.h" -#include <vespa/vespalib/util/md5.h> +#include <iostream> +#include <sstream> +#include <xxhash.h> #include <vespa/vespalib/stllike/asciistream.h> #include <vespa/vespalib/util/exceptions.h> #include <vespa/vespalib/data/slime/slime.h> @@ -12,12 +14,12 @@ using vespalib::Memory; namespace config { vespalib::string -calculateContentMd5(const std::vector<vespalib::string> & fileContents) +calculateContentXxhash64(const std::vector<vespalib::string> & fileContents) { vespalib::string normalizedLines; - int compact_md5size = 16; - unsigned char md5sum[compact_md5size]; + XXH64_hash_t xxhash64; vespalib::asciistream s; + std::stringstream ss; // remove comments, trailing spaces and empty lines // TODO: Remove multiple spaces and space before comma, like in Java @@ -30,16 +32,11 @@ calculateContentMd5(const std::vector<vespalib::string> & fileContents) normalizedLines += line; } } - fastc_md5sum((const unsigned char*)normalizedLines.c_str(), normalizedLines.size(), md5sum); + xxhash64 = XXH64((const unsigned char*)normalizedLines.c_str(), normalizedLines.size(), 0); - // convert to 32 character hex string - for (int i = 0; i < compact_md5size; i++) { - if (md5sum[i] < 16) { - s << "0"; - } - s << vespalib::hex << (int)md5sum[i]; - } - return s.str(); + ss << std::hex << xxhash64; + ss << std::endl; + return ss.str(); } bool diff --git a/config/src/vespa/config/common/misc.h b/config/src/vespa/config/common/misc.h index 0299ef001f1..089fd890224 100644 --- a/config/src/vespa/config/common/misc.h +++ b/config/src/vespa/config/common/misc.h @@ -19,7 +19,7 @@ namespace config { /** * Miscellaneous utility functions specific to config. */ -vespalib::string calculateContentMd5(const std::vector<vespalib::string> & fileContents); +vespalib::string calculateContentXxhash64(const std::vector<vespalib::string> & fileContents); bool isGenerationNewer(int64_t newGen, int64_t oldGen); diff --git a/config/src/vespa/config/file/filesource.cpp b/config/src/vespa/config/file/filesource.cpp index fcc69f68066..8e25d5ccffc 100644 --- a/config/src/vespa/config/file/filesource.cpp +++ b/config/src/vespa/config/file/filesource.cpp @@ -27,10 +27,10 @@ FileSource::getConfig() int64_t last = getLast(_fileName); if (last > _lastLoaded) { - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentMd5(lines)), true, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentXxhash64(lines)), true, _generation))); _lastLoaded = last; } else { - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentMd5(lines)), false, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentXxhash64(lines)), false, _generation))); } } diff --git a/config/src/vespa/config/frt/frtconfigagent.cpp b/config/src/vespa/config/frt/frtconfigagent.cpp index 0e5ee4f6b05..30112b677e1 100644 --- a/config/src/vespa/config/frt/frtconfigagent.cpp +++ b/config/src/vespa/config/frt/frtconfigagent.cpp @@ -27,7 +27,7 @@ FRTConfigAgent::handleResponse(const ConfigRequest & request, ConfigResponse::UP { if (LOG_WOULD_LOG(spam)) { const ConfigKey & key(request.getKey()); - LOG(spam, "current state for %s: generation %" PRId64 " md5 %s", key.toString().c_str(), _configState.generation, _configState.md5.c_str()); + LOG(spam, "current state for %s: generation %" PRId64 " xxhash64 %s", key.toString().c_str(), _configState.generation, _configState.xxhash64.c_str()); } if (response->validateResponse() && !response->isError()) { handleOKResponse(request, std::move(response)); @@ -57,12 +57,12 @@ void FRTConfigAgent::handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue) { if (LOG_WOULD_LOG(spam)) { - LOG(spam, "new generation %" PRId64 " md5:%s for key %s", newState.generation, newState.md5.c_str(), key.toString().c_str()); - LOG(spam, "Old config: md5:%s \n%s", _latest.getMd5().c_str(), _latest.asJson().c_str()); - LOG(spam, "New config: md5:%s \n%s", configValue.getMd5().c_str(), configValue.asJson().c_str()); + LOG(spam, "new generation %" PRId64 " xxhash64:%s for key %s", newState.generation, newState.xxhash64.c_str(), key.toString().c_str()); + LOG(spam, "Old config: xxhash64:%s \n%s", _latest.getXxhash64().c_str(), _latest.asJson().c_str()); + LOG(spam, "New config: xxhash64:%s \n%s", configValue.getXxhash64().c_str(), configValue.asJson().c_str()); } bool changed = false; - if (_latest.getMd5() != configValue.getMd5()) { + if (_latest.getXxhash64() != configValue.getXxhash64()) { _latest = configValue; changed = true; } diff --git a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp index fbc13556d14..3573b8c9cfe 100644 --- a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp +++ b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp @@ -24,7 +24,7 @@ FRTConfigRequest::UP FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection, const ConfigState & state, int64_t serverTimeout) const { - return make_unique<FRTConfigRequestV3>(connection, key, state.md5, state.generation, _hostName, + return make_unique<FRTConfigRequestV3>(connection, key, state.xxhash64, state.generation, _hostName, serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType); } diff --git a/config/src/vespa/config/frt/frtconfigresponsev3.cpp b/config/src/vespa/config/frt/frtconfigresponsev3.cpp index 80cdf88a79a..351f0fc8136 100644 --- a/config/src/vespa/config/frt/frtconfigresponsev3.cpp +++ b/config/src/vespa/config/frt/frtconfigresponsev3.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "frtconfigresponsev3.h" #include "compressioninfo.h" #include <vespa/fnet/frt/values.h> @@ -53,7 +53,7 @@ FRTConfigResponseV3::getResponseTypes() const ConfigValue FRTConfigResponseV3::readConfigValue() const { - vespalib::string md5(_data->get()[RESPONSE_CONFIG_MD5].asString().make_string()); + vespalib::string xxhash64(_data->get()[RESPONSE_CONFIG_XXHASH64].asString().make_string()); CompressionInfo info; info.deserialize(_data->get()[RESPONSE_COMPRESSION_INFO]); auto slime = std::make_unique<Slime>(); @@ -67,9 +67,9 @@ FRTConfigResponseV3::readConfigValue() const } } if (LOG_WOULD_LOG(spam)) { - LOG(spam, "read config value md5(%s), payload size: %lu", md5.c_str(), data.memRef.size); + LOG(spam, "read config value xxhash64(%s), payload size: %lu", xxhash64.c_str(), data.memRef.size); } - return ConfigValue(std::make_shared<V3Payload>(std::move(slime)), md5); + return ConfigValue(std::make_shared<V3Payload>(std::move(slime)), xxhash64); } } // namespace config diff --git a/config/src/vespa/config/frt/protocol.cpp b/config/src/vespa/config/frt/protocol.cpp index 4a1b0a6ddef..c6c99ccf1ff 100644 --- a/config/src/vespa/config/frt/protocol.cpp +++ b/config/src/vespa/config/frt/protocol.cpp @@ -25,6 +25,7 @@ const Memory REQUEST_DEF_CONTENT = "defContent"; const Memory REQUEST_CLIENT_CONFIGID = "configId"; const Memory REQUEST_CLIENT_HOSTNAME = "clientHostname"; const Memory REQUEST_CONFIG_MD5 = "configMD5"; +const Memory REQUEST_CONFIG_XXHASH64 = "configXxhash64"; const Memory REQUEST_CURRENT_GENERATION = "currentGeneration"; const Memory REQUEST_TIMEOUT = "timeout"; const Memory REQUEST_TRACE = "trace"; @@ -36,7 +37,7 @@ const Memory RESPONSE_DEF_NAMESPACE = "defNamespace"; const Memory RESPONSE_DEF_MD5 = "defMD5"; const Memory RESPONSE_CONFIGID = "configId"; const Memory RESPONSE_CLIENT_HOSTNAME = "clientHostname"; -const Memory RESPONSE_CONFIG_MD5 = "configMD5"; +const Memory RESPONSE_CONFIG_XXHASH64 = "configXxhash64"; const Memory RESPONSE_CONFIG_GENERATION = "generation"; const Memory RESPONSE_PAYLOAD = "payload"; const Memory RESPONSE_TRACE = "trace"; diff --git a/config/src/vespa/config/frt/protocol.h b/config/src/vespa/config/frt/protocol.h index 0ec16952701..ce09217b619 100644 --- a/config/src/vespa/config/frt/protocol.h +++ b/config/src/vespa/config/frt/protocol.h @@ -36,7 +36,7 @@ extern const vespalib::Memory REQUEST_DEF_MD5; extern const vespalib::Memory REQUEST_DEF_CONTENT; extern const vespalib::Memory REQUEST_CLIENT_CONFIGID; extern const vespalib::Memory REQUEST_CLIENT_HOSTNAME; -extern const vespalib::Memory REQUEST_CONFIG_MD5; +extern const vespalib::Memory REQUEST_CONFIG_XXHASH64; extern const vespalib::Memory REQUEST_CURRENT_GENERATION; extern const vespalib::Memory REQUEST_TIMEOUT; extern const vespalib::Memory REQUEST_TRACE; @@ -48,7 +48,7 @@ extern const vespalib::Memory RESPONSE_DEF_NAMESPACE; extern const vespalib::Memory RESPONSE_DEF_MD5; extern const vespalib::Memory RESPONSE_CONFIGID; extern const vespalib::Memory RESPONSE_CLIENT_HOSTNAME; -extern const vespalib::Memory RESPONSE_CONFIG_MD5; +extern const vespalib::Memory RESPONSE_CONFIG_XXHASH64; extern const vespalib::Memory RESPONSE_CONFIG_GENERATION; extern const vespalib::Memory RESPONSE_PAYLOAD; extern const vespalib::Memory RESPONSE_TRACE; diff --git a/config/src/vespa/config/frt/slimeconfigrequest.cpp b/config/src/vespa/config/frt/slimeconfigrequest.cpp index 8a6706974f6..3573f8c07b9 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.cpp +++ b/config/src/vespa/config/frt/slimeconfigrequest.cpp @@ -23,7 +23,7 @@ const vespalib::string SlimeConfigRequest::REQUEST_TYPES = "s"; SlimeConfigRequest::SlimeConfigRequest(Connection * connection, const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -35,7 +35,7 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection, : FRTConfigRequest(connection, key), _data() { - populateSlimeRequest(key, configMd5, currentGeneration, hostName, serverTimeout, trace, vespaVersion, protocolVersion, compressionType); + populateSlimeRequest(key, configXxhash64, currentGeneration, hostName, serverTimeout, trace, vespaVersion, protocolVersion, compressionType); _request->SetMethodName(methodName.c_str()); _parameters.AddString(createJsonFromSlime(_data).c_str()); } @@ -43,13 +43,13 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection, bool SlimeConfigRequest::verifyState(const ConfigState & state) const { - return (state.md5.compare(_data[REQUEST_CONFIG_MD5].asString().make_stringref()) == 0 && + return (state.xxhash64.compare(_data[REQUEST_CONFIG_XXHASH64].asString().make_stringref()) == 0 && state.generation == _data[REQUEST_CURRENT_GENERATION].asLong()); } void SlimeConfigRequest::populateSlimeRequest(const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -67,7 +67,7 @@ SlimeConfigRequest::populateSlimeRequest(const ConfigKey & key, def.serialize(root.setArray(REQUEST_DEF_CONTENT)); root.setString(REQUEST_CLIENT_CONFIGID, Memory(key.getConfigId())); root.setString(REQUEST_CLIENT_HOSTNAME, Memory(hostName)); - root.setString(REQUEST_CONFIG_MD5, Memory(configMd5)); + root.setString(REQUEST_CONFIG_XXHASH64, Memory(configXxhash64)); root.setLong(REQUEST_CURRENT_GENERATION, currentGeneration); root.setLong(REQUEST_TIMEOUT, serverTimeout); trace.serialize(root.setObject(REQUEST_TRACE)); diff --git a/config/src/vespa/config/frt/slimeconfigrequest.h b/config/src/vespa/config/frt/slimeconfigrequest.h index 6f2f42c98d5..d25530f815c 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.h +++ b/config/src/vespa/config/frt/slimeconfigrequest.h @@ -19,7 +19,7 @@ class SlimeConfigRequest : public FRTConfigRequest { public: SlimeConfigRequest(Connection * connection, const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -33,7 +33,7 @@ public: virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override = 0; private: void populateSlimeRequest(const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, diff --git a/config/src/vespa/config/frt/slimeconfigresponse.cpp b/config/src/vespa/config/frt/slimeconfigresponse.cpp index af224008d01..c1f3df7f674 100644 --- a/config/src/vespa/config/frt/slimeconfigresponse.cpp +++ b/config/src/vespa/config/frt/slimeconfigresponse.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "slimeconfigresponse.h" #include <vespa/config/common/misc.h> #include <vespa/fnet/frt/values.h> @@ -66,7 +66,7 @@ ConfigState SlimeConfigResponse::readState() const { const Slime & data(*_data); - return ConfigState(data.get()[RESPONSE_CONFIG_MD5].asString().make_string(), + return ConfigState(data.get()[RESPONSE_CONFIG_XXHASH64].asString().make_string(), data.get()[RESPONSE_CONFIG_GENERATION].asLong(), data.get()[RESPONSE_APPLY_ON_RESTART].asBool()); } diff --git a/config/src/vespa/config/print/asciiconfigreader.hpp b/config/src/vespa/config/print/asciiconfigreader.hpp index b4a25f4fbbd..9d142e83c00 100644 --- a/config/src/vespa/config/print/asciiconfigreader.hpp +++ b/config/src/vespa/config/print/asciiconfigreader.hpp @@ -27,7 +27,7 @@ AsciiConfigReader<ConfigType>::read() while (getline(_is, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/print/fileconfigreader.hpp b/config/src/vespa/config/print/fileconfigreader.hpp index bfebb4b89b9..87a4e20866c 100644 --- a/config/src/vespa/config/print/fileconfigreader.hpp +++ b/config/src/vespa/config/print/fileconfigreader.hpp @@ -44,7 +44,7 @@ FileConfigReader<ConfigType>::read() for (std::getline(f, line); f; std::getline(f, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/print/istreamconfigreader.hpp b/config/src/vespa/config/print/istreamconfigreader.hpp index 70f7b7e5f28..3a501d26245 100644 --- a/config/src/vespa/config/print/istreamconfigreader.hpp +++ b/config/src/vespa/config/print/istreamconfigreader.hpp @@ -31,7 +31,7 @@ IstreamConfigReader<ConfigType>::read() while (getline(_is, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/raw/rawsource.cpp b/config/src/vespa/config/raw/rawsource.cpp index 2b38cff214c..a6c4da0b4dd 100644 --- a/config/src/vespa/config/raw/rawsource.cpp +++ b/config/src/vespa/config/raw/rawsource.cpp @@ -16,7 +16,7 @@ void RawSource::getConfig() { auto lines(readConfig()); - ConfigValue value(lines, calculateContentMd5(lines)); + ConfigValue value(lines, calculateContentXxhash64(lines)); _holder->handle(ConfigUpdate::UP(new ConfigUpdate(value, true, 1))); } diff --git a/config/src/vespa/config/retriever/configsnapshot.cpp b/config/src/vespa/config/retriever/configsnapshot.cpp index 7aaf4ffd6f8..35f4415a26e 100644 --- a/config/src/vespa/config/retriever/configsnapshot.cpp +++ b/config/src/vespa/config/retriever/configsnapshot.cpp @@ -150,7 +150,7 @@ void ConfigSnapshot::serializeValueV2(Cursor & cursor, const Value & value) const { cursor.setDouble("lastChanged", value.first); - cursor.setString("md5", Memory(value.second.getMd5())); + cursor.setString("xxhash64", Memory(value.second.getXxhash64())); value.second.serializeV2(cursor.setObject("payload")); } @@ -225,7 +225,7 @@ ConfigSnapshot::deserializeValueV1(Inspector & inspector) const for (size_t i = 0; i < s.children(); i++) { payload.push_back(s[i].asString().make_string()); } - return Value(lastChanged, ConfigValue(payload, calculateContentMd5(payload))); + return Value(lastChanged, ConfigValue(payload, calculateContentXxhash64(payload))); } namespace { @@ -247,10 +247,10 @@ std::pair<int64_t, ConfigValue> ConfigSnapshot::deserializeValueV2(Inspector & inspector) const { int64_t lastChanged = static_cast<int64_t>(inspector["lastChanged"].asDouble()); - vespalib::string md5(inspector["md5"].asString().make_string()); + vespalib::string xxhash64(inspector["xxhash64"].asString().make_string()); auto payload = std::make_unique<FixedPayload>(); copySlimeObject(inspector["payload"], payload->getData().setObject()); - return Value(lastChanged, ConfigValue(std::move(payload) , md5)); + return Value(lastChanged, ConfigValue(std::move(payload), xxhash64)); } } diff --git a/config/src/vespa/config/set/configinstancesourcefactory.cpp b/config/src/vespa/config/set/configinstancesourcefactory.cpp index 7f963847997..5ebffd4c4e9 100644 --- a/config/src/vespa/config/set/configinstancesourcefactory.cpp +++ b/config/src/vespa/config/set/configinstancesourcefactory.cpp @@ -14,8 +14,8 @@ public: void close() override { } void getConfig() override { std::vector<vespalib::string> lines(_buffer.getlines()); - std::string currentMd5(config::calculateContentMd5(lines)); - _holder->handle(config::ConfigUpdate::UP(new config::ConfigUpdate(config::ConfigValue(lines, currentMd5), true, _generation))); + std::string currentXxhash64(config::calculateContentXxhash64(lines)); + _holder->handle(config::ConfigUpdate::UP(new config::ConfigUpdate(config::ConfigValue(lines, currentXxhash64), true, _generation))); } void reload(int64_t generation) override { _generation = generation; } diff --git a/config/src/vespa/config/set/configsetsource.cpp b/config/src/vespa/config/set/configsetsource.cpp index e6ab890a9df..36c7d70e0d3 100644 --- a/config/src/vespa/config/set/configsetsource.cpp +++ b/config/src/vespa/config/set/configsetsource.cpp @@ -30,16 +30,16 @@ ConfigSetSource::getConfig() AsciiConfigWriter writer(ss); writer.write(*instance); std::vector<vespalib::string> lines(ss.getlines()); - std::string currentMd5(calculateContentMd5(lines)); + std::string currentXxhash64(calculateContentXxhash64(lines)); - if (isGenerationNewer(_generation, _lastState.generation) && currentMd5.compare(_lastState.md5) != 0) { + if (isGenerationNewer(_generation, _lastState.generation) && currentXxhash64.compare(_lastState.xxhash64) != 0) { LOG(debug, "New generation, updating"); - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentMd5), true, _generation))); - _lastState.md5 = currentMd5; + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentXxhash64), true, _generation))); + _lastState.xxhash64 = currentXxhash64; _lastState.generation = _generation; } else { LOG(debug, "Sending timestamp update"); - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentMd5), false, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentXxhash64), false, _generation))); _lastState.generation = _generation; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/GetConfigContext.java b/configserver/src/main/java/com/yahoo/vespa/config/server/GetConfigContext.java index ef7d8756228..7a3fa9fe46c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/GetConfigContext.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/GetConfigContext.java @@ -49,4 +49,10 @@ public class GetConfigContext { public String logPre() { return TenantRepository.logPre(app); } + + @Override + public String toString() { + return "get config context for application " + app + ", having handler " + requestHandler; + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java index dd514e0d843..93148c52c3a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java @@ -205,9 +205,8 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica @Override public ConfigResponse resolveConfig(ApplicationId appId, GetConfigRequest req, Optional<Version> vespaVersion) { Application application = getApplication(appId, vespaVersion); - if (log.isLoggable(Level.FINE)) { - log.log(Level.FINE, TenantRepository.logPre(appId) + "Resolving for tenant '" + tenant + "' with handler for application '" + application + "'"); - } + log.log(Level.FINE, () -> TenantRepository.logPre(appId) + "Resolving for tenant '" + tenant + + "' with handler for application '" + application + "'"); return application.resolveConfig(req, responseFactory); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 0198e5899da..8dedb5a29c9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -187,7 +187,6 @@ public class ModelContextImpl implements ModelContext { private final double resourceLimitMemory; private final double minNodeRatioPerGroup; private final int metricsproxyNumThreads; - private final boolean enforceRankProfileInheritance; private final boolean newLocationBrokerLogic; private final boolean containerDumpHeapOnShutdownTimeout; private final double containerShutdownTimeout; @@ -216,7 +215,6 @@ public class ModelContextImpl implements ModelContext { this.resourceLimitMemory = flagValue(source, appId, PermanentFlags.RESOURCE_LIMIT_MEMORY); this.minNodeRatioPerGroup = flagValue(source, appId, Flags.MIN_NODE_RATIO_PER_GROUP); this.metricsproxyNumThreads = flagValue(source, appId, Flags.METRICSPROXY_NUM_THREADS); - this.enforceRankProfileInheritance = flagValue(source, appId, Flags.ENFORCE_RANK_PROFILE_INHERITANCE); this.newLocationBrokerLogic = flagValue(source, appId, Flags.NEW_LOCATION_BROKER_LOGIC); this.containerDumpHeapOnShutdownTimeout = flagValue(source, appId, Flags.CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT); this.containerShutdownTimeout = flagValue(source, appId,Flags.CONTAINER_SHUTDOWN_TIMEOUT); @@ -252,8 +250,6 @@ public class ModelContextImpl implements ModelContext { @Override public double containerShutdownTimeout() { return containerShutdownTimeout; } @Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; } - @Override public boolean enforceRankProfileInheritance() { return enforceRankProfileInheritance; } - private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) { return flag.bindTo(source) .with(FetchVector.Dimension.APPLICATION_ID, appId.serializedForm()) diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java index bad03862133..412eafd9882 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java @@ -27,8 +27,8 @@ import java.util.logging.Logger; import static com.yahoo.vespa.config.protocol.SlimeConfigResponse.fromConfigPayload; /** -* @author hmusum -*/ + * @author hmusum + */ class GetConfigProcessor implements Runnable { private static final Logger log = Logger.getLogger(GetConfigProcessor.class.getName()); @@ -69,7 +69,7 @@ class GetConfigProcessor implements Runnable { // TODO: Increment statistics (Metrics) failed counters when requests fail public Pair<GetConfigContext, Long> getConfig(JRTServerConfigRequest request) { - //Request has already been detached + // Request has already been detached if ( ! request.validateParameters()) { // Error code is set in verifyParameters if parameters are not OK. log.log(Level.WARNING, "Parameters for request " + request + " did not validate: " + request.errorCode() + " : " + request.errorMessage()); @@ -140,6 +140,7 @@ class GetConfigProcessor implements Runnable { } return null; } + @Override public void run() { rpcServer.hostLivenessTracker().receivedRequestFrom(request.getClientHostName()); diff --git a/configserver/src/main/sh/start-configserver b/configserver/src/main/sh/start-configserver index f9eb5e3a0a5..965dc8756be 100755 --- a/configserver/src/main/sh/start-configserver +++ b/configserver/src/main/sh/start-configserver @@ -188,7 +188,6 @@ vespa-run-as-vespa-user vespa-runserver -s configserver -r 30 -p $pidfile -- \ -Djdisc.config.file=$cfpfile \ -Djdisc.export.packages= \ -Djdisc.cache.path=$bundlecachedir \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path=${VESPA_HOME}/lib/jars \ -Djdisc.logger.enabled=true \ -Djdisc.logger.level=ALL \ diff --git a/container-core/src/main/sh/find-pid b/container-core/src/main/sh/find-pid index e1c2195c805..56c387387d4 100755 --- a/container-core/src/main/sh/find-pid +++ b/container-core/src/main/sh/find-pid @@ -77,20 +77,26 @@ findhost set -euo pipefail if (( $# != 1 )); then - echo "Usage: $0 <service-config-id>" >&2 + echo "Usage: $0 <service-name-or-config-id>" >&2 exit 1 fi -readonly config_id=$1 -status=$(vespa-sentinel-cmd list 2>/dev/null | grep "id=\"${config_id}\"") -if [ -z "${status}" ]; then - echo "No service named '${config_id}'" >&2 - exit 1 -fi -readonly pid=$(echo ${status} | cut -d " " -f 4 | cut -d "=" -f 2) -if ! [[ "${pid}" =~ ^[0-9]+$ ]]; then - echo "Could not find valid pid for '${config_id}' (pid='${pid}')" >&2 - exit 1 +readonly service=$1 +readonly pid_file="$VESPA_HOME/var/run/$service.pid" +if [ -f "$pid_file" ]; then + parent_pid=$(cat "$pid_file") + pid=$(pgrep --parent $parent_pid) +else + status=$(vespa-sentinel-cmd list 2>/dev/null | grep --perl-regexp "($service state=|id=\"$service\")") + if [ -z "${status}" ]; then + echo "No service with name or config id '${service}'" >&2 + exit 1 + fi + pid=$(echo ${status} | cut -d " " -f 4 | cut -d "=" -f 2) + if ! [[ "${pid}" =~ ^[0-9]+$ ]]; then + echo "Could not find valid pid for '${service}' (pid='${pid}')" >&2 + exit 1 + fi fi if [ -z "$(ps -p ${pid} -o pid=)" ]; then echo "Could not find process for '${pid}'" >&2 diff --git a/container-disc/pom.xml b/container-disc/pom.xml index 7d6a1ebb9d5..a362fb11be6 100644 --- a/container-disc/pom.xml +++ b/container-disc/pom.xml @@ -177,12 +177,14 @@ linguistics-components-jar-with-dependencies.jar, vespaclient-container-plugin-jar-with-dependencies.jar, vespa-athenz-jar-with-dependencies.jar, + container-apache-http-client-bundle-jar-with-dependencies.jar, <!-- Apache http client repackaged as bundle --> + + <!-- Vespa security utils with necessary 3rd party bundles --> security-utils-jar-with-dependencies.jar, - zkfacade-jar-with-dependencies.jar, - zookeeper-server-jar-with-dependencies.jar, - <!-- Apache http client repackaged as bundle --> - container-apache-http-client-bundle-jar-with-dependencies.jar, - <!-- Jetty --> + bcpkix-jdk15on-${bouncycastle.version}.jar, + bcprov-jdk15on-${bouncycastle.version}.jar, + + <!-- Jetty --> alpn-api-${jetty-alpn.version}.jar, http2-server-${jetty.version}.jar, http2-common-${jetty.version}.jar, @@ -199,6 +201,8 @@ jetty-servlets-${jetty.version}.jar, jetty-util-${jetty.version}.jar, jetty-util-ajax-${jetty.version}.jar, + javax.servlet-api-3.1.0.jar, + <!-- Spifly (required for OSGi service loader used by Jetty) --> org.apache.aries.spifly.dynamic.bundle-${spifly.version}.jar, asm-${asm.version}.jar, @@ -207,10 +211,7 @@ asm-tree-${asm.version}.jar, asm-util-${asm.version}.jar, <!-- Spifly end --> - <!-- Misc 3rd party bundles --> - bcpkix-jdk15on-${bouncycastle.version}.jar, - bcprov-jdk15on-${bouncycastle.version}.jar, - javax.servlet-api-3.1.0.jar, + <!-- Jersey 2 + Jackson 2 --> aopalliance-repackaged-${hk2.version}.jar, hk2-api-${hk2.version}.jar, diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java index 457d4fc96a9..a84d2521b8b 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java @@ -6,6 +6,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; import com.yahoo.cloud.config.SlobroksConfig; +import com.yahoo.component.Vtag; import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.concurrent.DaemonThreadFactory; import com.yahoo.config.ConfigInstance; @@ -102,7 +103,7 @@ public final class ConfiguredApplication implements Application { static { LogSetup.initVespaLogging("Container"); - log.log(Level.INFO, "Starting container"); + log.log(Level.INFO, "Starting jdisc" + (Vtag.currentVersion.isEmpty() ? "" : " at version " + Vtag.currentVersion)); } /** 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 f09a17de5e5..2d9bbcf21fc 100755 --- a/container-disc/src/main/sh/vespa-start-container-daemon.sh +++ b/container-disc/src/main/sh/vespa-start-container-daemon.sh @@ -212,7 +212,6 @@ exec $numactlcmd $envcmd java \ -Djdisc.config.file="$cfpfile" \ -Djdisc.export.packages=${jdisc_export_packages} \ -Djdisc.cache.path="$bundlecachedir" \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path="${VESPA_HOME}/lib/jars" \ -Djdisc.logger.enabled=false \ -Djdisc.logger.level=ALL \ diff --git a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java index 89ecc931efb..48657de1910 100644 --- a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java +++ b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java @@ -39,7 +39,7 @@ public class NetworkMultiplexerHolder extends AbstractComponent { public void deconstruct() { synchronized (monitor) { if (net != null) { - net.destroy(); + net.disown(); net = null; } destroyed = true; diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java index 922e4140868..009d1619503 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java @@ -9,6 +9,8 @@ import com.yahoo.jdisc.handler.ContentChannel; import com.yahoo.jdisc.handler.RequestDeniedException; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.service.ClientProvider; + +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import com.yahoo.messagebus.EmptyReply; import com.yahoo.messagebus.Error; @@ -29,9 +31,10 @@ import java.util.logging.Logger; public final class MbusClient extends AbstractResource implements ClientProvider, ReplyHandler { private static final Logger log = Logger.getLogger(MbusClient.class.getName()); + private static final AtomicInteger threadId = new AtomicInteger(0); private final BlockingQueue<MbusRequest> queue = new LinkedBlockingQueue<>(); private final ClientSession session; - private final Thread thread = new Thread(new SenderTask(), "MbusClient"); + private final Thread thread; private volatile boolean done = false; private final ResourceReference sessionReference; @@ -39,6 +42,7 @@ public final class MbusClient extends AbstractResource implements ClientProvider public MbusClient(ClientSession session) { this.session = session; this.sessionReference = session.refer(); + thread = new Thread(new SenderTask(), "mbus-client-" + threadId.getAndIncrement()); } @Override diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java index 67badddddd2..a165a0b8c64 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java @@ -10,13 +10,19 @@ import com.yahoo.jdisc.handler.ContentChannel; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.service.CurrentContainer; import com.yahoo.jdisc.service.ServerProvider; + +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; -import com.yahoo.messagebus.*; + +import com.yahoo.messagebus.EmptyReply; import com.yahoo.messagebus.Error; +import com.yahoo.messagebus.ErrorCode; +import com.yahoo.messagebus.Message; +import com.yahoo.messagebus.MessageHandler; +import com.yahoo.messagebus.Reply; import com.yahoo.messagebus.shared.ServerSession; import java.net.URI; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** @@ -24,8 +30,9 @@ import java.util.logging.Logger; */ public final class MbusServer extends AbstractResource implements ServerProvider, MessageHandler { + private enum State {INITIALIZING, RUNNING, STOPPED} private final static Logger log = Logger.getLogger(MbusServer.class.getName()); - private final AtomicBoolean running = new AtomicBoolean(false); + private final AtomicReference<State> runState = new AtomicReference<>(State.INITIALIZING); private final CurrentContainer container; private final ServerSession session; private final URI uri; @@ -44,26 +51,33 @@ public final class MbusServer extends AbstractResource implements ServerProvider public void start() { log.log(Level.FINE, "Starting message bus server."); session.connect(); - running.set(true); + runState.set(State.RUNNING); } @Override public void close() { log.log(Level.FINE, "Closing message bus server."); - running.set(false); + runState.set(State.STOPPED); } @Override protected void destroy() { log.log(Level.FINE, "Destroying message bus server."); - running.set(false); + runState.set(State.STOPPED); sessionReference.close(); } @Override public void handleMessage(Message msg) { - if (!running.get()) { - dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "Session temporarily closed."); + State state = runState.get(); + if (state == State.INITIALIZING) { + dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "MBusServer not started."); + return; + } + if (state == State.STOPPED) { + // We might need to detect requests originating from the same JVM, as they nede to fail fast + // As they are holding references to the container preventing proper shutdown. + dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "MBusServer has been closed."); return; } if (msg.getTrace().shouldTrace(6)) { diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java b/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java index 0964a254cf2..22322a49a78 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java @@ -10,5 +10,5 @@ import com.yahoo.messagebus.Result; */ public interface ClientSession extends SharedResource { - public Result sendMessage(Message msg); + Result sendMessage(Message msg); } diff --git a/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java b/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java index bf89f3869ed..0c387456cf3 100644 --- a/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java +++ b/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java @@ -31,6 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import static com.yahoo.messagebus.ErrorCode.APP_FATAL_ERROR; +import static com.yahoo.messagebus.ErrorCode.SEND_QUEUE_CLOSED; import static com.yahoo.messagebus.ErrorCode.SESSION_BUSY; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java index 970a70c6885..341b521212e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.aws; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.util.List; import java.util.Optional; @@ -12,7 +13,7 @@ import java.util.Optional; public class NoopRoleService implements RoleService { @Override - public Optional<TenantRoles> createTenantRole(TenantName tenant) { + public Optional<TenantRoles> createTenantRole(Tenant tenant) { return Optional.empty(); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java index d27fa0a5bd8..61007b9ff46 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.aws; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.util.List; import java.util.Optional; @@ -11,7 +12,7 @@ import java.util.Optional; */ public interface RoleService { - Optional<TenantRoles> createTenantRole(TenantName tenant); + Optional<TenantRoles> createTenantRole(Tenant tenant); /** Retrieve the names of the tenant roles (host and container). Does not guarantee these roles exist */ TenantRoles getTenantRole(TenantName tenant); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java index 7539ef3c63a..f650f71c0ec 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneApi; @@ -89,4 +90,7 @@ public interface ZoneRegistry { /** Returns a URL to the controller's api endpoint */ URI apiUrl(); + /** IAM tenant developer role ARN */ + Optional<String> tenantDeveloperRoleArn(TenantName tenant); + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java index 7fa46031c98..7fa46031c98 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java index 1060b118beb..1060b118beb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java index cf6d73cb8f8..cf6d73cb8f8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java index 15f2f97e7d1..15f2f97e7d1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java index 80982d70107..80982d70107 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java index a20477d7aab..81c08e1083b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java @@ -1,10 +1,6 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.tenant; -import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore; - -import java.util.ArrayList; -import java.util.List; import java.util.Objects; /** diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java index a12f351abd6..a12f351abd6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java index a00dd626f0a..a00dd626f0a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java index 9218bfcd850..9218bfcd850 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java index ca173437dd1..a7555307a59 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java @@ -33,8 +33,6 @@ public class CuratorArchiveBucketDb { * Policy size limit is 20kb, about 550 bytes for non-tenant related policies. Each tenant * needs about 500 + len(role_arn) bytes, we limit role_arn to 100 characters, so we can * fit about (20k - 550) / 600 ~ 32 tenants per bucket. - * - * This limit is only enforced for public systems as non-public systems does not use tenant specific policies. */ private final static int TENANTS_PER_BUCKET = 30; @@ -86,7 +84,7 @@ public class CuratorArchiveBucketDb { .orElseGet(() -> { // If not, find an existing bucket with space Optional<ArchiveBucket> unfilledBucket = zoneBuckets.stream() - .filter(bucket -> !system.isPublic() || bucket.tenants().size() < TENANTS_PER_BUCKET) + .filter(bucket -> bucket.tenants().size() < TENANTS_PER_BUCKET) .findAny(); // And place the tenant in that bucket. diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java index b096a853541..6c4f5ffff9d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java @@ -2,16 +2,25 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.google.common.collect.Maps; +import com.yahoo.config.provision.TenantName; +import com.yahoo.config.provision.zone.ZoneApi; +import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.jdisc.Metric; +import com.yahoo.vespa.flags.BooleanFlag; +import com.yahoo.vespa.flags.FetchVector; +import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.time.Duration; +import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -27,6 +36,8 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { private final ArchiveService archiveService; private final ZoneRegistry zoneRegistry; private final Metric metric; + private final BooleanFlag archiveEnabled; + private final BooleanFlag developerRoleEnabled; public ArchiveAccessMaintainer(Controller controller, Metric metric, Duration interval) { super(controller, interval); @@ -34,6 +45,8 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { this.archiveService = controller.serviceRegistry().archiveService(); this.zoneRegistry = controller().zoneRegistry(); this.metric = metric; + this.archiveEnabled = Flags.ENABLE_ONPREM_TENANT_S3_ARCHIVE.bindTo(controller().flagSource()); + this.developerRoleEnabled = Flags.ENABLE_TENANT_DEVELOPER_ROLE.bindTo(controller().flagSource()); } @Override @@ -43,22 +56,45 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { metric.set(bucketCountMetricName, archiveBucketDb.buckets(zoneId).size(), metric.createContext(Map.of("zone", zoneId.value())))); - var tenantArchiveAccessRoles = controller().tenants().asList().stream() - .filter(t -> t instanceof CloudTenant) - .map(t -> (CloudTenant) t) - .filter(t -> t.archiveAccessRole().isPresent()) - .collect(Collectors.toUnmodifiableMap( - Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow())); - zoneRegistry.zones().controllerUpgraded().ids().forEach(zoneId -> - archiveBucketDb.buckets(zoneId).forEach(archiveBucket -> - archiveService.updateBucketAndKeyPolicy(zoneId, archiveBucket, - Maps.filterEntries(tenantArchiveAccessRoles, - entry -> archiveBucket.tenants().contains(entry.getKey()))) - ) + zoneRegistry.zones().controllerUpgraded().zones().forEach(z -> { + ZoneId zoneId = z.getId(); + var tenantArchiveAccessRoles = tenantArchiveAccessRoles(z); + archiveBucketDb.buckets(zoneId).forEach(archiveBucket -> + archiveService.updateBucketAndKeyPolicy(zoneId, archiveBucket, + Maps.filterEntries(tenantArchiveAccessRoles, + entry -> archiveBucket.tenants().contains(entry.getKey()))) + ); + } ); return 1.0; } + private Map<TenantName, String> tenantArchiveAccessRoles(ZoneApi zone) { + List<Tenant> tenants = controller().tenants().asList(); + if (zoneRegistry.system().isPublic()) { + return tenants.stream() + .filter(t -> t instanceof CloudTenant) + .map(t -> (CloudTenant) t) + .filter(t -> t.archiveAccessRole().isPresent()) + .collect(Collectors.toUnmodifiableMap( + Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow())); + } else { + return tenants.stream() + .filter(t -> t instanceof AthenzTenant + && enabled(archiveEnabled, t, zone) && enabled(developerRoleEnabled, t, zone)) + .map(Tenant::name) + .collect(Collectors.toUnmodifiableMap( + Function.identity(), t -> zoneRegistry.tenantDeveloperRoleArn(t).orElseThrow())); + + } + } + + private boolean enabled(BooleanFlag flag, Tenant tenant, ZoneApi zone) { + return flag.with(FetchVector.Dimension.TENANT_ID, tenant.name().value()) + .with(FetchVector.Dimension.ZONE_ID, zone.getId().value()) + .value(); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java index ce6f9c802d6..d2b43dc63d9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java @@ -27,9 +27,7 @@ public class TenantRoleMaintainer extends ControllerMaintainer { var tenants = controller().tenants().asList(); // Create separate athenz service for all tenants - tenants.stream() - .map(Tenant::name) - .forEach(roleService::createTenantRole); + tenants.forEach(roleService::createTenantRole); // Until we have moved to separate athenz service per tenant, make sure we update the shared policy // to allow ssh logins for hosts in prod/perf with a separate tenant iam role. diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 1a2acb82348..98ac789de04 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -1967,6 +1967,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .flatMap(options -> optional("vespaVersion", options)) .map(Version::fromString); + ensureApplicationExists(TenantAndApplicationId.from(id), request); + controller.jobController().deploy(id, type, version, applicationPackage); RunId runId = controller.jobController().last(id, type).get().id(); Slime slime = new Slime(); @@ -2617,6 +2619,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { applicationPackage, Optional.of(requireUserPrincipal(request))); + ensureApplicationExists(TenantAndApplicationId.from(tenant, application), request); + return JobControllerApiHandlerHelper.submitResponse(controller.jobController(), tenant, application, @@ -2718,5 +2722,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .anyMatch(definition -> definition == RoleDefinition.hostedOperator); } + private void ensureApplicationExists(TenantAndApplicationId id, HttpRequest request) { + if (controller.applications().getApplication(id).isEmpty()) { + log.fine("Application does not exist in public, creating: " + id); + var credentials = accessControlRequests.credentials(id.tenant(), null /* not used on public */ , request.getJDiscRequest()); + controller.applications().createApplication(id, credentials); + } + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java index 157f57b3bea..7e88f127026 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java @@ -20,10 +20,7 @@ import com.yahoo.slime.SlimeStream; import com.yahoo.slime.SlimeUtils; import com.yahoo.text.Text; import com.yahoo.vespa.configserver.flags.FlagsDb; -import com.yahoo.vespa.flags.BooleanFlag; -import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagSource; -import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.IntFlag; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.Controller; @@ -71,9 +68,7 @@ public class UserApiHandler extends LoggingRequestHandler { private final UserManagement users; private final Controller controller; private final FlagsDb flagsDb; - private final BooleanFlag enable_public_signup_flow; private final IntFlag maxTrialTenants; - private final BooleanFlag enabledHorizonDashboard; @Inject public UserApiHandler(Context parentCtx, UserManagement users, Controller controller, FlagSource flagSource, FlagsDb flagsDb) { @@ -81,9 +76,7 @@ public class UserApiHandler extends LoggingRequestHandler { this.users = users; this.controller = controller; this.flagsDb = flagsDb; - this.enable_public_signup_flow = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource); this.maxTrialTenants = PermanentFlags.MAX_TRIAL_TENANTS.bindTo(flagSource); - this.enabledHorizonDashboard = Flags.ENABLED_HORIZON_DASHBOARD.bindTo(flagSource); } @Override @@ -173,9 +166,6 @@ public class UserApiHandler extends LoggingRequestHandler { root.setBool("isPublic", controller.system().isPublic()); root.setBool("isCd", controller.system().isCd()); - // TODO (freva): Remove after users have migrated to use 'flags' - root.setBool(enable_public_signup_flow.id().toString(), - enable_public_signup_flow.with(FetchVector.Dimension.CONSOLE_USER_EMAIL, user.email()).value()); root.setBool("hasTrialCapacity", hasTrialCapacity()); toSlime(root.setObject("user"), user); @@ -190,10 +180,6 @@ public class UserApiHandler extends LoggingRequestHandler { Cursor tenantRolesObject = tenantObject.setArray("roles"); tenantRolesByTenantName.getOrDefault(tenant, List.of()) .forEach(role -> tenantRolesObject.addString(role.definition().name())); - if (controller.system().isPublic()) { - tenantObject.setBool(enabledHorizonDashboard.id().toString(), - enabledHorizonDashboard.with(FetchVector.Dimension.TENANT_ID, tenant.value()).value()); - } }); if (!operatorRoles.isEmpty()) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index fe03b69a3fe..7ce17aff782 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneApi; @@ -216,6 +217,8 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry return URI.create("https://api.tld:4443/"); } + @Override public Optional<String> tenantDeveloperRoleArn(TenantName tenant) { return Optional.empty(); } + @Override public boolean hasZone(ZoneId zoneId) { return zones.stream().anyMatch(zone -> zone.getId().equals(zoneId)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index 5f76a30bf45..8c6d368d93a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -1,6 +1,7 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.application; +import ai.vespa.hosted.api.MultiPartStreamer; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; @@ -10,10 +11,12 @@ import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.LockedTenant; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; @@ -232,6 +235,42 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { 200); } + @Test + public void create_application_on_deploy() { + var application = ApplicationName.from("unique"); + var applicationPackage = new ApplicationPackageBuilder().build(); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isEmpty()); + + tester.assertResponse( + request("/application/v4/tenant/scoober/application/unique/instance/default/deploy/dev-aws-us-east-1c", POST) + .data(createApplicationDeployData(Optional.of(applicationPackage), Optional.empty(), true)) + .roles(Set.of(Role.developer(tenantName))), + "{\"message\":\"Deployment started in run 1 of dev-aws-us-east-1c for scoober.unique. This may take about 15 minutes the first time.\",\"run\":1}"); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isPresent()); + } + + @Test + public void create_application_on_submit() { + var application = ApplicationName.from("unique"); + var applicationPackage = new ApplicationPackageBuilder() + .trustDefaultCertificate() + .build(); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isEmpty()); + + var data = ApplicationApiTest.createApplicationSubmissionData(applicationPackage, 123); + + tester.assertResponse( + request("/application/v4/tenant/scoober/application/unique/submit", POST) + .data(data) + .roles(Set.of(Role.developer(tenantName))), + "{\"message\":\"Application package version: 1.0.1-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}"); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isPresent()); + } + private ApplicationPackageBuilder prodBuilder() { return new ApplicationPackageBuilder() .instances("default") @@ -264,8 +303,31 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { JobType.productionAwsUsEast1c, Optional.empty(), applicationPackage); + } + private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, + Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) { + MultiPartStreamer streamer = new MultiPartStreamer(); + streamer.addJson("deployOptions", deployOptions(deployDirectly, applicationVersion)); + applicationPackage.ifPresent(ap -> streamer.addBytes("applicationZip", ap.zippedContent())); + return streamer; + } + + private String deployOptions(boolean deployDirectly, Optional<ApplicationVersion> applicationVersion) { + return "{\"vespaVersion\":null," + + "\"ignoreValidationErrors\":false," + + "\"deployDirectly\":" + deployDirectly + + applicationVersion.map(version -> + "," + + "\"buildNumber\":" + version.buildNumber().getAsLong() + "," + + "\"sourceRevision\":{" + + "\"repository\":\"" + version.source().get().repository() + "\"," + + "\"branch\":\"" + version.source().get().branch() + "\"," + + "\"commit\":\"" + version.source().get().commit() + "\"" + + "}" + ).orElse("") + + "}"; } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index fb860063696..596a0b186db 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -1,4 +1,4 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.application; import ai.vespa.hosted.api.MultiPartStreamer; @@ -250,7 +250,7 @@ public class ApplicationApiTest extends ControllerContainerTest { var app1 = deploymentTester.newDeploymentContext(id); // POST (deploy) an application to start a manual deployment in prod is not allowed - MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/production-us-east-3/", POST) .data(entity) .userIdentity(USER_ID), @@ -289,7 +289,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST an application package is not generally allowed under user instance tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/deploy/dev-us-east-1", POST) .userIdentity(OTHER_USER_ID) - .data(createApplicationDeployData(applicationPackageInstance1, false)), + .data(createApplicationDeployData(applicationPackageInstance1)), accessDenied, 403); @@ -303,7 +303,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST an application package is not allowed under user instance for tenant admins tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/myuser/deploy/dev-us-east-1", POST) .userIdentity(USER_ID) - .data(createApplicationDeployData(applicationPackageInstance1, false)), + .data(createApplicationDeployData(applicationPackageInstance1)), new File("deployment-job-accepted-2.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/myuser/job/dev-us-east-1/diff/1", GET).userIdentity(HOSTED_VESPA_OPERATOR), @@ -1026,38 +1026,24 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testDeployDirectly() { + public void testDeployWithApplicationPackage() { // Setup - createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); - // Create tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), - new File("tenant-without-applications.json")); - - // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) - .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), - new File("instance-reference.json")); - - // Add build service to operator role - addUserToHostedOperatorRole(HostedAthenzIdentities.from(SCREWDRIVER_ID)); - // POST (deploy) a system application with an application package - MultiPartStreamer noAppEntity = createApplicationDeployData(Optional.empty(), true); + MultiPartStreamer noAppEntity = createApplicationDeployData(Optional.empty()); tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/routing/environment/prod/region/us-central-1/instance/default/deploy", POST) .data(noAppEntity) .userIdentity(HOSTED_VESPA_OPERATOR), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Deployment of system applications during a system upgrade is not allowed\"}", 400); - deploymentTester.controllerTester().upgradeSystem(deploymentTester.controller().readVersionStatus().controllerVersion().get().versionNumber()); + deploymentTester.controllerTester() + .upgradeSystem(deploymentTester.controller().readVersionStatus().controllerVersion().get() + .versionNumber()); tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/routing/environment/prod/region/us-central-1/instance/default/deploy", POST) - .data(noAppEntity) - .userIdentity(HOSTED_VESPA_OPERATOR), - new File("deploy-result.json")); + .data(noAppEntity) + .userIdentity(HOSTED_VESPA_OPERATOR), + new File("deploy-result.json")); } @Test @@ -1113,7 +1099,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testErrorResponses() throws Exception { + public void testErrorResponses() { createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // PUT (update) non-existing tenant returns 403 as tenant access cannot be determined when the tenant does not exist @@ -1223,7 +1209,7 @@ public class ApplicationApiTest extends ControllerContainerTest { 400); // POST (deploy) an application to legacy deploy path - MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-east-1/instance/instance1/deploy", POST) .data(entity) .userIdentity(USER_ID), @@ -1330,7 +1316,7 @@ public class ApplicationApiTest extends ControllerContainerTest { 200); // Deploy to an authorized zone by a user tenant is disallowed - MultiPartStreamer entity = createApplicationDeployData(applicationPackageDefault, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageDefault); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(entity) .userIdentity(USER_ID), @@ -1437,7 +1423,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); createTenantAndApplication(); - MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackage); // POST (deploy) an application to dev through a deployment job, with user instance and a proper tenant tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST) .data(entity) @@ -1484,7 +1470,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); // deploy the application to a dev zone. Should fail since the developer is not authorized to launch the service - MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackage); tester.assertResponse(request("/application/v4/tenant/sandbox/application/myapp/instance/default/deploy/dev-us-east-1", POST) .data(entity) .userIdentity(developer), @@ -1646,7 +1632,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testServiceView() throws Exception { + public void testServiceView() { createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); String serviceApi="/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service"; // Not allowed to request apis not listed in feature flag allowed-service-view-apis. e.g /document/v1 @@ -1671,6 +1657,36 @@ public class ApplicationApiTest extends ControllerContainerTest { 403); } + @Test + public void create_application_on_deploy() { + // Setup + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); + + // Create tenant + tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + new File("tenant-without-applications.json")); + + // Deploy application + var id = ApplicationId.from("tenant1", "application1", "instance1"); + var appId = TenantAndApplicationId.from(id); + var entity = createApplicationDeployData(applicationPackageInstance1); + + assertTrue(tester.controller().applications().getApplication(appId).isEmpty()); + + // POST (deploy) an application to start a manual deployment to dev + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1/", POST) + .data(entity) + .oktaIdentityToken(OKTA_IT) + .oktaAccessToken(OKTA_AT) + .userIdentity(USER_ID), + "{\"message\":\"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.\",\"run\":1}"); + + assertTrue(tester.controller().applications().getApplication(appId).isPresent()); + } + private static String serializeInstant(Instant i) { return DateTimeFormatter.ISO_INSTANT.format(i.truncatedTo(ChronoUnit.SECONDS)); } @@ -1683,18 +1699,18 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); } - private MultiPartStreamer createApplicationDeployData(ApplicationPackage applicationPackage, boolean deployDirectly) { - return createApplicationDeployData(Optional.of(applicationPackage), deployDirectly); + private MultiPartStreamer createApplicationDeployData(ApplicationPackage applicationPackage) { + return createApplicationDeployData(Optional.of(applicationPackage)); } - private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, boolean deployDirectly) { - return createApplicationDeployData(applicationPackage, Optional.empty(), deployDirectly); + private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage) { + return createApplicationDeployData(applicationPackage, Optional.empty()); } private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, - Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) { + Optional<ApplicationVersion> applicationVersion) { MultiPartStreamer streamer = new MultiPartStreamer(); - streamer.addJson("deployOptions", deployOptions(deployDirectly, applicationVersion)); + streamer.addJson("deployOptions", deployOptions(applicationVersion)); applicationPackage.ifPresent(ap -> streamer.addBytes("applicationZip", ap.zippedContent())); return streamer; } @@ -1706,10 +1722,9 @@ public class ApplicationApiTest extends ControllerContainerTest { .addBytes(EnvironmentResource.APPLICATION_TEST_ZIP, "content".getBytes()); } - private String deployOptions(boolean deployDirectly, Optional<ApplicationVersion> applicationVersion) { + private String deployOptions(Optional<ApplicationVersion> applicationVersion) { return "{\"vespaVersion\":null," + - "\"ignoreValidationErrors\":false," + - "\"deployDirectly\":" + deployDirectly + + "\"ignoreValidationErrors\":false" + applicationVersion.map(version -> "," + "\"buildNumber\":" + version.buildNumber().getAsLong() + "," + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json index 0a416600b2c..006c3b98a4d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json @@ -1,7 +1,6 @@ { "isPublic": false, "isCd": false, - "enable-public-signup-flow": (ignore), "hasTrialCapacity": (ignore), "user": { "name": "Joe Developer", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json index 4e179ad83c5..4ae55e97baa 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json @@ -1,7 +1,6 @@ { "isPublic": true, "isCd": false, - "enable-public-signup-flow": (ignore), "hasTrialCapacity": true, "user": { "name": "Joe Developer", @@ -14,20 +13,17 @@ "roles": [ "developer", "reader" - ], - "enabled-horizon-dashboard":false + ] }, "tenant1": { "roles": [ "administrator" - ], - "enabled-horizon-dashboard":false + ] }, "tenant2": { "roles": [ "developer" - ], - "enabled-horizon-dashboard":false + ] } }, "flags": [{"id":"enable-public-signup-flow","rules":[{"value":false}]}] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json index 7eb445140e7..9f9578e6ed8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json @@ -1,7 +1,6 @@ { "isPublic": (ignore), "isCd": (ignore), - "enable-public-signup-flow": (ignore), "hasTrialCapacity": (ignore), "user": { "name": "Joe Developer", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json index 3c1edab8cfc..2b98a75068a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json @@ -1,7 +1,6 @@ { "isPublic": true, "isCd": false, - "enable-public-signup-flow": true, "hasTrialCapacity": false, "user": { "name": "Joe Developer", diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java index f65550bbf0f..e0515a84b5d 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java @@ -16,13 +16,6 @@ public class GetDocumentMessage extends DocumentMessage { private String fieldSet = DEFAULT_FIELD_SET; /** - * Constructs a new message for deserialization. - */ - GetDocumentMessage() { - // empty - } - - /** * Constructs a new document get message. * * @param documentId The identifier of the document to get. diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java index b2bf26d3b05..58bc50088d7 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java @@ -19,13 +19,6 @@ public class PutDocumentMessage extends TestAndSetMessage { private LazyDecoder decoder = null; /** - * Constructs a new message for deserialization. - */ - PutDocumentMessage() { - // empty - } - - /** * Constructs a new message from a byte buffer. * * @param decoder The decoder to use for deserialization. diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt index 16dd729b1f1..8a7cd4f66bd 100644 --- a/eval/CMakeLists.txt +++ b/eval/CMakeLists.txt @@ -46,6 +46,7 @@ vespa_define_module( src/tests/gp/ponder_nov2017 src/tests/instruction/add_trivial_dimension_optimizer src/tests/instruction/dense_dot_product_function + src/tests/instruction/dense_hamming_distance src/tests/instruction/dense_inplace_join_function src/tests/instruction/dense_matmul_function src/tests/instruction/dense_multi_matmul_function diff --git a/eval/src/tests/instruction/dense_hamming_distance/CMakeLists.txt b/eval/src/tests/instruction/dense_hamming_distance/CMakeLists.txt new file mode 100644 index 00000000000..3d18f9613b3 --- /dev/null +++ b/eval/src/tests/instruction/dense_hamming_distance/CMakeLists.txt @@ -0,0 +1,9 @@ + +vespa_add_executable(eval_dense_hamming_distance_test_app TEST + SOURCES + dense_hamming_distance_test.cpp + DEPENDS + vespaeval + GTest::GTest +) +vespa_add_test(NAME eval_dense_hamming_distance_test_app COMMAND eval_dense_hamming_distance_test_app) diff --git a/eval/src/tests/instruction/dense_hamming_distance/dense_hamming_distance_test.cpp b/eval/src/tests/instruction/dense_hamming_distance/dense_hamming_distance_test.cpp new file mode 100644 index 00000000000..8eaa0e72ad5 --- /dev/null +++ b/eval/src/tests/instruction/dense_hamming_distance/dense_hamming_distance_test.cpp @@ -0,0 +1,91 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/eval/eval/fast_value.h> +#include <vespa/eval/eval/tensor_function.h> +#include <vespa/eval/eval/test/eval_fixture.h> +#include <vespa/eval/eval/test/gen_spec.h> +#include <vespa/eval/instruction/dense_hamming_distance.h> +#include <vespa/vespalib/util/stash.h> +#include <vespa/vespalib/util/stringfmt.h> + +#include <vespa/vespalib/util/require.h> +#include <vespa/vespalib/gtest/gtest.h> + +#include <vespa/log/log.h> +LOG_SETUP("dense_hamming_distance_function_test"); + +using namespace vespalib; +using namespace vespalib::eval; +using namespace vespalib::eval::test; + +const ValueBuilderFactory &prod_factory = FastValueBuilderFactory::get(); + +struct FunInfo { + using LookFor = DenseHammingDistance; + void verify(const LookFor &fun) const { + EXPECT_TRUE(fun.result_is_mutable()); + } +}; + +void assertOptimized(const vespalib::string &expr) { + CellTypeSpace just_int8({CellType::INT8}, 2); + EvalFixture::verify<FunInfo>(expr, {FunInfo{}}, just_int8); + CellTypeSpace just_double({CellType::DOUBLE}, 2); + EvalFixture::verify<FunInfo>(expr, {}, just_double); +} + +void assertNotOptimized(const vespalib::string &expr) { + CellTypeSpace just_int8({CellType::INT8}, 2); + EvalFixture::verify<FunInfo>(expr, {}, just_int8); +} + +TEST(DenseHammingDistanceOptimizer, hamming_distance_works_with_tensor_function) { + assertOptimized("reduce(hamming(x5$1,x5$2),sum)"); + assertOptimized("reduce(hamming(x5$1,x5$2),sum,x)"); + assertOptimized("reduce(join(x5$1,x5$2,f(x,y)(hamming(x,y))),sum)"); + assertOptimized("reduce(join(x5$1,x5$2,f(x,y)(hamming(x,y))),sum,x)"); +} + +TEST(DenseHammingDistanceOptimizer, hamming_distance_with_compatible_dimensions_is_optimized) { + // various vector sizes + assertOptimized("reduce(hamming(x1$1,x1$2),sum)"); + assertOptimized("reduce(hamming(x3$1,x3$2),sum)"); + assertOptimized("reduce(hamming(x7$1,x7$2),sum)"); + assertOptimized("reduce(hamming(x8$1,x8$2),sum)"); + assertOptimized("reduce(hamming(x9$1,x9$2),sum)"); + assertOptimized("reduce(hamming(x17$1,x17$2),sum)"); + // multiple dimensions + assertOptimized("reduce(hamming(x3y3$1,x3y3$2),sum)"); + assertOptimized("reduce(hamming(x3y4$1,x3y4$2),sum)"); + // with trivial dimensions + assertOptimized("reduce(hamming(a1x3$1,x3$2),sum)"); + assertOptimized("reduce(hamming(x3$1z1,x3$2),sum)"); + assertOptimized("reduce(hamming(a1x3$1,b1x3$2z1),sum)"); +} + +TEST(DenseHammingDistanceOptimizer, hamming_distance_with_mapped_dimensions_is_NOT_optimized) { + assertNotOptimized("reduce(hamming(x3_1$1,x3_1$2),sum)"); + assertNotOptimized("reduce(hamming(x3_1y2$1,x3_1y2$2),sum)"); +} + +TEST(DenseHammingDistanceOptimizer, hamming_distance_with_incompatible_dimensions_is_NOT_optimized) { + assertNotOptimized("reduce(hamming(x3,y3),sum)"); + assertNotOptimized("reduce(hamming(y3,x3),sum)"); + assertNotOptimized("reduce(hamming(x3,x3y3),sum)"); + assertNotOptimized("reduce(hamming(x3y3,x3),sum)"); +} + +TEST(DenseHammingDistanceOptimizer, expressions_similar_to_hamming_distance_are_not_optimized) { + assertNotOptimized("reduce(hamming(x3$1,x3$2),prod)"); +} + +TEST(DenseHammingDistanceOptimizer, result_must_be_double_to_trigger_optimization) { + assertOptimized("reduce(hamming(x3y3$1,x3y3$2),sum,x,y)"); + assertNotOptimized("reduce(hamming(x3y3$1,x3y3$2),sum,x)"); + assertNotOptimized("reduce(hamming(x3y3$1,x3y3$2),sum,y)"); +} + +//----------------------------------------------------------------------------- + +GTEST_MAIN_RUN_ALL_TESTS() + diff --git a/eval/src/vespa/eval/eval/optimize_tensor_function.cpp b/eval/src/vespa/eval/eval/optimize_tensor_function.cpp index 64acbceff04..c2e8d886fde 100644 --- a/eval/src/vespa/eval/eval/optimize_tensor_function.cpp +++ b/eval/src/vespa/eval/eval/optimize_tensor_function.cpp @@ -28,6 +28,7 @@ #include <vespa/eval/instruction/vector_from_doubles_function.h> #include <vespa/eval/instruction/dense_tensor_create_function.h> #include <vespa/eval/instruction/dense_tensor_peek_function.h> +#include <vespa/eval/instruction/dense_hamming_distance.h> #include <vespa/log/log.h> LOG_SETUP(".eval.eval.optimize_tensor_function"); @@ -53,6 +54,7 @@ const TensorFunction &optimize_for_factory(const ValueBuilderFactory &, const Te child.set(DenseMatMulFunction::optimize(child.get(), stash)); child.set(DenseMultiMatMulFunction::optimize(child.get(), stash)); child.set(MixedInnerProductFunction::optimize(child.get(), stash)); + child.set(DenseHammingDistance::optimize(child.get(), stash)); nodes.pop_back(); } } diff --git a/eval/src/vespa/eval/instruction/CMakeLists.txt b/eval/src/vespa/eval/instruction/CMakeLists.txt index 88e3272bb7c..3ed969c0a18 100644 --- a/eval/src/vespa/eval/instruction/CMakeLists.txt +++ b/eval/src/vespa/eval/instruction/CMakeLists.txt @@ -5,6 +5,7 @@ vespa_add_library(eval_instruction OBJECT add_trivial_dimension_optimizer.cpp dense_cell_range_function.cpp dense_dot_product_function.cpp + dense_hamming_distance.cpp dense_lambda_peek_function.cpp dense_lambda_peek_optimizer.cpp dense_matmul_function.cpp diff --git a/eval/src/vespa/eval/instruction/dense_hamming_distance.cpp b/eval/src/vespa/eval/instruction/dense_hamming_distance.cpp new file mode 100644 index 00000000000..2a68663631a --- /dev/null +++ b/eval/src/vespa/eval/instruction/dense_hamming_distance.cpp @@ -0,0 +1,91 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "dense_hamming_distance.h" +#include <vespa/eval/eval/operation.h> +#include <vespa/eval/eval/value.h> +#include <vespa/eval/eval/hamming_distance.h> + +#include <vespa/log/log.h> +LOG_SETUP(".eval.instruction.dense_hamming_distance"); + +namespace vespalib::eval { + +using namespace tensor_function; + +namespace { + + +size_t binary_hamming_distance(const void *lhs, const void *rhs, size_t sz) { + const uint64_t *words_a = static_cast<const uint64_t *>(lhs); + const uint64_t *words_b = static_cast<const uint64_t *>(rhs); + size_t sum = 0; + size_t i = 0; + for (; i * 8 + 7 < sz; ++i) { + uint64_t xor_bits = words_a[i] ^ words_b[i]; + sum += __builtin_popcountl(xor_bits); + } + if (__builtin_expect((i * 8 < sz), false)) { + const uint8_t *bytes_a = static_cast<const uint8_t *>(lhs); + const uint8_t *bytes_b = static_cast<const uint8_t *>(rhs); + for (i *= 8; i < sz; ++i) { + uint64_t xor_bits = bytes_a[i] ^ bytes_b[i]; + sum += __builtin_popcountl(xor_bits); + } + } + return sum; +}; + +void int8_hamming_to_double_op(InterpretedFunction::State &state, uint64_t vector_size) { + const auto &lhs = state.peek(1); + const auto &rhs = state.peek(0); + auto a = lhs.cells(); + auto b = rhs.cells(); + double result = binary_hamming_distance(a.data, b.data, vector_size); + state.pop_pop_push(state.stash.create<DoubleValue>(result)); +} + +bool compatible_types(const ValueType &lhs, const ValueType &rhs) { + return ((lhs.cell_type() == CellType::INT8) && + (rhs.cell_type() == CellType::INT8) && + lhs.is_dense() && + rhs.is_dense() && + (lhs.nontrivial_indexed_dimensions() == rhs.nontrivial_indexed_dimensions())); +} + +} // namespace <unnamed> + +DenseHammingDistance::DenseHammingDistance(const TensorFunction &lhs_child, + const TensorFunction &rhs_child) + : tensor_function::Op2(ValueType::double_type(), lhs_child, rhs_child) +{ +} + +InterpretedFunction::Instruction +DenseHammingDistance::compile_self(const ValueBuilderFactory &, Stash &) const +{ + auto op = int8_hamming_to_double_op; + const auto &lhs_type = lhs().result_type(); + const auto &rhs_type = rhs().result_type(); + LOG_ASSERT(lhs_type.dense_subspace_size() == rhs_type.dense_subspace_size()); + return InterpretedFunction::Instruction(op, lhs_type.dense_subspace_size()); +} + +const TensorFunction & +DenseHammingDistance::optimize(const TensorFunction &expr, Stash &stash) +{ + const auto & res_type = expr.result_type(); + auto reduce = as<Reduce>(expr); + if (res_type.is_double() && reduce && (reduce->aggr() == Aggr::SUM)) { + auto join = as<Join>(reduce->child()); + if (join && (join->function() == operation::Hamming::f)) { + const TensorFunction &lhs = join->lhs(); + const TensorFunction &rhs = join->rhs(); + if (compatible_types(lhs.result_type(), rhs.result_type())) { + return stash.create<DenseHammingDistance>(lhs, rhs); + } + } + } + return expr; +} + +} // namespace diff --git a/eval/src/vespa/eval/instruction/dense_hamming_distance.h b/eval/src/vespa/eval/instruction/dense_hamming_distance.h new file mode 100644 index 00000000000..efc70d74d21 --- /dev/null +++ b/eval/src/vespa/eval/instruction/dense_hamming_distance.h @@ -0,0 +1,22 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/eval/eval/tensor_function.h> + +namespace vespalib::eval { + +/** + * Tensor function for a hamming distance producing a scalar result. + **/ +class DenseHammingDistance : public tensor_function::Op2 +{ +public: + DenseHammingDistance(const TensorFunction &lhs_child, + const TensorFunction &rhs_child); + InterpretedFunction::Instruction compile_self(const ValueBuilderFactory &factory, Stash &stash) const override; + bool result_is_mutable() const override { return true; } + static const TensorFunction &optimize(const TensorFunction &expr, Stash &stash); +}; + +} // namespace 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 69d325d4841..28b031e5015 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -79,13 +79,6 @@ public class Flags { "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); - public static final UnboundBooleanFlag ENFORCE_RANK_PROFILE_INHERITANCE = defineFeatureFlag( - "enforce-rank-profile-inheritance", true, - List.of("baldersheim"), "2021-09-07", "2021-10-01", - "Should we enforce verification of rank-profile inheritance.", - "Takes effect at redeployment", - ZONE_ID, APPLICATION_ID); - public static final UnboundBooleanFlag SKIP_MBUS_REQUEST_THREAD = defineFeatureFlag( "skip-mbus-request-thread", false, List.of("baldersheim"), "2020-12-02", "2022-01-01", @@ -312,6 +305,15 @@ public class Flags { APPLICATION_ID ); + public static final UnboundBooleanFlag ENABLE_TENANT_DEVELOPER_ROLE = defineFeatureFlag( + "enable-tenant-developer-role", false, + List.of("bjorncs"), "2021-09-23", "2021-12-31", + "Enable tenant developer Athenz role in cd/main. Must be set on controller cluster only.", + "Takes effect immediately", + TENANT_ID + ); + + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, String createdAt, String expiresAt, String description, diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java index 6a733bd8942..487c60e0dcb 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java @@ -340,7 +340,6 @@ public abstract class ControllerHttpClient { Slime slime = new Slime(); Cursor rootObject = slime.setObject(); deployment.version().ifPresent(version -> rootObject.setString("vespaVersion", version)); - rootObject.setBool("deployDirectly", true); return toJson(slime); } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java index b561f08af92..05c3582a541 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java @@ -63,9 +63,8 @@ public abstract class AbstractResource implements SharedResource { activeReferences.add(referenceStack); state = currentStateDebugWithLock(); } - log.log(Level.WARNING, - getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }", - referenceStack); + log.log(Level.FINE, referenceStack, () -> + getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }"); return new DebugResourceReference(this, referenceStack); } @@ -87,9 +86,8 @@ public abstract class AbstractResource implements SharedResource { } doDestroy = activeReferences.isEmpty(); } - log.log(Level.WARNING, - getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }", - releaseStack); + log.log(Level.FINE, releaseStack, + () ->getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }"); if (doDestroy) { destroy(); } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java index c178147a952..a47c6b06321 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java @@ -2,7 +2,11 @@ package com.yahoo.jdisc.service; import com.yahoo.jdisc.Container; -import com.yahoo.jdisc.application.*; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.ContainerActivator; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.application.Application; +import com.yahoo.jdisc.application.UriPattern; import com.yahoo.jdisc.handler.RequestHandler; /** diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java index b58f3bc5138..3b5cbfd9cbc 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java @@ -37,7 +37,7 @@ public interface ServerProvider extends SharedResource { * {@link Container} does <em>not</em> call this method, instead it is a required step in the {@link Application} * initialization code.</p> */ - public void start(); + void start(); /** * <p>This is a synchronous method to close the listen port (or equivalent) of this ServerProvider and flush any @@ -48,5 +48,5 @@ public interface ServerProvider extends SharedResource { * <p>The {@link Container} does <em>not</em> call this method, instead it is a required step in the {@link * Application} shutdown code.</p> */ - public void close(); + void close(); } diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java index e75b5d0934a..4bd9bbf7f7f 100644 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java @@ -7,10 +7,9 @@ import com.yahoo.text.Utf8Array; import java.util.Deque; import java.util.Map; -import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** @@ -33,15 +32,16 @@ public class NetworkMultiplexer implements NetworkOwner { private final Network net; private final Deque<NetworkOwner> owners = new ConcurrentLinkedDeque<>(); private final Map<String, Deque<NetworkOwner>> sessions = new ConcurrentHashMap<>(); - private final boolean shared; + private final AtomicBoolean disowned; private NetworkMultiplexer(Network net, boolean shared) { net.attach(this); this.net = net; - this.shared = shared; + this.disowned = new AtomicBoolean( ! shared); } - /** Returns a network multiplexer which will be shared between several {@link NetworkOwner}s. */ + /** Returns a network multiplexer which will be shared between several {@link NetworkOwner}s, + * and will shut down when all these have detached, and {@link #disown()} has been called, in any order. */ public static NetworkMultiplexer shared(Network net) { return new NetworkMultiplexer(net, true); } @@ -100,6 +100,7 @@ public class NetworkMultiplexer implements NetworkOwner { owner.deliverMessage(message, session); } + /** Attach the network owner to this, allowing this to forward messages to it. */ public void attach(NetworkOwner owner) { if (owners.contains(owner)) throw new IllegalArgumentException(owner + " is already attached to this"); @@ -107,23 +108,27 @@ public class NetworkMultiplexer implements NetworkOwner { owners.add(owner); } + /** Detach the network owner from this, no longer allowing messages to it, and shutting down this is ownerless. */ public void detach(NetworkOwner owner) { if ( ! owners.remove(owner)) throw new IllegalArgumentException(owner + " not attached to this"); - if ( ! shared && owners.isEmpty()) - net.shutdown(); + destroyIfOwnerless(); } - public void destroy() { - if ( ! shared) - throw new UnsupportedOperationException("Destroy called on a dedicated multiplexer; " + - "this automatically shuts down when detached from"); + /** Signal that external ownership of this is relinquished, allowing destruction on last owner detachment. */ + public void disown() { + if (disowned.getAndSet(true)) + throw new IllegalStateException("Destroy called on a dedicated multiplexer--" + + "this automatically shuts down when detached from--or " + + "called multiple times on a shared multiplexer"); - if ( ! owners.isEmpty()) - log.warning("NetworkMultiplexer destroyed before all owners detached: " + this); + destroyIfOwnerless(); + } - net.shutdown(); + private void destroyIfOwnerless() { + if (disowned.get() && owners.isEmpty()) + net.shutdown(); } public Network net() { @@ -136,7 +141,7 @@ public class NetworkMultiplexer implements NetworkOwner { "net=" + net + ", owners=" + owners + ", sessions=" + sessions + - ", shared=" + shared + + ", destructible=" + disowned + '}'; } diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java new file mode 100644 index 00000000000..59cafed1836 --- /dev/null +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java @@ -0,0 +1,48 @@ +package com.yahoo.messagebus.network.rpc; + +import com.yahoo.jrt.slobrok.api.IMirror; +import com.yahoo.jrt.slobrok.api.Mirror; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class NamedRPCService implements RPCService { + private final IMirror mirror; + private final String pattern; + private int addressIdx = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); + private int addressGen = 0; + private List<Mirror.Entry> addressList = null; + + /** + * Create a new RPCService backed by the given network and using the given service pattern. + * + * @param mirror The naming server to send queries to. + * @param pattern The pattern to use when querying. + */ + public NamedRPCService(IMirror mirror, String pattern) { + this.mirror = mirror; + this.pattern = pattern; + } + + /** + * Resolve a concrete address from this service. This service may represent multiple remote sessions, so this will + * select one that is online. + * + * @return A concrete service address. + */ + public synchronized RPCServiceAddress resolve() { + if (addressGen != mirror.updates()) { + addressGen = mirror.updates(); + addressList = mirror.lookup(pattern); + } + if (addressList != null && !addressList.isEmpty()) { + ++addressIdx; + if (addressIdx >= addressList.size()) { + addressIdx = 0; + } + Mirror.Entry entry = addressList.get(addressIdx); + return new RPCServiceAddress(entry.getName(), entry.getSpec()); + } + return null; + } +} diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java index fb3c4cf9971..889df32ce1e 100644 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java @@ -2,10 +2,6 @@ package com.yahoo.messagebus.network.rpc; import com.yahoo.jrt.slobrok.api.IMirror; -import com.yahoo.jrt.slobrok.api.Mirror; - -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** * An RPCService represents a set of remote sessions matching a service pattern. The sessions are monitored using the @@ -13,23 +9,13 @@ import java.util.concurrent.ThreadLocalRandom; * * @author havardpe */ -public class RPCService { - - private final IMirror mirror; - private final String pattern; - private int addressIdx = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - private int addressGen = 0; - private List<Mirror.Entry> addressList = null; +public interface RPCService { - /** - * Create a new RPCService backed by the given network and using the given service pattern. - * - * @param mirror The naming server to send queries to. - * @param pattern The pattern to use when querying. - */ - public RPCService(IMirror mirror, String pattern) { - this.mirror = mirror; - this.pattern = pattern; + static RPCService create(IMirror mirror, String pattern) { + if (pattern.startsWith("tcp/")) { + return new TcpRPCService(pattern); + } + return new NamedRPCService(mirror, pattern); } /** @@ -38,38 +24,6 @@ public class RPCService { * * @return A concrete service address. */ - public RPCServiceAddress resolve() { - if (pattern.startsWith("tcp/")) { - int pos = pattern.lastIndexOf('/'); - if (pos > 0 && pos < pattern.length() - 1) { - RPCServiceAddress ret = new RPCServiceAddress(pattern, pattern.substring(0, pos)); - if (!ret.isMalformed()) { - return ret; - } - } - } else { - if (addressGen != mirror.updates()) { - addressGen = mirror.updates(); - addressList = mirror.lookup(pattern); - } - if (addressList != null && !addressList.isEmpty()) { - ++addressIdx; - if (addressIdx >= addressList.size()) { - addressIdx = 0; - } - Mirror.Entry entry = addressList.get(addressIdx); - return new RPCServiceAddress(entry.getName(), entry.getSpec()); - } - } - return null; - } + RPCServiceAddress resolve(); - /** - * Returns the pattern used when querying for the naming server for addresses. This is given at construtor time. - * - * @return The service pattern. - */ - String getPattern() { - return pattern; - } } diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java index 0a6a58d4e89..1b7bcf01731 100755 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java @@ -34,6 +34,13 @@ public class RPCServiceAddress implements ServiceAddress { public RPCServiceAddress(String serviceName, String connectionSpec) { this(serviceName, new Spec(connectionSpec)); } + public RPCServiceAddress(RPCServiceAddress blueprint) { + serviceName = blueprint.serviceName; + sessionName = blueprint.sessionName; + connectionSpec = blueprint.connectionSpec; + target = null; + } + @Override public boolean equals(Object obj) { diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java index abd33d6c9c2..a666a03c401 100755 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.messagebus.network.rpc; +import com.yahoo.concurrent.CopyOnWriteHashMap; + import java.util.LinkedHashMap; import java.util.Map; @@ -12,7 +14,7 @@ import java.util.Map; public class RPCServicePool { private final RPCNetwork net; - private final ThreadLocalCache services = new ThreadLocalCache(); + private final Map<Long, ServiceLRUCache> mapOfServiceCache; private final int maxSize; /** @@ -23,6 +25,7 @@ public class RPCServicePool { */ public RPCServicePool(RPCNetwork net, int maxSize) { this.net = net; + mapOfServiceCache = new CopyOnWriteHashMap<>(); this.maxSize = maxSize; } @@ -34,12 +37,12 @@ public class RPCServicePool { * @return A service address for the given pattern. */ public RPCServiceAddress resolve(String pattern) { - RPCService service = services.get().get(pattern); - if (service == null) { - service = new RPCService(net.getMirror(), pattern); - services.get().put(pattern, service); - } - return service.resolve(); + + return getPerThreadCache().computeIfAbsent(pattern, (key) -> RPCService.create(net.getMirror(), key)).resolve(); + } + + private ServiceLRUCache getPerThreadCache() { + return mapOfServiceCache.computeIfAbsent(Thread.currentThread().getId(), (key) -> new ServiceLRUCache(maxSize)); } /** @@ -49,7 +52,7 @@ public class RPCServicePool { * @return The current size of this pool. */ public int getSize() { - return services.get().size(); + return getPerThreadCache().size(); } /** @@ -59,21 +62,15 @@ public class RPCServicePool { * @return True if a corresponding service is in the pool. */ public boolean hasService(String pattern) { - return services.get().containsKey(pattern); - } - - private class ThreadLocalCache extends ThreadLocal<ServiceLRUCache> { - - @Override - protected ServiceLRUCache initialValue() { - return new ServiceLRUCache(); - } + return getPerThreadCache().containsKey(pattern); } - private class ServiceLRUCache extends LinkedHashMap<String, RPCService> { + private static class ServiceLRUCache extends LinkedHashMap<String, RPCService> { + private final int maxSize; - ServiceLRUCache() { + ServiceLRUCache(int maxSize) { super(16, 0.75f, true); + this.maxSize = maxSize; } @Override diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java new file mode 100644 index 00000000000..e2fae59b429 --- /dev/null +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java @@ -0,0 +1,23 @@ +package com.yahoo.messagebus.network.rpc; + +public class TcpRPCService implements RPCService { + private final RPCServiceAddress blueprint; + + TcpRPCService(String pattern) { + if ( ! pattern.startsWith("tcp/")) { + throw new IllegalArgumentException("Expect tcp adress to start with 'tcp/', was: " + pattern); + } + RPCServiceAddress ret = null; + int pos = pattern.lastIndexOf('/'); + if (pos > 0 && pos < pattern.length() - 1) { + ret = new RPCServiceAddress(pattern, pattern.substring(0, pos)); + if ( ret.isMalformed()) { + ret = null; + } + } + blueprint = ret; + } + public RPCServiceAddress resolve() { + return blueprint != null ? new RPCServiceAddress(blueprint) : null; + } +} diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java index 3bf754f29c7..6451a692fb6 100644 --- a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java +++ b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java @@ -86,7 +86,11 @@ public class NetworkMultiplexerTest { shared.detach(owner2); assertFalse(net.shutDown.get()); - shared.destroy(); + shared.attach(owner2); + shared.disown(); + assertFalse(net.shutDown.get()); + + shared.detach(owner2); assertTrue(net.shutDown.get()); } diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java index 1dbb30de585..0343b075579 100755 --- a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java +++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java @@ -83,11 +83,11 @@ public class ServiceAddressTestCase { } private void assertNullAddress(String pattern) { - assertNull(new RPCService(network.getMirror(), pattern).resolve()); + assertNull(RPCService.create(network.getMirror(), pattern).resolve()); } private void assertAddress(String pattern, String expectedSpec, String expectedSession) { - RPCService service = new RPCService(network.getMirror(), pattern); + RPCService service = RPCService.create(network.getMirror(), pattern); RPCServiceAddress obj = service.resolve(); assertNotNull(obj); assertNotNull(obj.getConnectionSpec()); diff --git a/searchcore/CMakeLists.txt b/searchcore/CMakeLists.txt index c0353747e80..36ef347b7ac 100644 --- a/searchcore/CMakeLists.txt +++ b/searchcore/CMakeLists.txt @@ -54,6 +54,7 @@ vespa_define_module( src/apps/vespa-transactionlog-inspect TESTS + src/tests/bmcluster/estimate_moved_docs_ratio src/tests/grouping src/tests/proton/attribute src/tests/proton/attribute/attribute_aspect_delayer diff --git a/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp b/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp index e5c3959d2d4..0227d9539d2 100644 --- a/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp +++ b/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp @@ -17,6 +17,8 @@ #include <vespa/searchcore/bmcluster/bm_node_stats_reporter.h> #include <vespa/searchcore/bmcluster/bm_range.h> #include <vespa/searchcore/bmcluster/bucket_selector.h> +#include <vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h> +#include <vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h> #include <vespa/searchcore/bmcluster/spi_bm_feed_handler.h> #include <vespa/searchlib/index/dummyfileheadercontext.h> #include <vespa/vespalib/io/fileutil.h> @@ -51,6 +53,8 @@ using search::bmcluster::BmNode; using search::bmcluster::BmNodeStatsReporter; using search::bmcluster::BmRange; using search::bmcluster::BucketSelector; +using search::bmcluster::CalculateMovedDocsRatio; +using search::bmcluster::EstimateMovedDocsRatio; using search::index::DummyFileHeaderContext; using storage::lib::State; @@ -100,76 +104,6 @@ vespalib::string& get_mode_name(Mode mode) { return (i < mode_names.size()) ? mode_names[i] : bad_mode_name; } -double -estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes) -{ - if (redundancy > lost_nodes) { - return 0.0; - } - double loss_ratio = 1.0; - for (uint32_t i = 0; i < redundancy; ++i) { - loss_ratio *= ((double) (lost_nodes - i)) / (num_nodes - i); - } - LOG(info, "estimated lost docs base ratio: %4.2f", loss_ratio); - return loss_ratio; -} - -double -estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes) -{ - double new_redundancy = redundancy; - double new_per_node_doc_ratio = new_redundancy / num_nodes; - double moved_ratio = new_per_node_doc_ratio * added_nodes; - LOG(info, "estimated_moved_docs_ratio_grow(%u,%u,%u)=%4.2f", redundancy, added_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes) -{ - double old_redundancy = redundancy; - double old_per_node_doc_ratio = old_redundancy / num_nodes; - uint32_t new_nodes = num_nodes - retired_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes; - LOG(info, "estimated_moved_docs_ratio_shrink(%u,%u,%u)=%4.2f", redundancy, retired_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes) -{ - double old_redundancy = redundancy; - double old_per_node_doc_ratio = old_redundancy / num_nodes; - uint32_t new_nodes = num_nodes - crashed_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double lost_docs_ratio = estimate_lost_docs_base_ratio(redundancy, crashed_nodes, num_nodes) * new_redundancy; - double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes - lost_docs_ratio; - LOG(info, "estimated_moved_docs_ratio_crash(%u,%u,%u)=%4.2f", redundancy, crashed_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes) -{ - uint32_t old_nodes = num_nodes - added_nodes; - double old_redundancy = std::min(redundancy, old_nodes); - double old_per_node_doc_ratio = old_redundancy / old_nodes; - uint32_t new_nodes = num_nodes - retired_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double moved_ratio = new_per_node_doc_ratio * added_nodes; - uint32_t stable_nodes = num_nodes - added_nodes - retired_nodes; - // Account for extra documents moved from retired nodes to stable nodes - double extra_per_stable_node_doc_ratio = new_per_node_doc_ratio * added_nodes / old_nodes; - double extra_moved_ratio = (std::min(1.0, new_per_node_doc_ratio + extra_per_stable_node_doc_ratio) - old_per_node_doc_ratio) * stable_nodes; - moved_ratio += extra_moved_ratio; - LOG(info, "estimated_moved_docs_ratio_replace(%u,%u,%u,%u)=%4.2f, (of which %4.2f extra)", redundancy, added_nodes, retired_nodes, num_nodes, moved_ratio, extra_moved_ratio); - return moved_ratio; -} - class BMParams : public BmClusterParams, public BmFeedParams { @@ -391,7 +325,7 @@ Benchmark::estimate_lost_docs() case Mode::TEMP_CRASH: { double new_redundancy = std::min(_params.get_redundancy(), _params.get_num_nodes() - _params.get_flip_nodes()); - auto lost_docs_ratio = estimate_lost_docs_base_ratio(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()) * new_redundancy; + auto lost_docs_ratio = EstimateMovedDocsRatio().estimate_lost_docs_base_ratio(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()) * new_redundancy; return _params.get_documents() * lost_docs_ratio; } default: @@ -404,14 +338,21 @@ Benchmark::estimate_moved_docs() { switch(_params.get_mode()) { case Mode::GROW: - return _params.get_documents() * estimate_moved_docs_ratio_grow(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_grow(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::SHRINK: - return _params.get_documents() * estimate_moved_docs_ratio_shrink(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_shrink(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::PERM_CRASH: case Mode::TEMP_CRASH: - return _params.get_documents() * estimate_moved_docs_ratio_crash(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_crash(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::REPLACE: - return _params.get_documents() * estimate_moved_docs_ratio_replace(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + if (_params.get_num_nodes() < 10) { + // Calculate better estimate for moved docs ratio with brute force + auto scanner = CalculateMovedDocsRatio::make_replace_calculator(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + scanner.scan(); + return _params.get_documents() * scanner.get_moved_docs_ratio(); + } else { + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_replace(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + } default: return 0.0; } diff --git a/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt new file mode 100644 index 00000000000..23f72e4f1fa --- /dev/null +++ b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(searchcore_bmcluster_estimate_moved_docs_ratio_test_app TEST + SOURCES + estimate_moved_docs_ratio_test.cpp + DEPENDS + searchcore_bmcluster + GTest::GTest +) + +vespa_add_test(NAME searchcore_bmcluster_estimate_moved_docs_ratio_test_app + COMMAND searchcore_bmcluster_estimate_moved_docs_ratio_test_app) diff --git a/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp new file mode 100644 index 00000000000..79af31e3247 --- /dev/null +++ b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp @@ -0,0 +1,134 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/gtest/gtest.h> +#include <vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h> +#include <vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h> +#include <iostream> + +#include <vespa/log/log.h> +LOG_SETUP("estimate_moved_docs_ratio_test"); + +using search::bmcluster::CalculateMovedDocsRatio; +using search::bmcluster::EstimateMovedDocsRatio; + +namespace { + +bool verbose; + +TEST(EstimateMovedDocsRatioTest, estimate_lost_docs_ratio) +{ + for (uint32_t nodes = 1; nodes < 2; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t lost_nodes = 0; lost_nodes <= nodes; ++lost_nodes) { + auto scanner = CalculateMovedDocsRatio::make_crash_calculator(redundancy, lost_nodes, nodes); + scanner.scan(); + double lost_docs_base_ratio = scanner.get_lost_docs_base_ratio(); + double estimated_lost_docs_base_ratio = EstimateMovedDocsRatio().estimate_lost_docs_base_ratio(redundancy, lost_nodes, nodes); + EXPECT_DOUBLE_EQ(lost_docs_base_ratio, estimated_lost_docs_base_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_grow) +{ + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t added_nodes = 0; added_nodes <= nodes; ++added_nodes) { + auto scanner = CalculateMovedDocsRatio::make_grow_calculator(redundancy, added_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_grow(redundancy, added_nodes, nodes); + EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_shrink) +{ + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t retired_nodes = 0; retired_nodes <= nodes; ++retired_nodes) { + auto scanner = CalculateMovedDocsRatio::make_shrink_calculator(redundancy, retired_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_shrink(redundancy, retired_nodes, nodes); + EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_crash) +{ + double epsilon = 1e-15; + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t crashed_nodes = 0; crashed_nodes <= nodes; ++crashed_nodes) { + auto scanner = CalculateMovedDocsRatio::make_crash_calculator(redundancy, crashed_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_crash(redundancy, crashed_nodes, nodes); + EXPECT_NEAR(moved_docs_ratio, estimated_moved_docs_ratio, epsilon); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_replace) +{ + uint32_t bad_cases = 0; + uint32_t really_bad_cases = 0; + if (verbose) { + std::cout << "Summary: HDR Red A Ret N Act Est ScaleMv ScaleEs States" << std::endl; + } + for (uint32_t nodes = 1; nodes < 6; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t retired_nodes = 0; retired_nodes <= nodes; ++retired_nodes) { + for (uint32_t added_nodes = 0; added_nodes <= nodes - retired_nodes; ++added_nodes) { + // std::cout << "Estimate moved docs ratio replace " << retired_nodes << " of " << nodes << " retired, added " << added_nodes << " nodes ,redundancy " << redundancy << std::endl; + auto scanner = CalculateMovedDocsRatio::make_replace_calculator(redundancy, added_nodes, retired_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio(verbose).estimate_moved_docs_ratio_replace(redundancy, added_nodes, retired_nodes, nodes); + double error_ratio = abs(moved_docs_ratio - estimated_moved_docs_ratio); + bool bad = error_ratio > 1e-8; + bool really_bad = error_ratio > 0.2 * estimated_moved_docs_ratio + 1e-8; + if (bad) { + ++bad_cases; + } + if (really_bad) { + ++really_bad_cases; + } + if (verbose) { + double scaled_moved = moved_docs_ratio * scanner.get_checked_states(); + double scaled_estimated_moved = estimated_moved_docs_ratio * scanner.get_checked_states(); + std::cout << "Summary: " << (bad ? "BAD" : "OK ") << std::setw(4) << redundancy << std::setw(4) << added_nodes << std::setw(4) << retired_nodes << std::setw(4) << nodes << " " << std::setw(12) << std::setprecision(5) << std::fixed << moved_docs_ratio << " " << std::setw(12) << std::setprecision(5) << std::fixed << estimated_moved_docs_ratio << " " << std::setw(12) << std::setprecision(5) << std::fixed << scaled_moved << " " << std::setw(12) << std::setprecision(5) << std::fixed << scaled_estimated_moved << std::setw(8) << scanner.get_checked_states(); + std::cout << " ["; + for (uint32_t node_idx = 0; node_idx < nodes; ++node_idx) { + std::cout << std::setw(8) << scanner.get_moved_docs_per_node()[node_idx]; + } + std::cout << " ]" << std::endl; + } + // TODO: Fix calculation so we get zero bad cases. + // EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } + } + EXPECT_LE(6, bad_cases); + EXPECT_LE(1, really_bad_cases); +} + +} // namespace + +int +main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + if (argc > 1 && std::string("--verbose") == argv[1]) { + verbose = true; + } + return RUN_ALL_TESTS(); +} diff --git a/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp b/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp index b7f217290d6..1475ac7f9d7 100644 --- a/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp +++ b/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp @@ -62,7 +62,13 @@ TEST_F(DiskMemUsageSamplerTest, resource_usage_is_sampled) std::this_thread::sleep_for(50ms); } LOG(info, "Polled %zu times (%zu ms) to get a sample", i, i * 50); +#ifdef __linux__ + // Anonymous resident memory used by current process is sampled. EXPECT_GT(filter().getMemoryStats().getAnonymousRss(), 0); +#else + // Anonymous resident memory used by current process is not sampled. + EXPECT_EQ(filter().getMemoryStats().getAnonymousRss(), 0); +#endif EXPECT_GT(filter().getDiskUsedSize(), 0); EXPECT_EQ(100, filter().get_transient_memory_usage()); EXPECT_EQ(100.0 / memory_size_bytes, filter().get_relative_transient_memory_usage()); diff --git a/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt b/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt index d2ab8b0c2a9..0de021a9514 100644 --- a/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt @@ -19,7 +19,9 @@ vespa_add_library(searchcore_bmcluster STATIC bm_storage_link.cpp bm_storage_message_addresses.cpp bucket_info_queue.cpp + calculate_moved_docs_ratio.cpp document_api_message_bus_bm_feed_handler.cpp + estimate_moved_docs_ratio.cpp pending_tracker.cpp pending_tracker_hash.cpp spi_bm_feed_handler.cpp diff --git a/searchcore/src/vespa/searchcore/bmcluster/bm_cluster_params.cpp b/searchcore/src/vespa/searchcore/bmcluster/bm_cluster_params.cpp index 2a214130392..f31f5fb6b88 100644 --- a/searchcore/src/vespa/searchcore/bmcluster/bm_cluster_params.cpp +++ b/searchcore/src/vespa/searchcore/bmcluster/bm_cluster_params.cpp @@ -6,7 +6,7 @@ namespace search::bmcluster { BmClusterParams::BmClusterParams() - : _bucket_db_stripe_bits(0), + : _bucket_db_stripe_bits(4), _distributor_stripes(0), _enable_distributor(false), _enable_service_layer(false), diff --git a/searchcore/src/vespa/searchcore/bmcluster/bm_node_stats_reporter.cpp b/searchcore/src/vespa/searchcore/bmcluster/bm_node_stats_reporter.cpp index 83956dfc274..ecd0593031e 100644 --- a/searchcore/src/vespa/searchcore/bmcluster/bm_node_stats_reporter.cpp +++ b/searchcore/src/vespa/searchcore/bmcluster/bm_node_stats_reporter.cpp @@ -95,16 +95,16 @@ BmNodeStatsReporter::report() for (auto &node : node_stats) { auto& document_db = node.get_document_db_stats(); if (document_db.has_value()) { - s << Width(8) << document_db.value().get_total_docs(); + s << Width(10) << document_db.value().get_total_docs(); } else { - s << Width(8) << "-"; + s << Width(10) << "-"; } totals += node; } if (totals.get_document_db_stats().has_value()) { - s << Width(8) << totals.get_document_db_stats().value().get_total_docs(); + s << Width(10) << totals.get_document_db_stats().value().get_total_docs(); } else { - s << Width(8) << "-"; + s << Width(10) << "-"; } auto& total_buckets = totals.get_buckets_stats(); if (total_buckets.has_value()) { diff --git a/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp new file mode 100644 index 00000000000..63f23476a95 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp @@ -0,0 +1,142 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "calculate_moved_docs_ratio.h" +#include <cassert> + +namespace search::bmcluster { + +namespace { + +uint32_t make_bit_range(uint32_t low, uint32_t high) +{ + return (1u << high) - (1u << low); +} + +} + +struct CalculateMovedDocsRatio::Placements +{ + uint32_t _mask; + uint32_t _count; + + Placements() + : _mask(0u), + _count(0u) + { + } + + Placements(uint32_t mask, uint32_t count) noexcept + : _mask(mask), + _count(count) + { + } + + Placements add(uint32_t idx) const noexcept { + return Placements(_mask | (1u << idx), _count + 1); + } + + Placements add(uint32_t idx, uint32_t mask, uint32_t redundancy) const noexcept { + return (((_count < redundancy) && ((1u << idx) & mask) != 0)) ? Placements(_mask | (1u << idx), _count + 1) : *this; + } +}; + + +CalculateMovedDocsRatio::CalculateMovedDocsRatio(uint32_t nodes, uint32_t redundancy, uint32_t old_placement_mask, uint32_t new_placement_mask, uint32_t new_up_mask) + : _num_states(nodes + 1), + _nodes(nodes), + _old_placement_mask(old_placement_mask), + _new_placement_mask(new_placement_mask), + _new_up_mask(new_up_mask), + _moved_docs(0u), + _moved_docs_per_node(nodes), + _checked_states(0u), + _lost_docs_base(0u), + _redundancy(redundancy), + _old_redundancy(std::min(redundancy, (uint32_t)__builtin_popcount(old_placement_mask))), + _new_redundancy(std::min(redundancy, (uint32_t)__builtin_popcount(new_placement_mask))) +{ + assert((new_placement_mask & ~new_up_mask) == 0u); + uint32_t states = 1; + for (uint32_t level = nodes; level > 0; --level) { + states *= std::max(1u, nodes - level); + _num_states[level] = states; + } + _num_states[0] = states * nodes; +} + +CalculateMovedDocsRatio::~CalculateMovedDocsRatio() = default; + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_grow_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(added_nodes, nodes); + uint32_t new_placement_mask = make_bit_range(0, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_placement_mask); +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_shrink_calculator(uint32_t redundancy, uint32_t retired_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(0, nodes); + uint32_t new_placement_mask = make_bit_range(retired_nodes, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, old_placement_mask); +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_crash_calculator(uint32_t redundancy, uint32_t crashed_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(0, nodes); + uint32_t new_placement_mask = make_bit_range(crashed_nodes, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_placement_mask); + +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_replace_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(added_nodes, nodes); + uint32_t new_placement_mask = make_bit_range(added_nodes + retired_nodes, nodes) | make_bit_range(0, added_nodes); + uint32_t new_up_mask = make_bit_range(0, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_up_mask); +} + +void +CalculateMovedDocsRatio::scan(Placements selected, Placements old_placement, Placements new_placement) +{ + if (old_placement._count >= _old_redundancy) { + if ((old_placement._mask & _new_up_mask) == 0) { + _lost_docs_base += _num_states[selected._count]; + _checked_states += _num_states[selected._count]; + return; + } + if (new_placement._count >= _new_redundancy) { + _checked_states += _num_states[selected._count]; + uint32_t only_new_mask = new_placement._mask & ~old_placement._mask; + if (only_new_mask != 0) { + _moved_docs += _num_states[selected._count] * (uint32_t)__builtin_popcount(only_new_mask); + for (uint32_t node_idx = 0; node_idx < _nodes; ++node_idx) { + if ((only_new_mask & (1u << node_idx)) != 0) { + _moved_docs_per_node[node_idx] += _num_states[selected._count]; + } + } + } + return; + } + } + assert(selected._count < _nodes); + for (uint32_t node_idx = 0; node_idx < _nodes; ++node_idx) { + if ((selected._mask & (1u << node_idx)) != 0) { + continue; + } + scan(selected.add(node_idx), old_placement.add(node_idx, _old_placement_mask, _old_redundancy), new_placement.add(node_idx, _new_placement_mask, _new_redundancy)); + } +} + +void +CalculateMovedDocsRatio::scan() +{ + scan(Placements(), Placements(), Placements()); + assert(_checked_states == _num_states[0]); +} + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h new file mode 100644 index 00000000000..cd161177246 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h @@ -0,0 +1,48 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <cstdint> +#include <vector> + +namespace search::bmcluster { + +/* + * Class for calculating moved docs ratio during + * document redistribution. + */ +class CalculateMovedDocsRatio +{ + class Placements; + std::vector<uint32_t> _num_states; + uint32_t _nodes; + uint32_t _old_placement_mask; + uint32_t _new_placement_mask; + uint32_t _new_up_mask; + uint32_t _moved_docs; + std::vector<uint32_t> _moved_docs_per_node; + uint32_t _checked_states; + uint32_t _lost_docs_base; + uint32_t _redundancy; + uint32_t _old_redundancy; + uint32_t _new_redundancy; + + void scan(Placements selected, Placements old_placement, Placements new_placement); +public: + CalculateMovedDocsRatio(uint32_t nodes, uint32_t redundancy, uint32_t old_placement_mask, uint32_t new_placement_mask, uint32_t new_up_mask); + ~CalculateMovedDocsRatio(); + static CalculateMovedDocsRatio make_grow_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_shrink_calculator(uint32_t redundancy, uint32_t retired_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_crash_calculator(uint32_t redundancy, uint32_t crashed_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_replace_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t nodes); + void scan(); + uint32_t get_lost_docs_base() const noexcept { return _lost_docs_base; } + uint32_t get_checked_states() const noexcept { return _checked_states; } + uint32_t get_new_redundancy() const noexcept { return _new_redundancy; } + uint32_t get_moved_docs() const noexcept { return _moved_docs; } + const std::vector<uint32_t>& get_moved_docs_per_node() const noexcept { return _moved_docs_per_node; } + double get_lost_docs_base_ratio() const noexcept { return ((double) _lost_docs_base) / _checked_states; } + double get_moved_docs_ratio() const noexcept { return ((double) _moved_docs) / _checked_states; } +}; + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp new file mode 100644 index 00000000000..cffa6fc79e9 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp @@ -0,0 +1,114 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "estimate_moved_docs_ratio.h" + +#include <vespa/log/log.h> +LOG_SETUP(".bmcluster.estimate_moved_docs_ratio"); + +namespace search::bmcluster { + +EstimateMovedDocsRatio::EstimateMovedDocsRatio() + : EstimateMovedDocsRatio(false) +{ +} + +EstimateMovedDocsRatio::EstimateMovedDocsRatio(bool verbose) + : _verbose(verbose) +{ +} + +double +EstimateMovedDocsRatio::estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes) +{ + if (redundancy > lost_nodes) { + return 0.0; + } + double loss_ratio = 1.0; + for (uint32_t i = 0; i < redundancy; ++i) { + loss_ratio *= ((double) (lost_nodes - i)) / (num_nodes - i); + } + if (_verbose) { + LOG(info, "estimated lost docs base ratio: %4.2f", loss_ratio); + } + return loss_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes) +{ + if (added_nodes == num_nodes) { + return 0.0; + } + double new_redundancy = redundancy; + double new_per_node_doc_ratio = new_redundancy / num_nodes; + double moved_ratio = new_per_node_doc_ratio * added_nodes; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_grow(%u,%u,%u)=%4.2f", redundancy, added_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes) +{ + if (retired_nodes == num_nodes) { + return 0.0; + } + double old_redundancy = redundancy; + double old_per_node_doc_ratio = old_redundancy / num_nodes; + uint32_t new_nodes = num_nodes - retired_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_shrink(%u,%u,%u)=%4.2f", redundancy, retired_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes) +{ + if (crashed_nodes == num_nodes) { + return 0.0; + } + double old_redundancy = redundancy; + double old_per_node_doc_ratio = old_redundancy / num_nodes; + uint32_t new_nodes = num_nodes - crashed_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double lost_docs_ratio = estimate_lost_docs_base_ratio(redundancy, crashed_nodes, num_nodes) * new_redundancy; + double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes - lost_docs_ratio; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_crash(%u,%u,%u)=%4.2f", redundancy, crashed_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes) +{ + if (added_nodes == num_nodes || retired_nodes == num_nodes) { + return 0.0; + } + uint32_t old_nodes = num_nodes - added_nodes; + double old_redundancy = std::min(redundancy, old_nodes); + [[maybe_unused]] double old_per_node_doc_ratio = old_redundancy / old_nodes; + uint32_t new_nodes = num_nodes - retired_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double moved_ratio = new_per_node_doc_ratio * added_nodes; + uint32_t stable_nodes = num_nodes - added_nodes - retired_nodes; + // Account for extra documents moved from retired nodes to stable nodes + // TODO: Fix calculation + double baseline_per_node_doc_ratio = ((double) redundancy) / num_nodes; + double extra_per_stable_node_doc_ratio = std::min(baseline_per_node_doc_ratio * retired_nodes / new_nodes, 1.0 - old_per_node_doc_ratio); + double extra_moved_ratio = extra_per_stable_node_doc_ratio * stable_nodes; + moved_ratio += extra_moved_ratio; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_replace(%u,%u,%u,%u)=%4.2f, (of which %4.2f extra)", redundancy, added_nodes, retired_nodes, num_nodes, moved_ratio, extra_moved_ratio); + } + return moved_ratio; +} + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h new file mode 100644 index 00000000000..fb14e80098d --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h @@ -0,0 +1,25 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <cstdint> + +namespace search::bmcluster { + +/* + * Class for estimating moved docs ratio during + * document redistribution. + */ +class EstimateMovedDocsRatio { + bool _verbose; +public: + EstimateMovedDocsRatio(); + explicit EstimateMovedDocsRatio(bool verbose); + double estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes); +}; + +} diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp index 0a13b196c43..90369009f0e 100644 --- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp +++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp @@ -3,8 +3,10 @@ #include "blueprintresolver.h" #include "blueprintfactory.h" #include "featurenameparser.h" +#include <vespa/vespalib/util/lambdatask.h> #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/util/size_literals.h> +#include <vespa/vespalib/util/threadstackexecutor.h> #include <stack> #include <cassert> #include <set> @@ -14,6 +16,8 @@ LOG_SETUP(".fef.blueprintresolver"); using vespalib::make_string_short::fmt; +using vespalib::ThreadStackExecutor; +using vespalib::makeLambdaTask; namespace search::fef { @@ -271,8 +275,7 @@ BlueprintResolver::compile() { assert(_executorSpecs.empty()); // only one compilation allowed Compiler compiler(_factory, _indexEnv, _executorSpecs, _featureMap); - std::thread compile_thread([&]() - { + auto compile_task = makeLambdaTask([&]() { compiler.probe_stack(); for (const auto &seed: _seeds) { auto ref = compiler.resolve_feature(seed, Blueprint::AcceptInput::ANY); @@ -282,7 +285,10 @@ BlueprintResolver::compile() _seedMap.emplace(FeatureNameParser(seed).featureName(), ref); } }); - compile_thread.join(); + ThreadStackExecutor executor(1, 8_Mi); + executor.execute(std::move(compile_task)); + executor.sync(); + executor.shutdown(); size_t stack_usage = compiler.stack_usage(); if (stack_usage > (128_Ki)) { LOG(warning, "high stack usage: %zu bytes", stack_usage); diff --git a/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp b/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp index f503453f934..0edcde172aa 100644 --- a/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp +++ b/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp @@ -148,7 +148,7 @@ TEST_F(RpcMappingMonitorTest, hurry_means_faster) { EXPECT_TRUE(debugger.step_until([&]() { return ((hist.map[foo_a].samples() > 0)); })); auto t2 = debugger.time(); - fprintf(stderr, "hurry: ~%zu ms, normal: ~%zu ms\n", count_ms(t1-t0), count_ms(t2-t0)); + fprintf(stderr, "hurry: ~%" PRIu64 " ms, normal: ~%" PRIu64 " ms\n", count_ms(t1-t0), count_ms(t2-t0)); EXPECT_GT((t2 - t0), 10 * (t1 - t0)); EXPECT_EQ(hist.map[foo_a].state(), State::UP); EXPECT_EQ(hist.map[baz_b].state(), State::UP); @@ -218,7 +218,7 @@ TEST_F(RpcMappingMonitorTest, detect_ping_interval) { a.set_last_conn(nullptr); EXPECT_TRUE(debugger.step_until([&]() { return (a.last_conn); })); auto t2 = debugger.time(); - fprintf(stderr, "ping interval: ~%zu ms\n", count_ms(t2-t1)); + fprintf(stderr, "ping interval: ~%" PRIu64 " ms\n", count_ms(t2-t1)); } GTEST_MAIN_RUN_ALL_TESTS() diff --git a/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java b/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java index dc50db1e598..a28d8be8f57 100644 --- a/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java +++ b/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; * @author ollivir */ public class LocalFileDb implements FileAcquirer, FileRegistry { + private final Map<FileReference, File> fileReferenceToFile = new HashMap<>(); private final Path appPath; @@ -37,7 +38,7 @@ public class LocalFileDb implements FileAcquirer, FileRegistry { synchronized (this) { File file = fileReferenceToFile.get(reference); if (file == null) { - return new File(reference.value()); // Downloaded file reference: Will (hopefully) be resolved client side + return new File(reference.value()); // Downloaded file reference: Will (hopefully) be resolved client side } return file; } @@ -48,6 +49,7 @@ public class LocalFileDb implements FileAcquirer, FileRegistry { } /* FileRegistry overrides */ + @Override public FileReference addFile(String relativePath) { File file = appPath.resolve(relativePath).toFile(); if (!file.exists()) { diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh index b30a7ad4cd0..e6cbb435b6f 100755 --- a/standalone-container/src/main/sh/standalone-container.sh +++ b/standalone-container/src/main/sh/standalone-container.sh @@ -180,7 +180,6 @@ StartCommand() { -Djdisc.config.file="$cfpfile" \ -Djdisc.export.packages= \ -Djdisc.cache.path="$bundlecachedir" \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path="$VESPA_HOME/lib/jars" \ -Djdisc.logger.enabled=true \ -Djdisc.logger.level=ALL \ diff --git a/storage/src/tests/distributor/distributor_stripe_test.cpp b/storage/src/tests/distributor/distributor_stripe_test.cpp index 418b224eb25..7fdc2578545 100644 --- a/storage/src/tests/distributor/distributor_stripe_test.cpp +++ b/storage/src/tests/distributor/distributor_stripe_test.cpp @@ -230,6 +230,25 @@ TEST_F(DistributorStripeTest, operations_generated_and_started_without_duplicate ASSERT_EQ(6, _sender.commands().size()); } +TEST_F(DistributorStripeTest, maintenance_scheduling_inhibited_if_cluster_state_is_pending) +{ + setup_stripe(Redundancy(2), NodeCount(4), "storage:3 distributor:1"); + simulate_set_pending_cluster_state("storage:4 distributor:1"); + + _sender.commands().clear(); // Remove pending bucket info requests + + tickDistributorNTimes(1); + EXPECT_FALSE(stripe_is_in_recovery_mode()); + + for (uint32_t i = 0; i < 6; ++i) { + addNodesToBucketDB(document::BucketId(16, i), "0=2"); // Needs activation, merging + } + tickDistributorNTimes(10); + + // No ops should have been actually generated + ASSERT_EQ(0, _sender.commands().size()); +} + TEST_F(DistributorStripeTest, recovery_mode_on_cluster_state_change) { setup_stripe(Redundancy(1), NodeCount(2), diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClient.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClient.java index 0089499701f..6fea2d3faa4 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClient.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClient.java @@ -22,18 +22,19 @@ public interface FeedClient extends Closeable { /** * Send a document put with the given parameters, returning a future with the result of the operation. * Exceptional completion will use be an instance of {@link FeedException} or one of its sub-classes. - * */ + */ CompletableFuture<Result> put(DocumentId documentId, String documentJson, OperationParameters params); /** * Send a document update with the given parameters, returning a future with the result of the operation. * Exceptional completion will use be an instance of {@link FeedException} or one of its sub-classes. - * */ + */ CompletableFuture<Result> update(DocumentId documentId, String updateJson, OperationParameters params); - /** Send a document remove with the given parameters, returning a future with the result of the operation. - * Exceptional completion will use be an instance of {@link FeedException} or one of its sub-classes. - * */ + /** + * Send a document remove with the given parameters, returning a future with the result of the operation. + * Exceptional completion will use be an instance of {@link FeedException} or one of its sub-classes. + */ CompletableFuture<Result> remove(DocumentId documentId, OperationParameters params); /** Returns a snapshot of the stats for this feed client, such as requests made, and responses by status. */ diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java index 58112f86090..345177dcf36 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java @@ -163,9 +163,9 @@ class HttpRequestStrategy implements RequestStrategy { return true; } - breaker.failure(response); logResponse(FINE, response, request, attempt); if (response.code() == 500 || response.code() == 502 || response.code() == 504) { // Hopefully temporary errors. + breaker.failure(response); return retry(request, attempt); } diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java index 50bdde28180..89597a2352a 100644 --- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java +++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java @@ -108,7 +108,10 @@ public abstract class AbstractVespaMojo extends AbstractMojo { private Optional<Path> apiKeyPath(String tenant) { if (!isNullOrBlank(apiKeyFile)) return Optional.of(Paths.get(apiKeyFile)); - Path cliApiKeyFile = Paths.get(System.getProperty("user.home"), ".vespa", tenant + ".api-key.pem"); + Path cliApiKeyFile = Optional.ofNullable(System.getenv("VESPA_CLI_HOME")) + .map(Paths::get) + .orElseGet(() -> Paths.get(System.getProperty("user.home"), ".vespa")) + .resolve(tenant + ".api-key.pem"); if (Files.exists(cliApiKeyFile)) return Optional.of(cliApiKeyFile); return Optional.empty(); diff --git a/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java b/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java index 857b7cc6acd..98370a8735a 100644 --- a/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java +++ b/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java @@ -9,7 +9,7 @@ import java.util.Map; import java.util.Set; /** - * A hashmap wrapper which defers cloning of the enclosed map until it is written. + * A hashmap wrapper which defers cloning of the enclosed map until it is written to. * Use this to make clones cheap in maps which are often not further modified. * <p> * As with regular maps, this can only be used safely if the content of the map is immutable. diff --git a/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java b/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java index c8a7f2253bb..ac1a33eea0d 100644 --- a/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java +++ b/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.slime; - final class ArrayValue extends Value { private int capacity = 16; @@ -10,16 +9,16 @@ final class ArrayValue extends Value { private final SymbolTable names; public ArrayValue(SymbolTable names) { this.names = names; } - public final Type type() { return Type.ARRAY; } - public final int children() { return used; } - public final int entries() { return used; } - public final Value entry(int index) { + public Type type() { return Type.ARRAY; } + public int children() { return used; } + public int entries() { return used; } + public Value entry(int index) { return (index < used) ? values[index] : NixValue.invalid(); } - public final void accept(Visitor v) { v.visitArray(this); } + public void accept(Visitor v) { v.visitArray(this); } - public final void traverse(ArrayTraverser at) { + public void traverse(ArrayTraverser at) { for (int i = 0; i < used; i++) { at.entry(i, values[i]); } @@ -32,7 +31,7 @@ final class ArrayValue extends Value { System.arraycopy(v, 0, values, 0, used); } - protected final Value addLeaf(Value value) { + protected Value addLeaf(Value value) { if (used == capacity) { grow(); } @@ -40,6 +39,7 @@ final class ArrayValue extends Value { return value; } - public final Value addArray() { return addLeaf(new ArrayValue(names)); } - public final Value addObject() { return addLeaf(new ObjectValue(names)); } + public Value addArray() { return addLeaf(new ArrayValue(names)); } + public Value addObject() { return addLeaf(new ObjectValue(names)); } + } |