diff --git a/cmd/minikube/cmd/status.go b/cmd/minikube/cmd/status.go index 11950ee7d082..bfcde0880bb4 100644 --- a/cmd/minikube/cmd/status.go +++ b/cmd/minikube/cmd/status.go @@ -135,6 +135,7 @@ type Status struct { APIServer string Kubeconfig string Worker bool + TimeToStop string } // ClusterState holds a cluster state representation @@ -142,6 +143,7 @@ type ClusterState struct { BaseState BinaryVersion string + TimeToStop string Components map[string]BaseState Nodes []NodeState } @@ -180,6 +182,7 @@ host: {{.Host}} kubelet: {{.Kubelet}} apiserver: {{.APIServer}} kubeconfig: {{.Kubeconfig}} +timeToStop: {{.TimeToStop}} ` workerStatusFormat = `{{.Name}} @@ -307,6 +310,7 @@ func nodeStatus(api libmachine.API, cc config.ClusterConfig, n config.Node) (*St Kubelet: Nonexistent, Kubeconfig: Nonexistent, Worker: !controlPlane, + TimeToStop: Nonexistent, } hs, err := machine.Status(api, name) @@ -366,7 +370,10 @@ func nodeStatus(api libmachine.API, cc config.ClusterConfig, n config.Node) (*St stk := kverify.ServiceStatus(cr, "kubelet") st.Kubelet = stk.String() - + if cc.ScheduledStop != nil { + initiationTime := time.Unix(cc.ScheduledStop.InitiationTime, 0) + st.TimeToStop = time.Until(initiationTime.Add(cc.ScheduledStop.Duration)).String() + } // Early exit for worker nodes if !controlPlane { return st, nil @@ -478,6 +485,7 @@ func clusterState(sts []*Status) ClusterState { statusName = sts[0].Host } sc := statusCode(statusName) + cs := ClusterState{ BinaryVersion: version.GetVersion(), @@ -488,6 +496,8 @@ func clusterState(sts []*Status) ClusterState { StatusDetail: codeDetails[sc], }, + TimeToStop: sts[0].TimeToStop, + Components: map[string]BaseState{ "kubeconfig": {Name: "kubeconfig", StatusCode: statusCode(sts[0].Kubeconfig)}, }, diff --git a/cmd/minikube/cmd/status_test.go b/cmd/minikube/cmd/status_test.go index 8bae78103706..aa9f905c22af 100644 --- a/cmd/minikube/cmd/status_test.go +++ b/cmd/minikube/cmd/status_test.go @@ -51,18 +51,18 @@ func TestStatusText(t *testing.T) { }{ { name: "ok", - state: &Status{Name: "minikube", Host: "Running", Kubelet: "Running", APIServer: "Running", Kubeconfig: Configured}, - want: "minikube\ntype: Control Plane\nhost: Running\nkubelet: Running\napiserver: Running\nkubeconfig: Configured\n\n", + state: &Status{Name: "minikube", Host: "Running", Kubelet: "Running", APIServer: "Running", Kubeconfig: Configured, TimeToStop: "10m"}, + want: "minikube\ntype: Control Plane\nhost: Running\nkubelet: Running\napiserver: Running\nkubeconfig: Configured\ntimeToStop: 10m\n\n", }, { name: "paused", - state: &Status{Name: "minikube", Host: "Running", Kubelet: "Stopped", APIServer: "Paused", Kubeconfig: Configured}, - want: "minikube\ntype: Control Plane\nhost: Running\nkubelet: Stopped\napiserver: Paused\nkubeconfig: Configured\n\n", + state: &Status{Name: "minikube", Host: "Running", Kubelet: "Stopped", APIServer: "Paused", Kubeconfig: Configured, TimeToStop: Nonexistent}, + want: "minikube\ntype: Control Plane\nhost: Running\nkubelet: Stopped\napiserver: Paused\nkubeconfig: Configured\ntimeToStop: Nonexistent\n\n", }, { name: "down", - state: &Status{Name: "minikube", Host: "Stopped", Kubelet: "Stopped", APIServer: "Stopped", Kubeconfig: Misconfigured}, - want: "minikube\ntype: Control Plane\nhost: Stopped\nkubelet: Stopped\napiserver: Stopped\nkubeconfig: Misconfigured\n\n\nWARNING: Your kubectl is pointing to stale minikube-vm.\nTo fix the kubectl context, run `minikube update-context`\n", + state: &Status{Name: "minikube", Host: "Stopped", Kubelet: "Stopped", APIServer: "Stopped", Kubeconfig: Misconfigured, TimeToStop: Nonexistent}, + want: "minikube\ntype: Control Plane\nhost: Stopped\nkubelet: Stopped\napiserver: Stopped\nkubeconfig: Misconfigured\ntimeToStop: Nonexistent\n\n\nWARNING: Your kubectl is pointing to stale minikube-vm.\nTo fix the kubectl context, run `minikube update-context`\n", }, } for _, tc := range tests { @@ -86,9 +86,9 @@ func TestStatusJSON(t *testing.T) { name string state *Status }{ - {"ok", &Status{Host: "Running", Kubelet: "Running", APIServer: "Running", Kubeconfig: Configured}}, - {"paused", &Status{Host: "Running", Kubelet: "Stopped", APIServer: "Paused", Kubeconfig: Configured}}, - {"down", &Status{Host: "Stopped", Kubelet: "Stopped", APIServer: "Stopped", Kubeconfig: Misconfigured}}, + {"ok", &Status{Host: "Running", Kubelet: "Running", APIServer: "Running", Kubeconfig: Configured, TimeToStop: "10m"}}, + {"paused", &Status{Host: "Running", Kubelet: "Stopped", APIServer: "Paused", Kubeconfig: Configured, TimeToStop: Nonexistent}}, + {"down", &Status{Host: "Stopped", Kubelet: "Stopped", APIServer: "Stopped", Kubeconfig: Misconfigured, TimeToStop: Nonexistent}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/site/content/en/docs/commands/status.md b/site/content/en/docs/commands/status.md index f4902dab0baf..e5c3c89e7dd5 100644 --- a/site/content/en/docs/commands/status.md +++ b/site/content/en/docs/commands/status.md @@ -23,7 +23,7 @@ minikube status [flags] ``` -f, --format string Go template format string for the status output. The format for Go templates can be found here: https://golang.org/pkg/text/template/ - For the list accessible variables for the template, see the struct values here: https://godoc.org/k8s.io/minikube/cmd/minikube/cmd#Status (default "{{.Name}}\ntype: Control Plane\nhost: {{.Host}}\nkubelet: {{.Kubelet}}\napiserver: {{.APIServer}}\nkubeconfig: {{.Kubeconfig}}\n\n") + For the list accessible variables for the template, see the struct values here: https://godoc.org/k8s.io/minikube/cmd/minikube/cmd#Status (default "{{.Name}}\ntype: Control Plane\nhost: {{.Host}}\nkubelet: {{.Kubelet}}\napiserver: {{.APIServer}}\nkubeconfig: {{.Kubeconfig}}\ntimeToStop: {{.TimeToStop}}\n\n") -l, --layout string output layout (EXPERIMENTAL, JSON only): 'nodes' or 'cluster' (default "nodes") -n, --node string The node to check status for. Defaults to control plane. Leave blank with default format for status on all nodes. -o, --output string minikube status --output OUTPUT. json, text (default "text") diff --git a/test/integration/scheduled_stop_test.go b/test/integration/scheduled_stop_test.go index 508a38599969..f10c4e2d6bf4 100644 --- a/test/integration/scheduled_stop_test.go +++ b/test/integration/scheduled_stop_test.go @@ -51,7 +51,8 @@ func TestScheduledStopWindows(t *testing.T) { // schedule a stop for 5m from now stopMinikube(ctx, t, profile, []string{"--schedule", "5m"}) - + // make sure timeToStop is present in status + ensureMinikubeScheduledTime(ctx, t, profile, 5*time.Minute) // make sure the systemd service is running rr, err := Run(t, exec.CommandContext(ctx, Target(), []string{"ssh", "-p", profile, "--", "sudo", "systemctl", "show", constants.ScheduledStopSystemdService, "--no-page"}...)) if err != nil { @@ -67,7 +68,9 @@ func TestScheduledStopWindows(t *testing.T) { // sleep for 5 seconds time.Sleep(5 * time.Second) // make sure minikube status is "Stopped" - ensureMinikubeStatus(ctx, t, profile, state.Stopped.String()) + ensureMinikubeStatus(ctx, t, profile, "Host", state.Stopped.String()) + // make sure minikube timtostop is "Nonexistent" + ensureMinikubeStatus(ctx, t, profile, "TimeToStop", "Nonexistent") } func TestScheduledStopUnix(t *testing.T) { @@ -84,6 +87,8 @@ func TestScheduledStopUnix(t *testing.T) { // schedule a stop for 5 min from now and make sure PID is created stopMinikube(ctx, t, profile, []string{"--schedule", "5m"}) + // make sure timeToStop is present in status + ensureMinikubeScheduledTime(ctx, t, profile, 5*time.Minute) pid := checkPID(t, profile) if !processRunning(t, pid) { t.Fatalf("process %v is not running", pid) @@ -100,14 +105,18 @@ func TestScheduledStopUnix(t *testing.T) { // sleep 12 just to be safe stopMinikube(ctx, t, profile, []string{"--cancel-scheduled"}) time.Sleep(12 * time.Second) - ensureMinikubeStatus(ctx, t, profile, state.Running.String()) + ensureMinikubeStatus(ctx, t, profile, "Host", state.Running.String()) // schedule another stop, make sure minikube status is "Stopped" stopMinikube(ctx, t, profile, []string{"--schedule", "5s"}) if processRunning(t, pid) { t.Fatalf("process %v running but should have been killed on reschedule of stop", pid) } - ensureMinikubeStatus(ctx, t, profile, state.Stopped.String()) + + // make sure minikube status is "Stopped" + ensureMinikubeStatus(ctx, t, profile, "Host", state.Stopped.String()) + // make sure minikube timtostop is "Nonexistent" + ensureMinikubeStatus(ctx, t, profile, "TimeToStop", "Nonexistent") } func startMinikube(ctx context.Context, t *testing.T, profile string) { @@ -156,14 +165,13 @@ func processRunning(t *testing.T, pid string) bool { t.Log("signal error was: ", err) return err == nil } -func ensureMinikubeStatus(ctx context.Context, t *testing.T, profile, wantStatus string) { - // wait allotted time to make sure minikube status is "Stopped" +func ensureMinikubeStatus(ctx context.Context, t *testing.T, profile, key string, wantStatus string) { checkStatus := func() error { ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) defer cancel() - got := Status(ctx, t, Target(), profile, "Host", profile) + got := Status(ctx, t, Target(), profile, key, profile) if got != wantStatus { - return fmt.Errorf("expected post-stop host status to be -%q- but got *%q*", state.Stopped, got) + return fmt.Errorf("expected post-stop %q status to be -%q- but got *%q*", key, wantStatus, got) } return nil } @@ -171,3 +179,19 @@ func ensureMinikubeStatus(ctx context.Context, t *testing.T, profile, wantStatus t.Fatalf("error %v", err) } } + +func ensureMinikubeScheduledTime(ctx context.Context, t *testing.T, profile string, givenTime time.Duration) { + checkTime := func() error { + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) + defer cancel() + got := Status(ctx, t, Target(), profile, "TimeToStop", profile) + gotTime, _ := time.ParseDuration(got) + if gotTime < givenTime { + return nil + } + return fmt.Errorf("expected scheduled stop TimeToStop to be less than *%q* but got *%q*", givenTime, got) + } + if err := retry.Expo(checkTime, time.Second, time.Minute); err != nil { + t.Fatalf("error %v", err) + } +}