From 947b0299ac998a9a833b69a0b0a2a0de8098b67c Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Wed, 12 Jul 2023 15:42:56 +0200 Subject: Wait by default in deploy commands --- client/go/internal/cli/cmd/config.go | 18 ------------ client/go/internal/cli/cmd/config_test.go | 8 +----- client/go/internal/cli/cmd/curl.go | 5 +++- client/go/internal/cli/cmd/deploy.go | 44 +++++++++++++---------------- client/go/internal/cli/cmd/deploy_test.go | 33 +++++++++++++--------- client/go/internal/cli/cmd/document.go | 46 +++++++++++++++++-------------- client/go/internal/cli/cmd/feed.go | 12 ++++---- client/go/internal/cli/cmd/prod.go | 7 ++--- client/go/internal/cli/cmd/query.go | 8 ++++-- client/go/internal/cli/cmd/root.go | 29 ++++++------------- client/go/internal/cli/cmd/status.go | 40 +++++++++++++++++---------- client/go/internal/cli/cmd/test.go | 18 ++++++------ client/go/internal/cli/cmd/visit.go | 4 ++- client/go/internal/cli/cmd/visit_test.go | 2 +- 14 files changed, 132 insertions(+), 142 deletions(-) (limited to 'client/go/internal') diff --git a/client/go/internal/cli/cmd/config.go b/client/go/internal/cli/cmd/config.go index 409254c4349..eb79a2004c4 100644 --- a/client/go/internal/cli/cmd/config.go +++ b/client/go/internal/cli/cmd/config.go @@ -337,18 +337,6 @@ func (c *Config) targetOrURL() (string, error) { return targetType, nil } -func (c *Config) timeout() (time.Duration, error) { - wait, ok := c.get(waitFlag) - if !ok { - return 0, nil - } - secs, err := strconv.Atoi(wait) - if err != nil { - return 0, err - } - return time.Duration(secs) * time.Second, nil -} - func (c *Config) isQuiet() bool { quiet, _ := c.get(quietFlag) return quiet == "true" @@ -629,12 +617,6 @@ func (c *Config) set(option, value string) error { case clusterFlag: c.config.Set(clusterFlag, value) return nil - case waitFlag: - if n, err := strconv.Atoi(value); err != nil || n < 0 { - return fmt.Errorf("%s option must be an integer >= 0, got %q", option, value) - } - c.config.Set(option, value) - return nil case colorFlag: switch value { case "auto", "never", "always": diff --git a/client/go/internal/cli/cmd/config_test.go b/client/go/internal/cli/cmd/config_test.go index 3a81b93ea0d..14a3cf7cbbc 100644 --- a/client/go/internal/cli/cmd/config_test.go +++ b/client/go/internal/cli/cmd/config_test.go @@ -54,12 +54,6 @@ func TestConfig(t *testing.T) { assertConfigCommand(t, configHome, "", "config", "set", "instance", "i2") assertConfigCommand(t, configHome, "instance = i2\n", "config", "get", "instance") - // wait - assertConfigCommandErr(t, configHome, "Error: wait option must be an integer >= 0, got \"foo\"\n", "config", "set", "wait", "foo") - assertConfigCommand(t, configHome, "", "config", "set", "wait", "60") - assertConfigCommand(t, configHome, "wait = 60\n", "config", "get", "wait") - assertConfigCommand(t, configHome, "wait = 30\n", "config", "get", "--wait", "30", "wait") // flag overrides global config - // color assertConfigCommandErr(t, configHome, "Error: invalid option or value: color = foo\n", "config", "set", "color", "foo") assertConfigCommand(t, configHome, "", "config", "set", "color", "never") @@ -74,6 +68,7 @@ func TestConfig(t *testing.T) { // zone assertConfigCommand(t, configHome, "", "config", "set", "zone", "dev.us-east-1") assertConfigCommand(t, configHome, "zone = dev.us-east-1\n", "config", "get", "zone") + assertConfigCommand(t, configHome, "zone = prod.us-north-1\n", "config", "get", "--zone", "prod.us-north-1", "zone") // flag overrides global config // Write empty value to YAML config, which should be ignored. This is for compatibility with older config formats configFile := filepath.Join(configHome, "config.yaml") @@ -118,7 +113,6 @@ color = auto instance = foo quiet = false target = cloud -wait = 0 zone = `, "config", "get") diff --git a/client/go/internal/cli/cmd/curl.go b/client/go/internal/cli/cmd/curl.go index 3d5aaff24dc..3009cab2b5e 100644 --- a/client/go/internal/cli/cmd/curl.go +++ b/client/go/internal/cli/cmd/curl.go @@ -6,6 +6,7 @@ import ( "log" "os" "strings" + "time" "github.com/spf13/cobra" "github.com/vespa-engine/vespa/client/go/internal/curl" @@ -14,6 +15,7 @@ import ( func newCurlCmd(cli *CLI) *cobra.Command { var ( + waitSecs int dryRun bool curlService string ) @@ -37,7 +39,7 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain if err != nil { return err } - service, err := target.Service(curlService, 0, 0, cli.config.cluster()) + service, err := target.Service(curlService, time.Duration(waitSecs)*time.Second, 0, cli.config.cluster()) if err != nil { return err } @@ -72,6 +74,7 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain } cmd.Flags().BoolVarP(&dryRun, "dry-run", "n", false, "Print the curl command that would be executed") cmd.Flags().StringVarP(&curlService, "service", "s", "query", "Which service to query. Must be \"deploy\", \"document\" or \"query\"") + cli.bindWaitFlag(cmd, 60, &waitSecs) return cmd } diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index 35b9ee0f300..ef32d7f01b7 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -9,6 +9,7 @@ import ( "io" "log" "strconv" + "time" "github.com/fatih/color" "github.com/spf13/cobra" @@ -18,6 +19,7 @@ import ( func newDeployCmd(cli *CLI) *cobra.Command { var ( + waitSecs int logLevelArg string versionArg string copyCert bool @@ -57,10 +59,8 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, if err != nil { return err } - opts, err := cli.createDeploymentOptions(pkg, target) - if err != nil { - return err - } + timeout := time.Duration(waitSecs) * time.Second + opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target, Timeout: timeout} if versionArg != "" { version, err := version.Parse(versionArg) if err != nil { @@ -95,12 +95,13 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, opts.Target.Deployment().Application.Instance, opts.Target.Deployment().Zone.Environment, opts.Target.Deployment().Zone.Region, result.ID))) } - return waitForQueryService(cli, target, result.ID) + return waitForQueryService(cli, target, result.ID, timeout) }, } cmd.Flags().StringVarP(&logLevelArg, "log-level", "l", "error", `Log level for Vespa logs. Must be "error", "warning", "info" or "debug"`) cmd.Flags().StringVarP(&versionArg, "version", "V", "", `Override the Vespa runtime version to use in Vespa Cloud`) cmd.Flags().BoolVarP(©Cert, "add-cert", "A", false, `Copy certificate of the configured application to the current application package`) + cli.bindWaitFlag(cmd, 60, &waitSecs) return cmd } @@ -120,10 +121,7 @@ func newPrepareCmd(cli *CLI) *cobra.Command { if err != nil { return err } - opts, err := cli.createDeploymentOptions(pkg, target) - if err != nil { - return err - } + opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} var result vespa.PrepareResult err = cli.spinner(cli.Stderr, "Uploading application package ...", func() error { result, err = vespa.Prepare(opts) @@ -143,7 +141,8 @@ func newPrepareCmd(cli *CLI) *cobra.Command { } func newActivateCmd(cli *CLI) *cobra.Command { - return &cobra.Command{ + var waitSecs int + cmd := &cobra.Command{ Use: "activate", Short: "Activate (deploy) a previously prepared application package", Args: cobra.MaximumNArgs(1), @@ -162,31 +161,26 @@ func newActivateCmd(cli *CLI) *cobra.Command { if err != nil { return err } - opts, err := cli.createDeploymentOptions(pkg, target) - if err != nil { - return err - } + timeout := time.Duration(waitSecs) * time.Second + opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target, Timeout: timeout} err = vespa.Activate(sessionID, opts) if err != nil { return err } cli.printSuccess("Activated ", color.CyanString(pkg.Path), " with session ", sessionID) - return waitForQueryService(cli, target, sessionID) + return waitForQueryService(cli, target, sessionID, timeout) }, } + cli.bindWaitFlag(cmd, 60, &waitSecs) + return cmd } -func waitForQueryService(cli *CLI, target vespa.Target, sessionOrRunID int64) error { - timeout, err := cli.config.timeout() - if err != nil { - return err - } - if timeout > 0 { - log.Println() - _, err := cli.service(target, vespa.QueryService, sessionOrRunID, cli.config.cluster()) - return err +func waitForQueryService(cli *CLI, target vespa.Target, sessionOrRunID int64, timeout time.Duration) error { + if timeout == 0 { + return nil } - return nil + _, err := cli.service(target, vespa.QueryService, sessionOrRunID, cli.config.cluster(), timeout) + return err } func printPrepareLog(stderr io.Writer, result vespa.PrepareResult) { diff --git a/client/go/internal/cli/cmd/deploy_test.go b/client/go/internal/cli/cmd/deploy_test.go index 9e82723e816..16aa3fd0ed8 100644 --- a/client/go/internal/cli/cmd/deploy_test.go +++ b/client/go/internal/cli/cmd/deploy_test.go @@ -40,7 +40,7 @@ Hint: Pass --add-cert to use the certificate of the current application ` assert.Equal(t, certError, stderr.String()) - require.Nil(t, cli.Run("deploy", "--add-cert", pkgDir)) + require.Nil(t, cli.Run("deploy", "--add-cert", "--wait=0", pkgDir)) assert.Contains(t, stdout.String(), "Success: Triggered deployment") // Answer interactive certificate copy prompt @@ -53,11 +53,11 @@ Hint: Pass --add-cert to use the certificate of the current application var buf bytes.Buffer buf.WriteString("wat\nthe\nfck\nn\n") cli.Stdin = &buf - require.NotNil(t, cli.Run("deploy", "--add-cert=false", pkgDir2)) + require.NotNil(t, cli.Run("deploy", "--add-cert=false", "--wait=0", pkgDir2)) warning := "Warning: Application package does not contain security/clients.pem, which is required for deployments to Vespa Cloud\n" assert.Equal(t, warning+strings.Repeat("Error: please answer 'y' or 'n'\n", 3)+certError, stderr.String()) buf.WriteString("y\n") - require.Nil(t, cli.Run("deploy", "--add-cert=false", pkgDir2)) + require.Nil(t, cli.Run("deploy", "--add-cert=false", "--wait=0", pkgDir2)) assert.Contains(t, stdout.String(), "Success: Triggered deployment") } @@ -68,17 +68,17 @@ func TestPrepareZip(t *testing.T) { func TestActivateZip(t *testing.T) { assertActivate("testdata/applications/withTarget/target/application.zip", - []string{"activate", "testdata/applications/withTarget/target/application.zip"}, t) + []string{"activate", "--wait=0", "testdata/applications/withTarget/target/application.zip"}, t) } func TestDeployZip(t *testing.T) { assertDeploy("testdata/applications/withTarget/target/application.zip", - []string{"deploy", "testdata/applications/withTarget/target/application.zip"}, t) + []string{"deploy", "--wait=0", "testdata/applications/withTarget/target/application.zip"}, t) } func TestDeployZipWithURLTargetArgument(t *testing.T) { applicationPackage := "testdata/applications/withTarget/target/application.zip" - arguments := []string{"deploy", "testdata/applications/withTarget/target/application.zip", "-t", "http://target:19071"} + arguments := []string{"deploy", "--wait=0", "testdata/applications/withTarget/target/application.zip", "-t", "http://target:19071"} client := &mock.HTTPClient{} cli, stdout, _ := newTestCLI(t) @@ -92,27 +92,27 @@ func TestDeployZipWithURLTargetArgument(t *testing.T) { func TestDeployZipWithLocalTargetArgument(t *testing.T) { assertDeploy("testdata/applications/withTarget/target/application.zip", - []string{"deploy", "testdata/applications/withTarget/target/application.zip", "-t", "local"}, t) + []string{"deploy", "--wait=0", "testdata/applications/withTarget/target/application.zip", "-t", "local"}, t) } func TestDeploySourceDirectory(t *testing.T) { assertDeploy("testdata/applications/withSource/src/main/application", - []string{"deploy", "testdata/applications/withSource/src/main/application"}, t) + []string{"deploy", "--wait=0", "testdata/applications/withSource/src/main/application"}, t) } func TestDeployApplicationDirectoryWithSource(t *testing.T) { assertDeploy("testdata/applications/withSource/src/main/application", - []string{"deploy", "testdata/applications/withSource"}, t) + []string{"deploy", "--wait=0", "testdata/applications/withSource"}, t) } func TestDeployApplicationDirectoryWithPomAndTarget(t *testing.T) { assertDeploy("testdata/applications/withTarget/target/application.zip", - []string{"deploy", "testdata/applications/withTarget"}, t) + []string{"deploy", "--wait=0", "testdata/applications/withTarget"}, t) } func TestDeployApplicationDirectoryWithPomAndEmptyTarget(t *testing.T) { cli, _, stderr := newTestCLI(t) - assert.NotNil(t, cli.Run("deploy", "testdata/applications/withEmptyTarget")) + assert.NotNil(t, cli.Run("deploy", "--wait=0", "testdata/applications/withEmptyTarget")) assert.Equal(t, "Error: found pom.xml, but target/application.zip does not exist: run 'mvn package' first\n", stderr.String()) @@ -228,7 +228,14 @@ func assertApplicationPackageError(t *testing.T, cmd string, status int, expecte client.NextResponseString(status, returnBody) cli, _, stderr := newTestCLI(t) cli.httpClient = client - assert.NotNil(t, cli.Run(cmd, "testdata/applications/withTarget/target/application.zip")) + args := []string{} + args = append(args, cmd) + switch cmd { + case "activate", "deploy": + args = append(args, "--wait=0") + } + args = append(args, "testdata/applications/withTarget/target/application.zip") + assert.NotNil(t, cli.Run(args...)) assert.Equal(t, "Error: invalid application package (Status "+strconv.Itoa(status)+")\n"+expectedMessage+"\n", stderr.String()) @@ -240,7 +247,7 @@ func assertDeployServerError(t *testing.T, status int, errorMessage string) { client.NextResponseString(status, errorMessage) cli, _, stderr := newTestCLI(t) cli.httpClient = client - assert.NotNil(t, cli.Run("deploy", "testdata/applications/withTarget/target/application.zip")) + assert.NotNil(t, cli.Run("deploy", "--wait=0", "testdata/applications/withTarget/target/application.zip")) assert.Equal(t, "Error: error from deploy api at 127.0.0.1:19071 (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", stderr.String()) diff --git a/client/go/internal/cli/cmd/document.go b/client/go/internal/cli/cmd/document.go index 6a07121a13b..c31f8c34d14 100644 --- a/client/go/internal/cli/cmd/document.go +++ b/client/go/internal/cli/cmd/document.go @@ -23,9 +23,10 @@ import ( "github.com/vespa-engine/vespa/client/go/internal/vespa/document" ) -func addDocumentFlags(cmd *cobra.Command, printCurl *bool, timeoutSecs *int) { +func addDocumentFlags(cli *CLI, cmd *cobra.Command, printCurl *bool, timeoutSecs, waitSecs *int) { cmd.PersistentFlags().BoolVarP(printCurl, "verbose", "v", false, "Print the equivalent curl command for the document operation") cmd.PersistentFlags().IntVarP(timeoutSecs, "timeout", "T", 60, "Timeout for the document request in seconds") + cli.bindWaitFlag(cmd, 0, waitSecs) } type serviceWithCurl struct { @@ -57,8 +58,8 @@ func (s *serviceWithCurl) Do(request *http.Request, timeout time.Duration) (*htt return s.service.Do(request, timeout) } -func documentClient(cli *CLI, timeoutSecs int, printCurl bool) (*document.Client, *serviceWithCurl, error) { - docService, err := documentService(cli) +func documentClient(cli *CLI, timeoutSecs, waitSecs int, printCurl bool) (*document.Client, *serviceWithCurl, error) { + docService, err := documentService(cli, waitSecs) if err != nil { return nil, nil, err } @@ -78,8 +79,8 @@ func documentClient(cli *CLI, timeoutSecs int, printCurl bool) (*document.Client return client, service, nil } -func sendOperation(op document.Operation, args []string, timeoutSecs int, printCurl bool, cli *CLI) error { - client, service, err := documentClient(cli, timeoutSecs, printCurl) +func sendOperation(op document.Operation, args []string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) if err != nil { return err } @@ -122,8 +123,8 @@ func sendOperation(op document.Operation, args []string, timeoutSecs int, printC return printResult(cli, operationResult(false, doc, service.service, result), false) } -func readDocument(id string, timeoutSecs int, printCurl bool, cli *CLI) error { - client, service, err := documentClient(cli, timeoutSecs, printCurl) +func readDocument(id string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) if err != nil { return err } @@ -157,6 +158,7 @@ func newDocumentCmd(cli *CLI) *cobra.Command { var ( printCurl bool timeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "document json-file", @@ -176,10 +178,10 @@ should be used instead of this.`, SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(-1, args, timeoutSecs, printCurl, cli) + return sendOperation(-1, args, timeoutSecs, waitSecs, printCurl, cli) }, } - addDocumentFlags(cmd, &printCurl, &timeoutSecs) + addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } @@ -187,6 +189,7 @@ func newDocumentPutCmd(cli *CLI) *cobra.Command { var ( printCurl bool timeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "put [id] json-file", @@ -200,10 +203,10 @@ $ vespa document put id:mynamespace:music::a-head-full-of-dreams src/test/resour DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(document.OperationPut, args, timeoutSecs, printCurl, cli) + return sendOperation(document.OperationPut, args, timeoutSecs, waitSecs, printCurl, cli) }, } - addDocumentFlags(cmd, &printCurl, &timeoutSecs) + addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } @@ -211,6 +214,7 @@ func newDocumentUpdateCmd(cli *CLI) *cobra.Command { var ( printCurl bool timeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "update [id] json-file", @@ -223,10 +227,10 @@ $ vespa document update id:mynamespace:music::a-head-full-of-dreams src/test/res DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(document.OperationUpdate, args, timeoutSecs, printCurl, cli) + return sendOperation(document.OperationUpdate, args, timeoutSecs, waitSecs, printCurl, cli) }, } - addDocumentFlags(cmd, &printCurl, &timeoutSecs) + addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } @@ -234,6 +238,7 @@ func newDocumentRemoveCmd(cli *CLI) *cobra.Command { var ( printCurl bool timeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "remove id | json-file", @@ -247,7 +252,7 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { if strings.HasPrefix(args[0], "id:") { - client, service, err := documentClient(cli, timeoutSecs, printCurl) + client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) if err != nil { return err } @@ -259,11 +264,11 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`, result := client.Send(doc) return printResult(cli, operationResult(false, doc, service.service, result), false) } else { - return sendOperation(document.OperationRemove, args, timeoutSecs, printCurl, cli) + return sendOperation(document.OperationRemove, args, timeoutSecs, waitSecs, printCurl, cli) } }, } - addDocumentFlags(cmd, &printCurl, &timeoutSecs) + addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } @@ -271,6 +276,7 @@ func newDocumentGetCmd(cli *CLI) *cobra.Command { var ( printCurl bool timeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "get id", @@ -280,19 +286,19 @@ func newDocumentGetCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Example: `$ vespa document get id:mynamespace:music::a-head-full-of-dreams`, RunE: func(cmd *cobra.Command, args []string) error { - return readDocument(args[0], timeoutSecs, printCurl, cli) + return readDocument(args[0], timeoutSecs, waitSecs, printCurl, cli) }, } - addDocumentFlags(cmd, &printCurl, &timeoutSecs) + addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } -func documentService(cli *CLI) (*vespa.Service, error) { +func documentService(cli *CLI, waitSecs int) (*vespa.Service, error) { target, err := cli.target(targetOptions{}) if err != nil { return nil, err } - return cli.service(target, vespa.DocumentService, 0, cli.config.cluster()) + return cli.service(target, vespa.DocumentService, 0, cli.config.cluster(), time.Duration(waitSecs)*time.Second) } func printResult(cli *CLI, result util.OperationResult, payloadOnlyOnSuccess bool) error { diff --git a/client/go/internal/cli/cmd/feed.go b/client/go/internal/cli/cmd/feed.go index fb01998b83f..7d4b9cc8042 100644 --- a/client/go/internal/cli/cmd/feed.go +++ b/client/go/internal/cli/cmd/feed.go @@ -16,7 +16,7 @@ import ( "github.com/vespa-engine/vespa/client/go/internal/vespa/document" ) -func addFeedFlags(cmd *cobra.Command, options *feedOptions) { +func addFeedFlags(cli *CLI, cmd *cobra.Command, options *feedOptions) { cmd.PersistentFlags().IntVar(&options.connections, "connections", 8, "The number of connections to use") cmd.PersistentFlags().StringVar(&options.compression, "compression", "auto", `Compression mode to use. Default is "auto" which compresses large documents. Must be "auto", "gzip" or "none"`) cmd.PersistentFlags().IntVar(&options.timeoutSecs, "timeout", 0, "Individual feed operation timeout in seconds. 0 to disable (default 0)") @@ -34,6 +34,7 @@ func addFeedFlags(cmd *cobra.Command, options *feedOptions) { // Hide these flags as they are intended for internal use cmd.PersistentFlags().MarkHidden(memprofile) cmd.PersistentFlags().MarkHidden(cpuprofile) + cli.bindWaitFlag(cmd, 0, &options.waitSecs) } type feedOptions struct { @@ -47,6 +48,7 @@ type feedOptions struct { summarySecs int speedtestBytes int speedtestSecs int + waitSecs int memprofile string cpuprofile string @@ -92,11 +94,11 @@ $ cat docs.jsonl | vespa feed -`, return err }, } - addFeedFlags(cmd, &options) + addFeedFlags(cli, cmd, &options) return cmd } -func createServices(n int, timeout time.Duration, cli *CLI) ([]util.HTTPClient, string, error) { +func createServices(n int, timeout time.Duration, waitSecs int, cli *CLI) ([]util.HTTPClient, string, error) { if n < 1 { return nil, "", fmt.Errorf("need at least one client") } @@ -107,7 +109,7 @@ func createServices(n int, timeout time.Duration, cli *CLI) ([]util.HTTPClient, services := make([]util.HTTPClient, 0, n) baseURL := "" for i := 0; i < n; i++ { - service, err := cli.service(target, vespa.DocumentService, 0, cli.config.cluster()) + service, err := cli.service(target, vespa.DocumentService, 0, cli.config.cluster(), time.Duration(waitSecs)*time.Second) if err != nil { return nil, "", err } @@ -201,7 +203,7 @@ func enqueueAndWait(files []string, dispatcher *document.Dispatcher, options fee func feed(files []string, options feedOptions, cli *CLI) error { timeout := time.Duration(options.timeoutSecs) * time.Second - clients, baseURL, err := createServices(options.connections, timeout, cli) + clients, baseURL, err := createServices(options.connections, timeout, options.waitSecs, cli) if err != nil { return err } diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go index 6daa8db6e81..14fbae68b17 100644 --- a/client/go/internal/cli/cmd/prod.go +++ b/client/go/internal/cli/cmd/prod.go @@ -142,10 +142,7 @@ $ vespa prod deploy`, if err := verifyTests(cli, pkg); err != nil { return err } - opts, err := cli.createDeploymentOptions(pkg, target) - if err != nil { - return err - } + opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} if err := maybeCopyCertificate(copyCert, true, cli, target, pkg); err != nil { return err } @@ -405,6 +402,6 @@ func verifyTest(cli *CLI, testsParent string, suite string, required bool) error } return nil } - _, _, err = runTests(cli, testDirectory, true) + _, _, err = runTests(cli, testDirectory, true, 0) return err } diff --git a/client/go/internal/cli/cmd/query.go b/client/go/internal/cli/cmd/query.go index a14e2d51036..a5b35052b11 100644 --- a/client/go/internal/cli/cmd/query.go +++ b/client/go/internal/cli/cmd/query.go @@ -24,6 +24,7 @@ func newQueryCmd(cli *CLI) *cobra.Command { var ( printCurl bool queryTimeoutSecs int + waitSecs int ) cmd := &cobra.Command{ Use: "query query-parameters", @@ -38,11 +39,12 @@ can be set by the syntax [parameter-name]=[value].`, SilenceUsage: true, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return query(cli, args, queryTimeoutSecs, printCurl) + return query(cli, args, queryTimeoutSecs, waitSecs, printCurl) }, } cmd.PersistentFlags().BoolVarP(&printCurl, "verbose", "v", false, "Print the equivalent curl command for the query") cmd.Flags().IntVarP(&queryTimeoutSecs, "timeout", "T", 10, "Timeout for the query in seconds") + cli.bindWaitFlag(cmd, 0, &waitSecs) return cmd } @@ -57,12 +59,12 @@ func printCurl(stderr io.Writer, url string, service *vespa.Service) error { return err } -func query(cli *CLI, arguments []string, timeoutSecs int, curl bool) error { +func query(cli *CLI, arguments []string, timeoutSecs, waitSecs int, curl bool) error { target, err := cli.target(targetOptions{}) if err != nil { return err } - service, err := cli.service(target, vespa.QueryService, 0, cli.config.cluster()) + service, err := cli.service(target, vespa.QueryService, 0, cli.config.cluster(), time.Duration(waitSecs)*time.Second) if err != nil { return err } diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go index c6742d74f3e..ad42ea588b0 100644 --- a/client/go/internal/cli/cmd/root.go +++ b/client/go/internal/cli/cmd/root.go @@ -31,7 +31,6 @@ const ( clusterFlag = "cluster" zoneFlag = "zone" targetFlag = "target" - waitFlag = "wait" colorFlag = "color" quietFlag = "quiet" ) @@ -202,7 +201,6 @@ func (c *CLI) configureFlags() map[string]*pflag.Flag { instance string cluster string zone string - waitSecs int color string quiet bool ) @@ -211,7 +209,6 @@ func (c *CLI) configureFlags() map[string]*pflag.Flag { c.cmd.PersistentFlags().StringVarP(&instance, instanceFlag, "i", "", "The instance of the application to use") c.cmd.PersistentFlags().StringVarP(&cluster, clusterFlag, "C", "", "The container cluster to use. This is only required for applications with multiple clusters") c.cmd.PersistentFlags().StringVarP(&zone, zoneFlag, "z", "", "The zone to use. This defaults to a dev zone") - c.cmd.PersistentFlags().IntVarP(&waitSecs, waitFlag, "w", 0, "Number of seconds to wait for a service to become ready") c.cmd.PersistentFlags().StringVarP(&color, colorFlag, "c", "auto", `Whether to use colors in output. Must be "auto", "never", or "always"`) c.cmd.PersistentFlags().BoolVarP(&quiet, quietFlag, "q", false, "Print only errors") flags := make(map[string]*pflag.Flag) @@ -280,6 +277,14 @@ func (c *CLI) configureCommands() { rootCmd.AddCommand(newFeedCmd(c)) // feed } +func (c *CLI) bindWaitFlag(cmd *cobra.Command, defaultSecs int, value *int) { + desc := "Number of seconds to wait for a service to become ready. 0 to disable" + if defaultSecs == 0 { + desc += " (default 0)" + } + cmd.PersistentFlags().IntVarP(value, "wait", "w", defaultSecs, desc) +} + func (c *CLI) printErr(err error, hints ...string) { fmt.Fprintln(c.Stderr, color.RedString("Error:"), err) for _, hint := range hints { @@ -502,11 +507,7 @@ func (c *CLI) system(targetType string) (vespa.System, error) { // service returns the service of given name located at target. If non-empty, cluster specifies a cluster to query. This // function blocks according to the wait period configured in this CLI. The parameter sessionOrRunID specifies either // the session ID (local target) or run ID (cloud target) to wait for. -func (c *CLI) service(target vespa.Target, name string, sessionOrRunID int64, cluster string) (*vespa.Service, error) { - timeout, err := c.config.timeout() - if err != nil { - return nil, err - } +func (c *CLI) service(target vespa.Target, name string, sessionOrRunID int64, cluster string, timeout time.Duration) (*vespa.Service, error) { if timeout > 0 { log.Printf("Waiting up to %s for %s service to become available ...", color.CyanString(timeout.String()), color.CyanString(name)) } @@ -521,18 +522,6 @@ func (c *CLI) service(target vespa.Target, name string, sessionOrRunID int64, cl return s, nil } -func (c *CLI) createDeploymentOptions(pkg vespa.ApplicationPackage, target vespa.Target) (vespa.DeploymentOptions, error) { - timeout, err := c.config.timeout() - if err != nil { - return vespa.DeploymentOptions{}, err - } - return vespa.DeploymentOptions{ - ApplicationPackage: pkg, - Target: target, - Timeout: timeout, - }, nil -} - // isCI returns true if running inside a continuous integration environment. func (c *CLI) isCI() bool { _, ok := c.Environment["CI"] diff --git a/client/go/internal/cli/cmd/status.go b/client/go/internal/cli/cmd/status.go index ab98a4da160..6570aeff448 100644 --- a/client/go/internal/cli/cmd/status.go +++ b/client/go/internal/cli/cmd/status.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" "log" + "time" "github.com/fatih/color" "github.com/spf13/cobra" @@ -14,7 +15,8 @@ import ( ) func newStatusCmd(cli *CLI) *cobra.Command { - return &cobra.Command{ + var waitSecs int + cmd := &cobra.Command{ Use: "status", Short: "Verify that a service is ready to use (query by default)", Example: `$ vespa status query`, @@ -22,13 +24,16 @@ func newStatusCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return printServiceStatus(cli, vespa.QueryService) + return printServiceStatus(cli, vespa.QueryService, waitSecs) }, } + cli.bindWaitFlag(cmd, 0, &waitSecs) + return cmd } func newStatusQueryCmd(cli *CLI) *cobra.Command { - return &cobra.Command{ + var waitSecs int + cmd := &cobra.Command{ Use: "query", Short: "Verify that the query service is ready to use (default)", Example: `$ vespa status query`, @@ -36,13 +41,16 @@ func newStatusQueryCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - return printServiceStatus(cli, vespa.QueryService) + return printServiceStatus(cli, vespa.QueryService, waitSecs) }, } + cli.bindWaitFlag(cmd, 0, &waitSecs) + return cmd } func newStatusDocumentCmd(cli *CLI) *cobra.Command { - return &cobra.Command{ + var waitSecs int + cmd := &cobra.Command{ Use: "document", Short: "Verify that the document service is ready to use", Example: `$ vespa status document`, @@ -50,13 +58,16 @@ func newStatusDocumentCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - return printServiceStatus(cli, vespa.DocumentService) + return printServiceStatus(cli, vespa.DocumentService, waitSecs) }, } + cli.bindWaitFlag(cmd, 0, &waitSecs) + return cmd } func newStatusDeployCmd(cli *CLI) *cobra.Command { - return &cobra.Command{ + var waitSecs int + cmd := &cobra.Command{ Use: "deploy", Short: "Verify that the deploy service is ready to use", Example: `$ vespa status deploy`, @@ -64,26 +75,25 @@ func newStatusDeployCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - return printServiceStatus(cli, vespa.DeployService) + return printServiceStatus(cli, vespa.DeployService, waitSecs) }, } + cli.bindWaitFlag(cmd, 0, &waitSecs) + return cmd } -func printServiceStatus(cli *CLI, name string) error { +func printServiceStatus(cli *CLI, name string, waitSecs int) error { t, err := cli.target(targetOptions{}) if err != nil { return err } cluster := cli.config.cluster() - s, err := cli.service(t, name, 0, cluster) + s, err := cli.service(t, name, 0, cluster, 0) if err != nil { return err } - timeout, err := cli.config.timeout() - if err != nil { - return err - } - status, err := s.Wait(timeout) + // Wait explicitly + status, err := s.Wait(time.Duration(waitSecs) * time.Second) clusterPart := "" if cluster != "" { clusterPart = fmt.Sprintf(" named %s", color.CyanString(cluster)) diff --git a/client/go/internal/cli/cmd/test.go b/client/go/internal/cli/cmd/test.go index 8c4501e2870..abee760efbb 100644 --- a/client/go/internal/cli/cmd/test.go +++ b/client/go/internal/cli/cmd/test.go @@ -26,6 +26,7 @@ import ( ) func newTestCmd(cli *CLI) *cobra.Command { + var waitSecs int testCmd := &cobra.Command{ Use: "test test-directory-or-file", Short: "Run a test suite, or a single test", @@ -40,7 +41,7 @@ $ vespa test src/test/application/tests/system-test/feed-and-query.json`, DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - count, failed, err := runTests(cli, args[0], false) + count, failed, err := runTests(cli, args[0], false, waitSecs) if err != nil { return err } @@ -64,10 +65,11 @@ $ vespa test src/test/application/tests/system-test/feed-and-query.json`, } }, } + cli.bindWaitFlag(testCmd, 0, &waitSecs) return testCmd } -func runTests(cli *CLI, rootPath string, dryRun bool) (int, []string, error) { +func runTests(cli *CLI, rootPath string, dryRun bool, waitSecs int) (int, []string, error) { count := 0 failed := make([]string, 0) if stat, err := os.Stat(rootPath); err != nil { @@ -86,7 +88,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool) (int, []string, error) { fmt.Fprintln(cli.Stdout, "") previousFailed = false } - failure, err := runTest(testPath, context) + failure, err := runTest(testPath, context, waitSecs) if err != nil { return 0, nil, err } @@ -98,7 +100,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool) (int, []string, error) { } } } else if strings.HasSuffix(stat.Name(), ".json") { - failure, err := runTest(rootPath, testContext{testsPath: filepath.Dir(rootPath), dryRun: dryRun, cli: cli}) + failure, err := runTest(rootPath, testContext{testsPath: filepath.Dir(rootPath), dryRun: dryRun, cli: cli}, waitSecs) if err != nil { return 0, nil, err } @@ -114,7 +116,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool) (int, []string, error) { } // Runs the test at the given path, and returns the specified test name if the test fails -func runTest(testPath string, context testContext) (string, error) { +func runTest(testPath string, context testContext, waitSecs int) (string, error) { var test test testBytes, err := os.ReadFile(testPath) if err != nil { @@ -147,7 +149,7 @@ func runTest(testPath string, context testContext) (string, error) { if step.Name != "" { stepName += ": " + step.Name } - failure, longFailure, err := verify(step, test.Defaults.Cluster, defaultParameters, context) + failure, longFailure, err := verify(step, test.Defaults.Cluster, defaultParameters, context, waitSecs) if err != nil { fmt.Fprintln(context.cli.Stderr) return "", errHint(fmt.Errorf("error in %s: %w", stepName, err), "See https://docs.vespa.ai/en/reference/testing") @@ -170,7 +172,7 @@ func runTest(testPath string, context testContext) (string, error) { } // Asserts specified response is obtained for request, or returns a failure message, or an error if this fails -func verify(step step, defaultCluster string, defaultParameters map[string]string, context testContext) (string, string, error) { +func verify(step step, defaultCluster string, defaultParameters map[string]string, context testContext, waitSecs int) (string, string, error) { requestBody, err := getBody(step.Request.BodyRaw, context.testsPath) if err != nil { return "", "", err @@ -214,7 +216,7 @@ func verify(step step, defaultCluster string, defaultParameters map[string]strin if err != nil { return "", "", err } - service, err = target.Service(vespa.QueryService, 0, 0, cluster) + service, err = target.Service(vespa.QueryService, time.Duration(waitSecs)*time.Second, 0, cluster) if err != nil { return "", "", err } diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go index a588474bd2b..302857bc7cd 100644 --- a/client/go/internal/cli/cmd/visit.go +++ b/client/go/internal/cli/cmd/visit.go @@ -35,6 +35,7 @@ type visitArgs struct { sliceId int bucketSpace string bucketSpaces []string + waitSecs int cli *CLI } @@ -107,7 +108,7 @@ $ vespa visit --field-set "[id]" # list document IDs if !result.Success { return fmt.Errorf("argument error: %s", result.Message) } - service, err := documentService(cli) + service, err := documentService(cli, vArgs.waitSecs) if err != nil { return err } @@ -135,6 +136,7 @@ $ vespa visit --field-set "[id]" # list document IDs cmd.Flags().IntVar(&vArgs.sliceId, "slice-id", -1, `The number of the slice this visit invocation should fetch`) cmd.Flags().IntVar(&vArgs.slices, "slices", -1, `Split the document corpus into this number of independent slices`) cmd.Flags().StringSliceVar(&vArgs.bucketSpaces, "bucket-space", []string{"global", "default"}, `"default" or "global" bucket space`) + cli.bindWaitFlag(cmd, 0, &vArgs.waitSecs) return cmd } diff --git a/client/go/internal/cli/cmd/visit_test.go b/client/go/internal/cli/cmd/visit_test.go index 9bb8f61554a..f85fb739370 100644 --- a/client/go/internal/cli/cmd/visit_test.go +++ b/client/go/internal/cli/cmd/visit_test.go @@ -96,7 +96,7 @@ func withMockClient(t *testing.T, prepCli func(*mock.HTTPClient), runOp func(*ve prepCli(client) cli, _, _ := newTestCLI(t) cli.httpClient = client - service, _ := documentService(cli) + service, _ := documentService(cli, 0) runOp(service) return client.LastRequest } -- cgit v1.2.3