diff options
-rw-r--r-- | client/go/internal/cli/cmd/curl.go | 6 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/deploy.go | 44 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/deploy_test.go | 1 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/document.go | 3 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/feed.go | 3 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/query.go | 3 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/root.go | 38 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/status.go | 37 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/test.go | 3 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/waiter.go | 97 | ||||
-rw-r--r-- | client/go/internal/vespa/deploy.go | 6 | ||||
-rw-r--r-- | client/go/internal/vespa/target.go | 5 | ||||
-rw-r--r-- | client/go/internal/vespa/target_cloud.go | 16 | ||||
-rw-r--r-- | client/go/internal/vespa/target_custom.go | 12 | ||||
-rw-r--r-- | client/go/internal/vespa/target_test.go | 2 |
15 files changed, 156 insertions, 120 deletions
diff --git a/client/go/internal/cli/cmd/curl.go b/client/go/internal/cli/cmd/curl.go index 500a2a75663..44540db9ccf 100644 --- a/client/go/internal/cli/cmd/curl.go +++ b/client/go/internal/cli/cmd/curl.go @@ -41,14 +41,14 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain } var service *vespa.Service useDeploy := curlService == "deploy" - timeout := time.Duration(waitSecs) * time.Second + waiter := cli.waiter(false, time.Duration(waitSecs)*time.Second) if useDeploy { if cli.config.cluster() != "" { return fmt.Errorf("cannot specify cluster for service %s", curlService) } - service, err = target.DeployService(timeout) + service, err = target.DeployService() } else { - service, err = cli.service(target, cli.config.cluster(), time.Duration(waitSecs)*time.Second) + service, err = waiter.Service(target, cli.config.cluster()) } if err != nil { return err diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index b037a953b53..cf2e435fea5 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -73,11 +73,12 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, return err } } - if err := waitForDeployService(cli, target, timeout); err != nil { + waiter := cli.waiter(false, timeout) + if _, err := waiter.DeployService(target); err != nil { return err } var result vespa.PrepareResult - if err := cli.spinner(cli.Stderr, "Uploading application package ...", func() error { + if err := cli.spinner(cli.Stderr, "Uploading application package...", func() error { result, err = vespa.Deploy(opts) return err }); err != nil { @@ -98,7 +99,7 @@ $ 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 waitForContainerServices(cli, target, result.ID, timeout) + return waitForDeploymentReady(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"`) @@ -126,7 +127,7 @@ func newPrepareCmd(cli *CLI) *cobra.Command { } opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} var result vespa.PrepareResult - err = cli.spinner(cli.Stderr, "Uploading application package ...", func() error { + err = cli.spinner(cli.Stderr, "Uploading application package...", func() error { result, err = vespa.Prepare(opts) return err }) @@ -161,7 +162,8 @@ func newActivateCmd(cli *CLI) *cobra.Command { return err } timeout := time.Duration(waitSecs) * time.Second - if err := waitForDeployService(cli, target, timeout); err != nil { + waiter := cli.waiter(false, timeout) + if _, err := waiter.DeployService(target); err != nil { return err } opts := vespa.DeploymentOptions{Target: target, Timeout: timeout} @@ -170,43 +172,23 @@ func newActivateCmd(cli *CLI) *cobra.Command { return err } cli.printSuccess("Activated application with session ", sessionID) - return waitForContainerServices(cli, target, sessionID, timeout) + return waitForDeploymentReady(cli, target, sessionID, timeout) }, } cli.bindWaitFlag(cmd, 60, &waitSecs) return cmd } -func waitForDeployService(cli *CLI, target vespa.Target, timeout time.Duration) error { +func waitForDeploymentReady(cli *CLI, target vespa.Target, sessionOrRunID int64, timeout time.Duration) error { if timeout == 0 { return nil } - s, err := target.DeployService(0) - if err != nil { + waiter := cli.waiter(false, timeout) + if _, err := waiter.Deployment(target, sessionOrRunID); err != nil { return err } - cli.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for ", s.Description(), " to become ready ...") - return s.Wait(timeout) -} - -func waitForContainerServices(cli *CLI, target vespa.Target, sessionOrRunID int64, timeout time.Duration) error { - if timeout == 0 { - return nil - } - cli.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for deployment to converge ...") - if _, err := target.AwaitDeployment(sessionOrRunID, timeout); err != nil { - return err - } - services, err := cli.services(target, timeout) - if err != nil { - return err - } - for _, s := range services { - if err := s.Wait(timeout); err != nil { - return err - } - } - return nil + _, err := waiter.Services(target) + 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 35c9b05050c..d578b2a4629 100644 --- a/client/go/internal/cli/cmd/deploy_test.go +++ b/client/go/internal/cli/cmd/deploy_test.go @@ -74,7 +74,6 @@ func TestDeployWait(t *testing.T) { client.NextStatus(500) // ... then becomes healthy client.NextStatus(200) - client.NextStatus(200) // Deployment succeeds client.NextResponse(mock.HTTPResponse{ URI: "/application/v2/tenant/default/prepareandactivate", diff --git a/client/go/internal/cli/cmd/document.go b/client/go/internal/cli/cmd/document.go index aacef9be825..21db343c11e 100644 --- a/client/go/internal/cli/cmd/document.go +++ b/client/go/internal/cli/cmd/document.go @@ -298,7 +298,8 @@ func documentService(cli *CLI, waitSecs int) (*vespa.Service, error) { if err != nil { return nil, err } - return cli.service(target, cli.config.cluster(), time.Duration(waitSecs)*time.Second) + waiter := cli.waiter(false, time.Duration(waitSecs)*time.Second) + return waiter.Service(target, cli.config.cluster()) } 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 8a2bdf04ce3..8b8589baec3 100644 --- a/client/go/internal/cli/cmd/feed.go +++ b/client/go/internal/cli/cmd/feed.go @@ -107,8 +107,9 @@ func createServices(n int, timeout time.Duration, waitSecs int, cli *CLI) ([]uti } services := make([]util.HTTPClient, 0, n) baseURL := "" + waiter := cli.waiter(false, time.Duration(waitSecs)*time.Second) for i := 0; i < n; i++ { - service, err := cli.service(target, cli.config.cluster(), time.Duration(waitSecs)*time.Second) + service, err := waiter.Service(target, cli.config.cluster()) if err != nil { return nil, "", err } diff --git a/client/go/internal/cli/cmd/query.go b/client/go/internal/cli/cmd/query.go index 90f51b398c2..3f849fae99e 100644 --- a/client/go/internal/cli/cmd/query.go +++ b/client/go/internal/cli/cmd/query.go @@ -64,7 +64,8 @@ func query(cli *CLI, arguments []string, timeoutSecs, waitSecs int, curl bool) e if err != nil { return err } - service, err := cli.service(target, cli.config.cluster(), time.Duration(waitSecs)*time.Second) + waiter := cli.waiter(false, time.Duration(waitSecs)*time.Second) + service, err := waiter.Service(target, cli.config.cluster()) if err != nil { return err } diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go index 1dec2461ff8..69fd88c1b2b 100644 --- a/client/go/internal/cli/cmd/root.go +++ b/client/go/internal/cli/cmd/root.go @@ -343,6 +343,10 @@ func (c *CLI) confirm(question string, confirmByDefault bool) (bool, error) { } } +func (c *CLI) waiter(once bool, timeout time.Duration) *Waiter { + return &Waiter{Once: once, Timeout: timeout, cli: c} +} + // target creates a target according the configuration of this CLI and given opts. func (c *CLI) target(opts targetOptions) (vespa.Target, error) { targetType, err := c.targetType() @@ -513,40 +517,6 @@ func (c *CLI) system(targetType string) (vespa.System, error) { return vespa.System{}, fmt.Errorf("no default system found for %s target", targetType) } -// service returns the service identified by cluster ID, located at target. If timeout is positive, this waits for the -// service to become ready. -func (c *CLI) service(target vespa.Target, cluster string, timeout time.Duration) (*vespa.Service, error) { - targetType, err := c.targetType() - if err != nil { - return nil, err - } - if targetType.url != "" && cluster != "" { - return nil, fmt.Errorf("cluster cannot be specified when target is an URL") - } - services, err := c.services(target, timeout) - if err != nil { - return nil, err - } - service, err := vespa.FindService(cluster, services) - if err != nil { - return nil, errHint(err, "The --cluster option specifies the service to use") - } - if timeout > 0 { - c.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for ", color.CyanString(service.Description()), " to become available ...") - if err := service.Wait(timeout); err != nil { - return nil, err - } - } - return service, nil -} - -func (c *CLI) services(target vespa.Target, timeout time.Duration) ([]*vespa.Service, error) { - if timeout > 0 { - c.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for cluster discovery ...") - } - return target.ContainerServices(timeout) -} - // 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 c912a0fa712..f185fde6ca1 100644 --- a/client/go/internal/cli/cmd/status.go +++ b/client/go/internal/cli/cmd/status.go @@ -37,24 +37,26 @@ $ vespa status --cluster mycluster`, if err != nil { return err } + waiter := cli.waiter(true, time.Duration(waitSecs)*time.Second) if cluster == "" { - timeout := time.Duration(waitSecs) * time.Second - services, err := cli.services(t, timeout) + services, err := waiter.Services(t) if err != nil { return err } if len(services) == 0 { return errHint(fmt.Errorf("no services exist"), "Deployment may not be ready yet", "Try 'vespa status deployment'") } - for _, service := range services { - if err := printServiceStatus(service, service.Wait(timeout), cli); err != nil { - return err - } + for _, s := range services { + printReadyService(s, cli) } return nil } else { - s, err := cli.service(t, cluster, 0) - return printServiceStatus(s, err, cli) + s, err := waiter.Service(t, cluster) + if err != nil { + return err + } + printReadyService(s, cli) + return nil } }, } @@ -76,11 +78,13 @@ func newStatusDeployCmd(cli *CLI) *cobra.Command { if err != nil { return err } - s, err := t.DeployService(0) + waiter := cli.waiter(true, time.Duration(waitSecs)*time.Second) + s, err := waiter.DeployService(t) if err != nil { return err } - return printServiceStatus(s, s.Wait(time.Duration(waitSecs)*time.Second), cli) + printReadyService(s, cli) + return nil }, } cli.bindWaitFlag(cmd, 0, &waitSecs) @@ -112,11 +116,8 @@ $ vespa status deployment -t local [session-id] if err != nil { return err } - timeout := time.Duration(waitSecs) * time.Second - if timeout > 0 { - cli.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for deployment to converge ...") - } - id, err := t.AwaitDeployment(wantedID, timeout) + waiter := cli.waiter(true, time.Duration(waitSecs)*time.Second) + id, err := waiter.Deployment(t, wantedID) if err != nil { return err } @@ -137,12 +138,8 @@ $ vespa status deployment -t local [session-id] return cmd } -func printServiceStatus(s *vespa.Service, waitErr error, cli *CLI) error { - if waitErr != nil { - return waitErr - } +func printReadyService(s *vespa.Service, cli *CLI) { desc := s.Description() desc = strings.ToUpper(string(desc[0])) + string(desc[1:]) log.Print(desc, " at ", color.CyanString(s.BaseURL), " is ", color.GreenString("ready")) - return nil } diff --git a/client/go/internal/cli/cmd/test.go b/client/go/internal/cli/cmd/test.go index 58c254ad6e8..5b99973d879 100644 --- a/client/go/internal/cli/cmd/test.go +++ b/client/go/internal/cli/cmd/test.go @@ -220,7 +220,8 @@ func verify(step step, defaultCluster string, defaultParameters map[string]strin service, ok = context.clusters[cluster] if !ok { // Cache service so we don't have to discover it for every step - service, err = context.cli.service(target, cluster, time.Duration(waitSecs)*time.Second) + waiter := context.cli.waiter(false, time.Duration(waitSecs)*time.Second) + service, err = waiter.Service(target, cluster) if err != nil { return "", "", err } diff --git a/client/go/internal/cli/cmd/waiter.go b/client/go/internal/cli/cmd/waiter.go new file mode 100644 index 00000000000..a1919de798d --- /dev/null +++ b/client/go/internal/cli/cmd/waiter.go @@ -0,0 +1,97 @@ +package cmd + +import ( + "fmt" + "time" + + "github.com/fatih/color" + "github.com/vespa-engine/vespa/client/go/internal/vespa" +) + +// Waiter waits for Vespa services to become ready, within a timeout. +type Waiter struct { + // Once species whether we should wait at least one time, irregardless of timeout. + Once bool + + // Timeout specifies how long we should wait for an operation to complete. + Timeout time.Duration // TODO(mpolden): Consider making this a budget + + cli *CLI +} + +func (w *Waiter) wait() bool { return w.Once || w.Timeout > 0 } + +// DeployService returns the service providing the deploy API on given target, +func (w *Waiter) DeployService(target vespa.Target) (*vespa.Service, error) { + s, err := target.DeployService() + if err != nil { + return nil, err + } + if w.Timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for ", s.Description(), " to become ready...") + } + if w.wait() { + if err := s.Wait(w.Timeout); err != nil { + return nil, err + } + } + return s, nil +} + +// Service returns the service identified by cluster ID, available on target. +func (w *Waiter) Service(target vespa.Target, cluster string) (*vespa.Service, error) { + targetType, err := w.cli.targetType() + if err != nil { + return nil, err + } + if targetType.url != "" && cluster != "" { + return nil, fmt.Errorf("cluster cannot be specified when target is an URL") + } + services, err := w.Services(target) + if err != nil { + return nil, err + } + service, err := vespa.FindService(cluster, services) + if err != nil { + return nil, errHint(err, "The --cluster option specifies the service to use") + } + if w.Timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for ", color.CyanString(service.Description()), " to become available...") + } + if w.wait() { + if err := service.Wait(w.Timeout); err != nil { + return nil, err + } + } + return service, nil +} + +// Services returns all container services available on target. +func (w *Waiter) Services(target vespa.Target) ([]*vespa.Service, error) { + if w.Timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for cluster discovery...") + } + services, err := target.ContainerServices(w.Timeout) + if err != nil { + return nil, err + } + for _, s := range services { + if w.Timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for ", s.Description(), "...") + } + if w.wait() { + if err := s.Wait(w.Timeout); err != nil { + return nil, err + } + } + } + return services, nil +} + +// Deployment waits for a deployment to become ready, returning the ID of the converged deployment. +func (w *Waiter) Deployment(target vespa.Target, id int64) (int64, error) { + if w.Timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for deployment to converge...") + } + return target.AwaitDeployment(id, w.Timeout) +} diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go index 8fd35e4a933..3a3af0d66a0 100644 --- a/client/go/internal/vespa/deploy.go +++ b/client/go/internal/vespa/deploy.go @@ -92,7 +92,7 @@ func (d DeploymentOptions) String() string { } func (d *DeploymentOptions) url(path string) (*url.URL, error) { - service, err := d.Target.DeployService(0) + service, err := d.Target.DeployService() if err != nil { return nil, err } @@ -311,7 +311,7 @@ func Submit(opts DeploymentOptions, submission Submission) error { } func deployServiceDo(request *http.Request, timeout time.Duration, opts DeploymentOptions) (*http.Response, error) { - s, err := opts.Target.DeployService(0) + s, err := opts.Target.DeployService() if err != nil { return nil, err } @@ -373,7 +373,7 @@ func uploadApplicationPackage(url *url.URL, opts DeploymentOptions) (PrepareResu if err != nil { return PrepareResult{}, err } - service, err := opts.Target.DeployService(opts.Timeout) + service, err := opts.Target.DeployService() if err != nil { return PrepareResult{}, err } diff --git a/client/go/internal/vespa/target.go b/client/go/internal/vespa/target.go index bbee85217df..3c65369f986 100644 --- a/client/go/internal/vespa/target.go +++ b/client/go/internal/vespa/target.go @@ -67,9 +67,8 @@ type Target interface { // Deployment returns the deployment managed by this target. Deployment() Deployment - // DeployService returns the service to use for deployments. If timeout is positive, wait for the service to become - // ready. - DeployService(timeout time.Duration) (*Service, error) + // DeployService returns the service providing the deploy API on this target. + DeployService() (*Service, error) // ContainerServices returns all container services of the current deployment. If timeout is positive, wait for // services to be discovered. diff --git a/client/go/internal/vespa/target_cloud.go b/client/go/internal/vespa/target_cloud.go index 14b7f1f4c52..24133ba5fc3 100644 --- a/client/go/internal/vespa/target_cloud.go +++ b/client/go/internal/vespa/target_cloud.go @@ -98,21 +98,15 @@ func (t *cloudTarget) IsCloud() bool { return true } func (t *cloudTarget) Deployment() Deployment { return t.deploymentOptions.Deployment } -func (t *cloudTarget) DeployService(timeout time.Duration) (*Service, error) { - service := &Service{ +func (t *cloudTarget) DeployService() (*Service, error) { + return &Service{ BaseURL: t.apiOptions.System.URL, TLSOptions: t.apiOptions.TLSOptions, deployAPI: true, httpClient: t.httpClient, auth: t.apiAuth, retryInterval: t.retryInterval, - } - if timeout > 0 { - if err := service.Wait(timeout); err != nil { - return nil, err - } - } - return service, nil + }, nil } func (t *cloudTarget) ContainerServices(timeout time.Duration) ([]*Service, error) { @@ -160,7 +154,7 @@ func (t *cloudTarget) CheckVersion(clientVersion version.Version) error { if err != nil { return err } - deployService, err := t.DeployService(0) + deployService, err := t.DeployService() if err != nil { return err } @@ -244,7 +238,7 @@ func (t *cloudTarget) PrintLog(options LogOptions) error { } func (t *cloudTarget) deployServiceWait(fn responseFunc, reqFn requestFunc, timeout time.Duration) (int, error) { - deployService, err := t.DeployService(0) + deployService, err := t.DeployService() if err != nil { return 0, err } diff --git a/client/go/internal/vespa/target_custom.go b/client/go/internal/vespa/target_custom.go index a49654e3dff..b0ca4f8492c 100644 --- a/client/go/internal/vespa/target_custom.go +++ b/client/go/internal/vespa/target_custom.go @@ -79,7 +79,7 @@ func (t *customTarget) newService(url, name string, deployAPI bool) *Service { } } -func (t *customTarget) DeployService(timeout time.Duration) (*Service, error) { +func (t *customTarget) DeployService() (*Service, error) { if t.targetType == TargetCustom { return t.newService(t.baseURL, "", true), nil } @@ -87,13 +87,7 @@ func (t *customTarget) DeployService(timeout time.Duration) (*Service, error) { if err != nil { return nil, err } - service := t.newService(u.String(), "", true) - if timeout > 0 { - if err := service.Wait(timeout); err != nil { - return nil, err - } - } - return service, nil + return t.newService(u.String(), "", true), nil } func (t *customTarget) ContainerServices(timeout time.Duration) ([]*Service, error) { @@ -149,7 +143,7 @@ func (t *customTarget) urlWithPort(port int) (*url.URL, error) { } func (t *customTarget) serviceStatus(wantedGeneration int64, timeout time.Duration) (serviceStatus, error) { - deployService, err := t.DeployService(0) + deployService, err := t.DeployService() if err != nil { return serviceStatus{}, err } diff --git a/client/go/internal/vespa/target_test.go b/client/go/internal/vespa/target_test.go index c9a5fc74de9..b208489ddce 100644 --- a/client/go/internal/vespa/target_test.go +++ b/client/go/internal/vespa/target_test.go @@ -265,7 +265,7 @@ func createCloudTarget(t *testing.T, logWriter io.Writer) (Target, *mock.HTTPCli func getService(t *testing.T, target Target, name string) (*Service, error) { t.Helper() if name == "deploy" { - return target.DeployService(0) + return target.DeployService() } services, err := target.ContainerServices(0) require.Nil(t, err) |