functional_test.go 49.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// +build integration

/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package integration

import (
22
	"bufio"
23 24 25 26 27 28 29 30 31
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
32
	"path"
33
	"path/filepath"
34
	"regexp"
T
tstromberg 已提交
35
	"runtime"
36
	"strings"
37
	"testing"
38 39
	"time"

40 41
	"github.com/google/go-cmp/cmp"

S
Sharif Elgamal 已提交
42
	"k8s.io/minikube/pkg/drivers/kic/oci"
M
Medya Gh 已提交
43
	"k8s.io/minikube/pkg/minikube/config"
44
	"k8s.io/minikube/pkg/minikube/localpath"
45
	"k8s.io/minikube/pkg/minikube/reason"
M
lint  
Medya Gh 已提交
46
	"k8s.io/minikube/pkg/util/retry"
47

48 49
	"github.com/elazarl/goproxy"
	"github.com/hashicorp/go-retryablehttp"
50
	"github.com/otiai10/copy"
51 52 53
	"github.com/phayes/freeport"
	"github.com/pkg/errors"
	"golang.org/x/build/kubernetes/api"
54 55
)

56 57 58
// validateFunc are for subtests that share a single setup
type validateFunc func(context.Context, *testing.T, string)

M
Medya Gh 已提交
59 60 61
// used in validateStartWithProxy and validateSoftStart
var apiPortTest = 8441

62
// TestFunctional are functionality tests which can safely share a profile in parallel
N
Nick Kubala 已提交
63
func TestFunctional(t *testing.T) {
64 65

	profile := UniqueProfileName("functional")
M
Medya Gh 已提交
66
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
67
	defer func() {
68 69 70
		if !*cleanup {
			return
		}
71 72
		p := localSyncTestPath()
		if err := os.Remove(p); err != nil {
73
			t.Logf("unable to remove %q: %v", p, err)
74
		}
75

76
		Cleanup(t, profile, cancel)
77
	}()
78 79 80 81 82 83 84

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
85 86
			{"CopySyncFile", setupFileSync},                 // Set file for the file sync test case
			{"StartWithProxy", validateStartWithProxy},      // Set everything else up for success
M
Medya Gh 已提交
87
			{"SoftStart", validateSoftStart},                // do a soft start. ensure config didnt change.
88 89 90 91
			{"KubeContext", validateKubeContext},            // Racy: must come immediately after "minikube start"
			{"KubectlGetPods", validateKubectlGetPods},      // Make sure apiserver is up
			{"CacheCmd", validateCacheCmd},                  // Caches images needed for subsequent tests because of proxy
			{"MinikubeKubectlCmd", validateMinikubeKubectl}, // Make sure `minikube kubectl` works
P
Pablo Caderno 已提交
92
			{"MinikubeKubectlCmdDirectly", validateMinikubeKubectlDirectCall},
93
			{"ExtraConfig", validateExtraConfig}, // Ensure extra cmdline config change is saved
94
			{"ComponentHealth", validateComponentHealth},
95 96 97
		}
		for _, tc := range tests {
			tc := tc
98 99 100
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}
101
			t.Run(tc.name, func(t *testing.T) {
M
lint  
Medya Gh 已提交
102
				tc.validator(ctx, t, profile)
103
			})
M
Medya Gh 已提交
104
		}
105
	})
M
Medya Gh 已提交
106

107 108 109 110 111 112
	// Parallelized tests
	t.Run("parallel", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
			{"ConfigCmd", validateConfigCmd},
			{"DashboardCmd", validateDashboardCmd},
			{"DryRun", validateDryRun},
			{"StatusCmd", validateStatusCmd},
			{"LogsCmd", validateLogsCmd},
			{"MountCmd", validateMountCmd},
			{"ProfileCmd", validateProfileCmd},
			{"ServiceCmd", validateServiceCmd},
			{"AddonsCmd", validateAddonsCmd},
			{"PersistentVolumeClaim", validatePersistentVolumeClaim},
			{"TunnelCmd", validateTunnelCmd},
			{"SSHCmd", validateSSHCmd},
			{"MySQL", validateMySQL},
			{"FileSync", validateFileSync},
			{"CertSync", validateCertSync},
			{"UpdateContextCmd", validateUpdateContextCmd},
			{"DockerEnv", validateDockerEnv},
			{"NodeLabels", validateNodeLabels},
131
			{"LoadImage", validateLoadImage},
132 133 134
		}
		for _, tc := range tests {
			tc := tc
135 136 137 138
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

139 140 141 142 143
			t.Run(tc.name, func(t *testing.T) {
				MaybeParallel(t)
				tc.validator(ctx, t, profile)
			})
		}
M
Medya Gh 已提交
144
	})
145 146
}

M
Medya Gh 已提交
147 148
// validateNodeLabels checks if minikube cluster is created with correct kubernetes's node label
func validateNodeLabels(ctx context.Context, t *testing.T, profile string) {
149 150
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
151
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "nodes", "--output=go-template", "--template='{{range $k, $v := (index .items 0).metadata.labels}}{{$k}} {{end}}'"))
M
Medya Gh 已提交
152
	if err != nil {
M
Medya Gh 已提交
153
		t.Errorf("failed to 'kubectl get nodes' with args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
154 155 156
	}
	expectedLabels := []string{"minikube.k8s.io/commit", "minikube.k8s.io/version", "minikube.k8s.io/updated_at", "minikube.k8s.io/name"}
	for _, el := range expectedLabels {
M
Medya Gh 已提交
157
		if !strings.Contains(rr.Output(), el) {
M
Medya Gh 已提交
158
			t.Errorf("expected to have label %q in node labels but got : %s", el, rr.Output())
M
Medya Gh 已提交
159 160 161 162
		}
	}
}

163 164
// validateLoadImage makes sure that `minikube load image` works as expected
func validateLoadImage(ctx context.Context, t *testing.T, profile string) {
P
Priya Wadhwa 已提交
165 166 167
	if NoneDriver() {
		t.Skip("load image not available on none driver")
	}
168 169 170
	if GithubActionRunner() && runtime.GOOS == "darwin" {
		t.Skip("skipping on github actions and darwin, as this test requires a running docker daemon")
	}
P
Priya Wadhwa 已提交
171
	defer PostMortemLogs(t, profile)
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	// pull busybox
	busybox := "busybox:latest"
	rr, err := Run(t, exec.CommandContext(ctx, "docker", "pull", busybox))
	if err != nil {
		t.Fatalf("starting minikube: %v\n%s", err, rr.Output())
	}

	// tag busybox
	newImage := fmt.Sprintf("busybox:%s", profile)
	rr, err = Run(t, exec.CommandContext(ctx, "docker", "tag", busybox, newImage))
	if err != nil {
		t.Fatalf("starting minikube: %v\n%s", err, rr.Output())
	}

	// try to load the new image into minikube
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "image", "load", newImage))
	if err != nil {
		t.Fatalf("loading image into minikube: %v\n%s", err, rr.Output())
	}

	// make sure the image was correctly loaded
P
Priya Wadhwa 已提交
193 194 195 196 197 198 199
	var cmd *exec.Cmd
	if ContainerRuntime() == "docker" {
		cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images", "--format", "{{.Repository}}:{{.Tag}}")
	} else {
		cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "ctr", "-n=k8s.io", "image", "ls")
	}
	rr, err = Run(t, cmd)
200 201 202 203 204 205 206 207
	if err != nil {
		t.Fatalf("listing images: %v\n%s", err, rr.Output())
	}
	if !strings.Contains(rr.Output(), newImage) {
		t.Fatalf("expected %s to be loaded into minikube but the image is not there", newImage)
	}
}

208
// check functionality of minikube after evaling docker-env
P
Priya Wadhwa 已提交
209
// TODO: Add validatePodmanEnv for crio runtime: #10231
210
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {
211 212 213
	if cr := ContainerRuntime(); cr != "docker" {
		t.Skipf("only validate docker env with docker container runtime, currently testing %s", cr)
	}
214
	defer PostMortemLogs(t, profile)
215
	mctx, cancel := context.WithTimeout(ctx, Seconds(120))
216
	defer cancel()
M
Medya Gh 已提交
217 218
	var rr *RunResult
	var err error
M
Medya Gh 已提交
219
	if runtime.GOOS == "windows" {
M
Medya Gh 已提交
220 221
		c := exec.CommandContext(mctx, "powershell.exe", "-NoProfile", "-NonInteractive", Target()+" -p "+profile+" docker-env | Invoke-Expression ;"+Target()+" status -p "+profile)
		rr, err = Run(t, c)
M
Medya Gh 已提交
222 223 224 225 226
	} else {
		c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && "+Target()+" status -p "+profile)
		// we should be able to get minikube status with a bash which evaled docker-env
		rr, err = Run(t, c)
	}
M
Medya Gh 已提交
227 228
	if mctx.Err() == context.DeadlineExceeded {
		t.Errorf("failed to run the command by deadline. exceeded timeout. %s", rr.Command())
M
Medya Gh 已提交
229
	}
230
	if err != nil {
M
Medya Gh 已提交
231
		t.Fatalf("failed to do status after eval-ing docker-env. error: %v", err)
232 233
	}
	if !strings.Contains(rr.Output(), "Running") {
M
Medya Gh 已提交
234
		t.Fatalf("expected status output to include 'Running' after eval docker-env but got: *%s*", rr.Output())
235 236
	}

237
	mctx, cancel = context.WithTimeout(ctx, Seconds(60))
238 239
	defer cancel()
	// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
M
Medya Gh 已提交
240
	if runtime.GOOS == "windows" { // testing docker-env eval in powershell
M
Medya Gh 已提交
241 242
		c := exec.CommandContext(mctx, "powershell.exe", "-NoProfile", "-NonInteractive", Target(), "-p "+profile+" docker-env | Invoke-Expression ; docker images")
		rr, err = Run(t, c)
M
Medya Gh 已提交
243
	} else {
M
Medya Gh 已提交
244
		c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && docker images")
M
Medya Gh 已提交
245 246 247
		rr, err = Run(t, c)
	}

M
Medya Gh 已提交
248
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
249
		t.Errorf("failed to run the command in 30 seconds. exceeded 30s timeout. %s", rr.Command())
M
Medya Gh 已提交
250 251
	}

252
	if err != nil {
M
Medya Gh 已提交
253
		t.Fatalf("failed to run minikube docker-env. args %q : %v ", rr.Command(), err)
254 255 256 257
	}

	expectedImgInside := "gcr.io/k8s-minikube/storage-provisioner"
	if !strings.Contains(rr.Output(), expectedImgInside) {
M
Medya Gh 已提交
258
		t.Fatalf("expected 'docker images' to have %q inside minikube. but the output is: *%s*", expectedImgInside, rr.Output())
259 260 261
	}
}

M
lint  
Medya Gh 已提交
262
func validateStartWithProxy(ctx context.Context, t *testing.T, profile string) {
263 264
	defer PostMortemLogs(t, profile)

265 266
	srv, err := startHTTPProxy(t)
	if err != nil {
267
		t.Fatalf("failed to set up the test proxy: %s", err)
268
	}
269 270

	// Use more memory so that we may reliably fit MySQL and nginx
M
Medya Gh 已提交
271
	// changing api server so later in soft start we verify it didn't change
272
	startArgs := append([]string{"start", "-p", profile, "--memory=4000", fmt.Sprintf("--apiserver-port=%d", apiPortTest), "--wait=all"}, StartArgs()...)
273
	c := exec.CommandContext(ctx, Target(), startArgs...)
274 275 276 277 278 279
	env := os.Environ()
	env = append(env, fmt.Sprintf("HTTP_PROXY=%s", srv.Addr))
	env = append(env, "NO_PROXY=")
	c.Env = env
	rr, err := Run(t, c)
	if err != nil {
M
Medya Gh 已提交
280
		t.Errorf("failed minikube start. args %q: %v", rr.Command(), err)
281 282 283 284 285 286 287 288 289 290 291
	}

	want := "Found network options:"
	if !strings.Contains(rr.Stdout.String(), want) {
		t.Errorf("start stdout=%s, want: *%s*", rr.Stdout.String(), want)
	}

	want = "You appear to be using a proxy"
	if !strings.Contains(rr.Stderr.String(), want) {
		t.Errorf("start stderr=%s, want: *%s*", rr.Stderr.String(), want)
	}
292 293 294 295

	t.Run("Audit", func(t *testing.T) {
		got, err := auditContains(profile)
		if err != nil {
296
			t.Fatalf("failed to check audit log: %v", err)
297 298 299 300 301
		}
		if !got {
			t.Errorf("audit.json does not contain the profile %q", profile)
		}
	})
302 303
}

M
Medya Gh 已提交
304
// validateSoftStart validates that after minikube already started, a "minikube start" should not change the configs.
M
lint  
Medya Gh 已提交
305
func validateSoftStart(ctx context.Context, t *testing.T, profile string) {
306 307
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
308
	start := time.Now()
M
Medya Gh 已提交
309
	// the test before this had been start with --apiserver-port=8441
M
lint  
Medya Gh 已提交
310 311
	beforeCfg, err := config.LoadProfile(profile)
	if err != nil {
312
		t.Fatalf("error reading cluster config before soft start: %v", err)
M
lint  
Medya Gh 已提交
313
	}
M
Medya Gh 已提交
314 315
	if beforeCfg.Config.KubernetesConfig.NodePort != apiPortTest {
		t.Errorf("expected cluster config node port before soft start to be %d but got %d", apiPortTest, beforeCfg.Config.KubernetesConfig.NodePort)
M
Medya Gh 已提交
316 317
	}

M
typo  
Medya Gh 已提交
318
	softStartArgs := []string{"start", "-p", profile, "--alsologtostderr", "-v=8"}
M
Medya Gh 已提交
319
	c := exec.CommandContext(ctx, Target(), softStartArgs...)
M
lint  
Medya Gh 已提交
320
	rr, err := Run(t, c)
M
Medya Gh 已提交
321 322 323
	if err != nil {
		t.Errorf("failed to soft start minikube. args %q: %v", rr.Command(), err)
	}
M
Medya Gh 已提交
324
	t.Logf("soft start took %s for %q cluster.", time.Since(start), profile)
M
Medya Gh 已提交
325

M
lint  
Medya Gh 已提交
326 327 328 329 330
	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

M
Medya Gh 已提交
331 332
	if afterCfg.Config.KubernetesConfig.NodePort != apiPortTest {
		t.Errorf("expected node port in the config not change after soft start. exepceted node port to be %d but got %d.", apiPortTest, afterCfg.Config.KubernetesConfig.NodePort)
M
Medya Gh 已提交
333
	}
M
Medya Gh 已提交
334

335 336 337
	t.Run("Audit", func(t *testing.T) {
		got, err := auditContains(profile)
		if err != nil {
338
			t.Fatalf("failed to check audit log: %v", err)
339 340 341 342 343
		}
		if !got {
			t.Errorf("audit.json does not contain the profile %q", profile)
		}
	})
M
Medya Gh 已提交
344 345
}

346 347
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext(ctx context.Context, t *testing.T, profile string) {
348 349
	defer PostMortemLogs(t, profile)

350 351
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "config", "current-context"))
	if err != nil {
M
Medya Gh 已提交
352
		t.Errorf("failed to get current-context. args %q : %v", rr.Command(), err)
353 354
	}
	if !strings.Contains(rr.Stdout.String(), profile) {
355
		t.Errorf("expected current-context = %q, but got *%q*", profile, rr.Stdout.String())
356 357 358
	}
}

P
Priya Wadhwa 已提交
359 360
// validateKubectlGetPods asserts that `kubectl get pod -A` returns non-zero content
func validateKubectlGetPods(ctx context.Context, t *testing.T, profile string) {
361 362
	defer PostMortemLogs(t, profile)

363
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "po", "-A"))
P
Priya Wadhwa 已提交
364
	if err != nil {
M
Medya Gh 已提交
365
		t.Errorf("failed to get kubectl pods: args %q : %v", rr.Command(), err)
P
Priya Wadhwa 已提交
366
	}
367
	if rr.Stderr.String() != "" {
368
		t.Errorf("expected stderr to be empty but got *%q*: args %q", rr.Stderr, rr.Command())
369
	}
T
Thomas Stromberg 已提交
370
	if !strings.Contains(rr.Stdout.String(), "kube-system") {
371
		t.Errorf("expected stdout to include *kube-system* but got *%q*. args: %q", rr.Stdout, rr.Command())
P
Priya Wadhwa 已提交
372 373 374
	}
}

375 376
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl(ctx context.Context, t *testing.T, profile string) {
377 378
	defer PostMortemLogs(t, profile)

379 380
	// Must set the profile so that it knows what version of Kubernetes to use
	kubectlArgs := []string{"-p", profile, "kubectl", "--", "--context", profile, "get", "pods"}
P
Priya Wadhwa 已提交
381
	rr, err := Run(t, exec.CommandContext(ctx, Target(), kubectlArgs...))
382
	if err != nil {
M
Medya Gh 已提交
383
		t.Fatalf("failed to get pods. args %q: %v", rr.Command(), err)
384 385 386
	}
}

P
Pablo Caderno 已提交
387 388 389 390 391 392 393 394 395 396 397 398
// validateMinikubeKubectlDirectCall validates that calling minikube's kubectl
func validateMinikubeKubectlDirectCall(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)
	dir := filepath.Dir(Target())
	dstfn := filepath.Join(dir, "kubectl")
	err := os.Link(Target(), dstfn)

	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(dstfn) // clean up

399
	kubectlArgs := []string{"--context", profile, "get", "pods"}
P
Pablo Caderno 已提交
400 401
	rr, err := Run(t, exec.CommandContext(ctx, dstfn, kubectlArgs...))
	if err != nil {
402
		t.Fatalf("failed to run kubectl directly. args %q: %v", rr.Command(), err)
P
Pablo Caderno 已提交
403 404 405
	}
}

406 407 408 409 410
func validateExtraConfig(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	start := time.Now()
	// The tests before this already created a profile, starting minikube with different --extra-config cmdline option.
411
	startArgs := []string{"start", "-p", profile, "--extra-config=apiserver.enable-admission-plugins=NamespaceAutoProvision", "--wait=all"}
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
	c := exec.CommandContext(ctx, Target(), startArgs...)
	rr, err := Run(t, c)
	if err != nil {
		t.Errorf("failed to restart minikube. args %q: %v", rr.Command(), err)
	}
	t.Logf("restart took %s for %q cluster.", time.Since(start), profile)

	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

	expectedExtraOptions := "apiserver.enable-admission-plugins=NamespaceAutoProvision"

	if !strings.Contains(afterCfg.Config.KubernetesConfig.ExtraOptions.String(), expectedExtraOptions) {
		t.Errorf("expected ExtraOptions to contain %s but got %s", expectedExtraOptions, afterCfg.Config.KubernetesConfig.ExtraOptions.String())
	}
}

I
Ilya Zuyev 已提交
431 432 433
// imageID returns a docker image id for image `image` and current architecture
// 'image' is supposed to be one commonly used in minikube integration tests,
// like k8s 'pause'
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
func imageID(image string) string {
	ids := map[string]map[string]string{
		"pause": {
			"amd64": "0184c1613d929",
			"arm64": "3d18732f8686c",
		},
	}

	if imgIds, ok := ids[image]; ok {
		if id, ok := imgIds[runtime.GOARCH]; ok {
			return id
		}
		panic(fmt.Sprintf("unexpected architecture for image %q: %v", image, runtime.GOARCH))
	}
	panic("unexpected image name: " + image)
}

451
// validateComponentHealth asserts that all Kubernetes components are healthy
P
Predrag Rogic 已提交
452
// note: it expects all components to be Ready, so it makes sense to run it close after only those tests that include '--wait=all' start flag (ie, with extra wait)
453
func validateComponentHealth(ctx context.Context, t *testing.T, profile string) {
454 455
	defer PostMortemLogs(t, profile)

456 457 458 459 460 461 462 463 464
	// The ComponentStatus API is deprecated in v1.19, so do the next closest thing.
	found := map[string]bool{
		"etcd":                    false,
		"kube-apiserver":          false,
		"kube-controller-manager": false,
		"kube-scheduler":          false,
	}

	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "po", "-l", "tier=control-plane", "-n", "kube-system", "-o=json"))
465
	if err != nil {
M
Medya Gh 已提交
466
		t.Fatalf("failed to get components. args %q: %v", rr.Command(), err)
467
	}
468
	cs := api.PodList{}
469 470
	d := json.NewDecoder(bytes.NewReader(rr.Stdout.Bytes()))
	if err := d.Decode(&cs); err != nil {
M
Medya Gh 已提交
471
		t.Fatalf("failed to decode kubectl json output: args %q : %v", rr.Command(), err)
472 473 474
	}

	for _, i := range cs.Items {
475
		for _, l := range i.Labels {
476
			if _, ok := found[l]; ok { // skip irrelevant (eg, repeating/redundant '"tier": "control-plane"') labels
477
				found[l] = true
478 479
				t.Logf("%s phase: %s", l, i.Status.Phase)
				if i.Status.Phase != api.PodRunning {
480
					t.Errorf("%s is not Running: %+v", l, i.Status)
481 482 483 484 485 486 487 488 489 490 491
					continue
				}
				for _, c := range i.Status.Conditions {
					if c.Type == api.PodReady {
						if c.Status != api.ConditionTrue {
							t.Errorf("%s is not Ready: %+v", l, i.Status)
						} else {
							t.Logf("%s status: %s", l, c.Type)
						}
						break
					}
492
				}
493 494
			}
		}
495 496 497 498 499
	}

	for k, v := range found {
		if !v {
			t.Errorf("expected component %q was not found", k)
500 501 502 503
		}
	}
}

J
Josh Woodcock 已提交
504
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
505
	defer PostMortemLogs(t, profile)
506
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status"))
J
Josh Woodcock 已提交
507
	if err != nil {
M
Medya Gh 已提交
508
		t.Errorf("failed to run minikube status. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
509 510 511
	}

	// Custom format
512
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}"))
J
Josh Woodcock 已提交
513
	if err != nil {
M
Medya Gh 已提交
514
		t.Errorf("failed to run minikube status with custom format: args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
515
	}
516 517
	re := `host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+)`
	match, _ := regexp.MatchString(re, rr.Stdout.String())
J
Josh Woodcock 已提交
518
	if !match {
M
Medya Gh 已提交
519
		t.Errorf("failed to match regex %q for minikube status with custom format. args %q. output: %s", re, rr.Command(), rr.Output())
J
Josh Woodcock 已提交
520 521 522
	}

	// Json output
523
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-o", "json"))
J
Josh Woodcock 已提交
524
	if err != nil {
M
Medya Gh 已提交
525
		t.Errorf("failed to run minikube status with json output. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
526 527 528 529
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
M
Medya Gh 已提交
530
		t.Errorf("failed to decode json from minikube status. args %q. %v", rr.Command(), err)
J
Josh Woodcock 已提交
531 532
	}
	if _, ok := jsonObject["Host"]; !ok {
M
Medya Gh 已提交
533
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Host")
J
Josh Woodcock 已提交
534 535
	}
	if _, ok := jsonObject["Kubelet"]; !ok {
M
Medya Gh 已提交
536
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubelet")
J
Josh Woodcock 已提交
537 538
	}
	if _, ok := jsonObject["APIServer"]; !ok {
M
Medya Gh 已提交
539
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "APIServer")
J
Josh Woodcock 已提交
540 541
	}
	if _, ok := jsonObject["Kubeconfig"]; !ok {
M
Medya Gh 已提交
542
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubeconfig")
J
Josh Woodcock 已提交
543 544 545
	}
}

546 547
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
548 549
	defer PostMortemLogs(t, profile)

550 551 552
	mctx, cancel := context.WithTimeout(ctx, Seconds(300))
	defer cancel()

553
	args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}
554
	ss, err := Start(t, exec.CommandContext(mctx, Target(), args...))
555
	if err != nil {
556
		t.Errorf("failed to run minikube dashboard. args %q : %v", args, err)
557 558 559 560 561
	}
	defer func() {
		ss.Stop(t)
	}()

562
	s, err := dashboardURL(ss.Stdout)
563
	if err != nil {
T
tstromberg 已提交
564
		if runtime.GOOS == "windows" {
565
			t.Skip(err)
T
tstromberg 已提交
566
		}
567
		t.Fatal(err)
568 569 570 571 572 573 574 575 576
	}

	u, err := url.Parse(strings.TrimSpace(s))
	if err != nil {
		t.Fatalf("failed to parse %q: %v", s, err)
	}

	resp, err := retryablehttp.Get(u.String())
	if err != nil {
577
		t.Fatalf("failed to http get %q: %v\nresponse: %+v", u.String(), err, resp)
578
	}
579

580 581 582
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
583
			t.Errorf("failed to read http response body from dashboard %q: %v", u.String(), err)
584 585 586 587
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}
M
Medya Gh 已提交
588

589
// dashboardURL gets the dashboard URL from the command stdout.
590
func dashboardURL(b *bufio.Reader) (string, error) {
591
	// match http://127.0.0.1:XXXXX/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/
592
	dashURLRegexp := regexp.MustCompile(`^http:\/\/127\.0\.0\.1:[0-9]{5}\/api\/v1\/namespaces\/kubernetes-dashboard\/services\/http:kubernetes-dashboard:\/proxy\/$`)
593

S
Steven Powell 已提交
594 595 596 597 598
	s := bufio.NewScanner(b)
	for s.Scan() {
		t := s.Text()
		if dashURLRegexp.MatchString(t) {
			return t, nil
599 600
		}
	}
601 602 603
	if err := s.Err(); err != nil {
		return "", fmt.Errorf("failed reading input: %v", err)
	}
604 605 606
	return "", fmt.Errorf("output didn't produce a URL")
}

T
Thomas Stromberg 已提交
607 608
// validateDryRun asserts that the dry-run mode quickly exits with the right code
func validateDryRun(ctx context.Context, t *testing.T, profile string) {
609
	// dry-run mode should always be able to finish quickly (<5s)
M
Medya Gh 已提交
610
	mctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
611 612 613
	defer cancel()

	// Too little memory!
614
	startArgs := append([]string{"start", "-p", profile, "--dry-run", "--memory", "250MB", "--alsologtostderr"}, StartArgs()...)
T
Thomas Stromberg 已提交
615 616 617
	c := exec.CommandContext(mctx, Target(), startArgs...)
	rr, err := Run(t, c)

618
	wantCode := reason.ExInsufficientMemory
T
Thomas Stromberg 已提交
619 620 621 622
	if rr.ExitCode != wantCode {
		t.Errorf("dry-run(250MB) exit code = %d, wanted = %d: %v", rr.ExitCode, wantCode, err)
	}

M
Medya Gh 已提交
623
	dctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
624
	defer cancel()
625
	startArgs = append([]string{"start", "-p", profile, "--dry-run", "--alsologtostderr", "-v=1"}, StartArgs()...)
T
Thomas Stromberg 已提交
626 627 628 629 630 631 632
	c = exec.CommandContext(dctx, Target(), startArgs...)
	rr, err = Run(t, c)
	if rr.ExitCode != 0 || err != nil {
		t.Errorf("dry-run exit code = %d, wanted = %d: %v", rr.ExitCode, 0, err)
	}
}

M
Medya Gh 已提交
633
// validateCacheCmd tests functionality of cache command (cache add, delete, list)
634
func validateCacheCmd(ctx context.Context, t *testing.T, profile string) {
635 636
	defer PostMortemLogs(t, profile)

637 638 639
	if NoneDriver() {
		t.Skipf("skipping: cache unsupported by none")
	}
640

M
Medya Gh 已提交
641
	t.Run("cache", func(t *testing.T) {
M
Medya Gh 已提交
642
		t.Run("add_remote", func(t *testing.T) {
S
Sharif Elgamal 已提交
643
			for _, img := range []string{"k8s.gcr.io/pause:3.1", "k8s.gcr.io/pause:3.3", "k8s.gcr.io/pause:latest"} {
644
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
M
Medya Gh 已提交
645
				if err != nil {
M
Medya Gh 已提交
646
					t.Errorf("failed to 'cache add' remote image %q. args %q err %v", img, rr.Command(), err)
M
Medya Gh 已提交
647 648 649
				}
			}
		})
650 651

		t.Run("add_local", func(t *testing.T) {
652 653
			if GithubActionRunner() && runtime.GOOS == "darwin" {
				t.Skipf("skipping this test because Docker can not run in macos on github action free version. https://github.community/t/is-it-possible-to-install-and-configure-docker-on-macos-runner/16981")
S
Sharif Elgamal 已提交
654
			}
655

S
Sharif Elgamal 已提交
656 657 658
			_, err := exec.LookPath(oci.Docker)
			if err != nil {
				t.Skipf("docker is not installed, skipping local image test")
659 660
			}

661 662 663 664 665 666 667 668
			dname, err := ioutil.TempDir("", profile)
			if err != nil {
				t.Fatalf("Cannot create temp dir: %v", err)
			}

			message := []byte("FROM scratch\nADD Dockerfile /x")
			err = ioutil.WriteFile(filepath.Join(dname, "Dockerfile"), message, 0644)
			if err != nil {
M
Medya Gh 已提交
669
				t.Fatalf("unable to write Dockerfile: %v", err)
670 671 672
			}

			img := "minikube-local-cache-test:" + profile
T
Thomas Stromberg 已提交
673
			_, err = Run(t, exec.CommandContext(ctx, "docker", "build", "-t", img, dname))
674
			if err != nil {
S
Sharif Elgamal 已提交
675
				t.Skipf("failed to build docker image, skipping local test: %v", err)
676 677
			}

T
Thomas Stromberg 已提交
678
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
679
			if err != nil {
M
Medya Gh 已提交
680
				t.Errorf("failed to 'cache add' local image %q. args %q err %v", img, rr.Command(), err)
681 682 683
			}
		})

M
Medya Gh 已提交
684 685
		t.Run("delete_k8s.gcr.io/pause:3.3", func(t *testing.T) {
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", "k8s.gcr.io/pause:3.3"))
M
Medya Gh 已提交
686
			if err != nil {
M
Medya Gh 已提交
687
				t.Errorf("failed to delete image k8s.gcr.io/pause:3.3 from cache. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
688 689
			}
		})
M
Medya Gh 已提交
690

M
Medya Gh 已提交
691 692 693
		t.Run("list", func(t *testing.T) {
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "list"))
			if err != nil {
M
Medya Gh 已提交
694
				t.Errorf("failed to do cache list. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
695 696
			}
			if !strings.Contains(rr.Output(), "k8s.gcr.io/pause") {
M
Medya Gh 已提交
697
				t.Errorf("expected 'cache list' output to include 'k8s.gcr.io/pause' but got: ***%s***", rr.Output())
M
Medya Gh 已提交
698
			}
M
Medya Gh 已提交
699 700
			if strings.Contains(rr.Output(), "k8s.gcr.io/pause:3.3") {
				t.Errorf("expected 'cache list' output not to include k8s.gcr.io/pause:3.3 but got: ***%s***", rr.Output())
M
Medya Gh 已提交
701 702
			}
		})
M
Medya Gh 已提交
703

M
Medya Ghazizadeh 已提交
704
		t.Run("verify_cache_inside_node", func(t *testing.T) {
M
Medya Gh 已提交
705
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "images"))
M
Medya Gh 已提交
706
			if err != nil {
M
Medya Gh 已提交
707
				t.Errorf("failed to get images by %q ssh %v", rr.Command(), err)
M
Medya Gh 已提交
708
			}
709
			pauseID := imageID("pause")
I
Ilya Zuyev 已提交
710
			if !strings.Contains(rr.Output(), pauseID) {
711
				t.Errorf("expected sha for pause:3.3 %q to be in the output but got *%s*", pauseID, rr.Output())
M
Medya Gh 已提交
712 713
			}
		})
M
Medya Gh 已提交
714

M
Medya Ghazizadeh 已提交
715
		t.Run("cache_reload", func(t *testing.T) { // deleting image inside minikube node manually and expecting reload to bring it back
M
Medya Gh 已提交
716
			img := "k8s.gcr.io/pause:latest"
M
Medya Gh 已提交
717
			// deleting image inside minikube node manually
718 719 720 721 722 723 724 725 726 727

			var binary string
			switch ContainerRuntime() {
			case "docker":
				binary = "docker"
			case "containerd", "crio":
				binary = "crictl"
			}

			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", binary, "rmi", img))
M
Medya Gh 已提交
728

M
Medya Gh 已提交
729
			if err != nil {
M
spell  
Medya Gh 已提交
730
				t.Errorf("failed to manually delete image %q : %v", rr.Command(), err)
M
Medya Gh 已提交
731 732 733 734
			}
			// make sure the image is deleted.
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err == nil {
M
Medya Gh 已提交
735
				t.Errorf("expected an error  but got no error. image should not exist. ! cmd: %q", rr.Command())
M
Medya Gh 已提交
736
			}
M
Medya Gh 已提交
737
			// minikube cache reload.
M
Medya Gh 已提交
738 739
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "reload"))
			if err != nil {
740
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
741
			}
M
Medya Gh 已提交
742
			// make sure 'cache reload' brought back the manually deleted image.
M
Medya Gh 已提交
743 744
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err != nil {
745
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
746 747 748
			}
		})

M
Medya Gh 已提交
749 750
		// delete will clean up the cached images since they are global and all other tests will load it for no reason
		t.Run("delete", func(t *testing.T) {
S
Sharif Elgamal 已提交
751
			for _, img := range []string{"k8s.gcr.io/pause:3.1", "k8s.gcr.io/pause:latest"} {
M
Medya Gh 已提交
752 753 754 755 756 757
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", img))
				if err != nil {
					t.Errorf("failed to delete %s from cache. args %q: %v", img, rr.Command(), err)
				}
			}
		})
M
Medya Gh 已提交
758
	})
759 760 761 762 763 764 765 766 767 768 769
}

// validateConfigCmd asserts basic "config" command functionality
func validateConfigCmd(ctx context.Context, t *testing.T, profile string) {
	tests := []struct {
		args    []string
		wantOut string
		wantErr string
	}{
		{[]string{"unset", "cpus"}, "", ""},
		{[]string{"get", "cpus"}, "", "Error: specified key could not be found in config"},
T
Thomas Stromberg 已提交
770
		{[]string{"set", "cpus", "2"}, "", "! These changes will take effect upon a minikube delete and then a minikube start"},
771 772 773 774 775 776 777 778 779
		{[]string{"get", "cpus"}, "2", ""},
		{[]string{"unset", "cpus"}, "", ""},
		{[]string{"get", "cpus"}, "", "Error: specified key could not be found in config"},
	}

	for _, tc := range tests {
		args := append([]string{"-p", profile, "config"}, tc.args...)
		rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
		if err != nil && tc.wantErr == "" {
M
Medya Gh 已提交
780
			t.Errorf("failed to config minikube. args %q : %v", rr.Command(), err)
781 782 783 784
		}

		got := strings.TrimSpace(rr.Stdout.String())
		if got != tc.wantOut {
785
			t.Errorf("expected config output for %q to be -%q- but got *%q*", rr.Command(), tc.wantOut, got)
786 787 788
		}
		got = strings.TrimSpace(rr.Stderr.String())
		if got != tc.wantErr {
789
			t.Errorf("expected config error for %q to be -%q- but got *%q*", rr.Command(), tc.wantErr, got)
790 791 792 793 794 795 796 797
		}
	}
}

// validateLogsCmd asserts basic "logs" command functionality
func validateLogsCmd(ctx context.Context, t *testing.T, profile string) {
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "logs"))
	if err != nil {
M
Medya Gh 已提交
798
		t.Errorf("%s failed: %v", rr.Command(), err)
799
	}
S
Steven Powell 已提交
800
	expectedWords := []string{"apiserver", "Linux", "kubelet", "Audit"}
801 802 803 804 805 806 807 808 809 810
	switch ContainerRuntime() {
	case "docker":
		expectedWords = append(expectedWords, "Docker")
	case "containerd":
		expectedWords = append(expectedWords, "containerd")
	case "crio":
		expectedWords = append(expectedWords, "crio")
	}

	for _, word := range expectedWords {
811
		if !strings.Contains(rr.Stdout.String(), word) {
812
			t.Errorf("expected minikube logs to include word: -%q- but got \n***%s***\n", word, rr.Output())
813 814 815 816
		}
	}
}

J
Josh Woodcock 已提交
817
// validateProfileCmd asserts "profile" command functionality
818
func validateProfileCmd(ctx context.Context, t *testing.T, profile string) {
M
Medya Gh 已提交
819 820 821 822 823
	t.Run("profile_not_create", func(t *testing.T) {
		// Profile command should not create a nonexistent profile
		nonexistentProfile := "lis"
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", nonexistentProfile))
		if err != nil {
M
Medya Gh 已提交
824
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
825 826 827
		}
		rr, err = Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
		if err != nil {
M
Medya Gh 已提交
828
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
829 830 831 832
		}
		var profileJSON map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &profileJSON)
		if err != nil {
M
Medya Gh 已提交
833
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
834 835 836 837 838 839 840
		}
		for profileK := range profileJSON {
			for _, p := range profileJSON[profileK] {
				var name = p["Name"]
				if name == nonexistentProfile {
					t.Errorf("minikube profile %s should not exist", nonexistentProfile)
				}
841
			}
842
		}
M
Medya Gh 已提交
843
	})
844

M
Medya Gh 已提交
845
	t.Run("profile_list", func(t *testing.T) {
846 847 848 849 850 851 852 853 854 855 856 857
		// helper function to run command then, return target profile line from table output.
		extractrofileListFunc := func(rr *RunResult) string {
			listLines := strings.Split(strings.TrimSpace(rr.Stdout.String()), "\n")
			for i := 3; i < (len(listLines) - 1); i++ {
				profileLine := listLines[i]
				if strings.Contains(profileLine, profile) {
					return profileLine
				}
			}
			return ""
		}

M
Medya Gh 已提交
858
		// List profiles
859
		start := time.Now()
M
lint  
Medya Gh 已提交
860
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list"))
861
		elapsed := time.Since(start)
M
Medya Gh 已提交
862
		if err != nil {
M
Medya Gh 已提交
863
			t.Errorf("failed to list profiles: args %q : %v", rr.Command(), err)
M
Medya Gh 已提交
864
		}
865
		t.Logf("Took %q to run %q", elapsed, rr.Command())
J
Josh Woodcock 已提交
866

867 868
		profileLine := extractrofileListFunc(rr)
		if profileLine == "" {
M
Medya Gh 已提交
869
			t.Errorf("expected 'profile list' output to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
J
Josh Woodcock 已提交
870
		}
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888

		// List profiles with light option.
		start = time.Now()
		lrr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "-l"))
		lightElapsed := time.Since(start)
		if err != nil {
			t.Errorf("failed to list profiles: args %q : %v", lrr.Command(), err)
		}
		t.Logf("Took %q to run %q", lightElapsed, lrr.Command())

		profileLine = extractrofileListFunc(lrr)
		if profileLine == "" || !strings.Contains(profileLine, "Skipped") {
			t.Errorf("expected 'profile list' output to include %q with 'Skipped' status but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
		}

		if lightElapsed > 3*time.Second {
			t.Errorf("expected running time of '%q' is less than 3 seconds. Took %q ", lrr.Command(), lightElapsed)
		}
M
Medya Gh 已提交
889 890 891
	})

	t.Run("profile_json_output", func(t *testing.T) {
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
		// helper function to run command then, return target profile object from json output.
		extractProfileObjFunc := func(rr *RunResult) *config.Profile {
			var jsonObject map[string][]config.Profile
			err := json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
			if err != nil {
				t.Errorf("failed to decode json from profile list: args %q: %v", rr.Command(), err)
				return nil
			}

			for _, profileObject := range jsonObject["valid"] {
				if profileObject.Name == profile {
					return &profileObject
				}
			}
			return nil
		}

		start := time.Now()
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "-o", "json"))
		elapsed := time.Since(start)
M
Medya Gh 已提交
912
		if err != nil {
M
Medya Gh 已提交
913
			t.Errorf("failed to list profiles with json format. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
914
		}
915 916 917 918 919 920 921 922 923 924
		t.Logf("Took %q to run %q", elapsed, rr.Command())

		pr := extractProfileObjFunc(rr)
		if pr == nil {
			t.Errorf("expected the json of 'profile list' to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
		}

		start = time.Now()
		lrr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "-o", "json", "--light"))
		lightElapsed := time.Since(start)
M
Medya Gh 已提交
925
		if err != nil {
926
			t.Errorf("failed to list profiles with json format. args %q: %v", lrr.Command(), err)
M
Medya Gh 已提交
927
		}
928 929 930 931 932
		t.Logf("Took %q to run %q", lightElapsed, lrr.Command())

		pr = extractProfileObjFunc(lrr)
		if pr == nil || pr.Status != "Skipped" {
			t.Errorf("expected the json of 'profile list' to include 'Skipped' status for %q but got *%q*. args: %q", profile, lrr.Stdout.String(), lrr.Command())
M
Medya Gh 已提交
933 934
		}

935 936 937
		if lightElapsed > 3*time.Second {
			t.Errorf("expected running time of '%q' is less than 3 seconds. Took %q ", lrr.Command(), lightElapsed)
		}
M
Medya Gh 已提交
938
	})
939 940 941
}

// validateServiceCmd asserts basic "service" command functionality
942
func validateServiceCmd(ctx context.Context, t *testing.T, profile string) {
943 944
	defer PostMortemLogs(t, profile)

945 946 947
	defer func() {
		if t.Failed() {
			t.Logf("service test failed - dumping debug information")
948 949 950
			t.Logf("-----------------------service failure post-mortem--------------------------------")
			ctx, cancel := context.WithTimeout(context.Background(), Minutes(2))
			defer cancel()
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
			rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "po", "hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node pod describe:\n%s", rr.Stdout)

			rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "logs", "-l", "app=hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node logs:\n%s", rr.Stdout)

			rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "svc", "hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node svc describe:\n%s", rr.Stdout)
		}
	}()

971 972 973
	var rr *RunResult
	var err error
	// k8s.gcr.io/echoserver is not multi-arch
I
Ilya Zuyev 已提交
974
	if arm64Platform() {
975
		rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "deployment", "hello-node", "--image=k8s.gcr.io/echoserver-arm:1.8"))
I
Ilya Zuyev 已提交
976
	} else {
977
		rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "deployment", "hello-node", "--image=k8s.gcr.io/echoserver:1.8"))
I
Ilya Zuyev 已提交
978 979
	}

980
	if err != nil {
981
		t.Fatalf("failed to create hello-node deployment with this command %q: %v.", rr.Command(), err)
982
	}
983
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "expose", "deployment", "hello-node", "--type=NodePort", "--port=8080"))
984
	if err != nil {
985
		t.Fatalf("failed to expose hello-node deployment: %q : %v", rr.Command(), err)
986 987
	}

988
	if _, err := PodWait(ctx, t, profile, "default", "app=hello-node", Minutes(10)); err != nil {
989
		t.Fatalf("failed waiting for hello-node pod: %v", err)
990 991
	}

992
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "list"))
993
	if err != nil {
M
Medya Gh 已提交
994
		t.Errorf("failed to do service list. args %q : %v", rr.Command(), err)
995
	}
996
	if !strings.Contains(rr.Stdout.String(), "hello-node") {
997
		t.Errorf("expected 'service list' to contain *hello-node* but got -%q-", rr.Stdout.String())
998 999
	}

1000 1001 1002 1003
	if NeedsPortForward() {
		t.Skipf("test is broken for port-forwarded drivers: https://github.com/kubernetes/minikube/issues/7383")
	}

T
Thomas Stromberg 已提交
1004
	// Test --https --url mode
1005
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "--namespace=default", "--https", "--url", "hello-node"))
1006
	if err != nil {
M
Medya Gh 已提交
1007
		t.Fatalf("failed to get service url. args %q : %v", rr.Command(), err)
1008 1009
	}
	if rr.Stderr.String() != "" {
M
Medya Gh 已提交
1010
		t.Errorf("expected stderr to be empty but got *%q* . args %q", rr.Stderr, rr.Command())
1011
	}
1012

T
Thomas Stromberg 已提交
1013
	endpoint := strings.TrimSpace(rr.Stdout.String())
1014
	t.Logf("found endpoint: %s", endpoint)
T
Thomas Stromberg 已提交
1015

1016 1017
	u, err := url.Parse(endpoint)
	if err != nil {
1018
		t.Fatalf("failed to parse service url endpoint %q: %v", endpoint, err)
1019 1020
	}
	if u.Scheme != "https" {
1021
		t.Errorf("expected scheme for %s to be 'https' but got %q", endpoint, u.Scheme)
1022 1023 1024
	}

	// Test --format=IP
1025
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url", "--format={{.IP}}"))
1026
	if err != nil {
M
Medya Gh 已提交
1027
		t.Errorf("failed to get service url with custom format. args %q: %v", rr.Command(), err)
1028
	}
1029
	if strings.TrimSpace(rr.Stdout.String()) != u.Hostname() {
M
Medya Gh 已提交
1030
		t.Errorf("expected 'service --format={{.IP}}' output to be -%q- but got *%q* . args %q.", u.Hostname(), rr.Stdout.String(), rr.Command())
1031 1032
	}

1033
	// Test a regular URL
1034
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url"))
1035
	if err != nil {
M
Medya Gh 已提交
1036
		t.Errorf("failed to get service url. args: %q: %v", rr.Command(), err)
1037
	}
1038 1039

	endpoint = strings.TrimSpace(rr.Stdout.String())
1040 1041
	t.Logf("found endpoint for hello-node: %s", endpoint)

1042 1043 1044 1045
	u, err = url.Parse(endpoint)
	if err != nil {
		t.Fatalf("failed to parse %q: %v", endpoint, err)
	}
1046

1047
	if u.Scheme != "http" {
M
lint  
Medya Gh 已提交
1048
		t.Fatalf("expected scheme to be -%q- got scheme: *%q*", "http", u.Scheme)
1049 1050
	}

1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
	t.Logf("Attempting to fetch %s ...", endpoint)

	fetch := func() error {
		resp, err := http.Get(endpoint)
		if err != nil {
			t.Logf("error fetching %s: %v", endpoint, err)
			return err
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Logf("error reading body from %s: %v", endpoint, err)
			return err
		}
		if resp.StatusCode != http.StatusOK {
			t.Logf("%s: unexpected status code %d - body:\n%s", endpoint, resp.StatusCode, body)
		} else {
			t.Logf("%s: success! body:\n%s", endpoint, body)
		}
		return nil
1073
	}
1074 1075 1076

	if err = retry.Expo(fetch, 1*time.Second, Seconds(30)); err != nil {
		t.Errorf("failed to fetch %s: %v", endpoint, err)
1077 1078 1079
	}
}

1080 1081
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd(ctx context.Context, t *testing.T, profile string) {
1082 1083
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
1084
	// Table output
1085 1086
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list"))
	if err != nil {
M
Medya Gh 已提交
1087
		t.Errorf("failed to do addon list: args %q : %v", rr.Command(), err)
1088
	}
M
Medya Gh 已提交
1089 1090
	for _, a := range []string{"dashboard", "ingress", "ingress-dns"} {
		if !strings.Contains(rr.Output(), a) {
M
Medya Gh 已提交
1091
			t.Errorf("expected 'addon list' output to include -%q- but got *%s*", a, rr.Output())
1092 1093
		}
	}
J
Josh Woodcock 已提交
1094 1095 1096 1097

	// Json output
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list", "-o", "json"))
	if err != nil {
M
Medya Gh 已提交
1098
		t.Errorf("failed to do addon list with json output. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
1099 1100 1101 1102
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
1103
		t.Errorf("failed to decode addon list json output : %v", err)
J
Josh Woodcock 已提交
1104
	}
1105 1106
}

1107 1108
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd(ctx context.Context, t *testing.T, profile string) {
1109
	defer PostMortemLogs(t, profile)
1110 1111 1112
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
M
Medya Gh 已提交
1113 1114
	mctx, cancel := context.WithTimeout(ctx, Minutes(1))
	defer cancel()
M
Medya Gh 已提交
1115

S
Sharif Elgamal 已提交
1116
	want := "hello"
M
Medya Gh 已提交
1117

M
Medya Gh 已提交
1118 1119
	rr, err := Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "echo hello"))
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
1120 1121 1122 1123 1124
		t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
	}
	if err != nil {
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
	}
1125
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
1126
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
try pwd  
Medya Gh 已提交
1127 1128 1129
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
	}

M
Medya Gh 已提交
1130 1131
	// testing hostname as well because testing something like "minikube ssh echo" could be confusing
	// because it  is not clear if echo was run inside minikube on the powershell
M
Medya Gh 已提交
1132
	// so better to test something inside minikube, that is meaningful per profile
M
Medya Gh 已提交
1133
	// in this case /etc/hostname is same as the profile name
1134
	want = profile
M
Medya Gh 已提交
1135
	rr, err = Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "cat /etc/hostname"))
M
Medya Gh 已提交
1136
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
1137
		t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
M
Medya Gh 已提交
1138 1139
	}

1140
	if err != nil {
M
Medya Gh 已提交
1141
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
1142
	}
S
Sharif Elgamal 已提交
1143
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
1144
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
Medya Gh 已提交
1145
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
1146 1147 1148
	}
}

1149 1150
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL(ctx context.Context, t *testing.T, profile string) {
I
Ilya Zuyev 已提交
1151
	if arm64Platform() {
1152
		t.Skip("arm64 is not supported by mysql. Skip the test. See https://github.com/kubernetes/minikube/issues/10144")
I
Ilya Zuyev 已提交
1153 1154
	}

1155 1156
	defer PostMortemLogs(t, profile)

1157 1158
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "replace", "--force", "-f", filepath.Join(*testdataDir, "mysql.yaml")))
	if err != nil {
M
Medya Gh 已提交
1159
		t.Fatalf("failed to kubectl replace mysql: args %q failed: %v", rr.Command(), err)
1160 1161
	}

1162
	names, err := PodWait(ctx, t, profile, "default", "app=mysql", Minutes(10))
1163
	if err != nil {
1164
		t.Fatalf("failed waiting for mysql pod: %v", err)
1165 1166
	}

1167 1168 1169 1170
	// Retry, as mysqld first comes up without users configured. Scan for names in case of a reschedule.
	mysql := func() error {
		rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "exec", names[0], "--", "mysql", "-ppassword", "-e", "show databases;"))
		return err
1171
	}
M
Medya Gh 已提交
1172
	if err = retry.Expo(mysql, 1*time.Second, Minutes(5)); err != nil {
1173
		t.Errorf("failed to exec 'mysql -ppassword -e show databases;': %v", err)
1174 1175 1176
	}
}

1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
// vmSyncTestPath is where the test file will be synced into the VM
func vmSyncTestPath() string {
	return fmt.Sprintf("/etc/test/nested/copy/%d/hosts", os.Getpid())
}

// localSyncTestPath is where the test file will be synced into the VM
func localSyncTestPath() string {
	return filepath.Join(localpath.MiniPath(), "/files", vmSyncTestPath())
}

1187 1188 1189 1190 1191 1192 1193 1194 1195 1196
// testCert is name of the test certificate installed
func testCert() string {
	return fmt.Sprintf("%d.pem", os.Getpid())
}

// localTestCertPath is where the test file will be synced into the VM
func localTestCertPath() string {
	return filepath.Join(localpath.MiniPath(), "/certs", testCert())
}

1197 1198 1199 1200 1201
// localEmptyCertPath is where the test file will be synced into the VM
func localEmptyCertPath() string {
	return filepath.Join(localpath.MiniPath(), "/certs", fmt.Sprintf("%d_empty.pem", os.Getpid()))
}

1202 1203
// Copy extra file into minikube home folder for file sync test
func setupFileSync(ctx context.Context, t *testing.T, profile string) {
1204 1205
	p := localSyncTestPath()
	t.Logf("local sync path: %s", p)
1206 1207
	syncFile := filepath.Join(*testdataDir, "sync.test")
	err := copy.Copy(syncFile, p)
1208
	if err != nil {
1209
		t.Fatalf("failed to copy testdata/sync.test: %v", err)
1210
	}
1211

1212
	testPem := filepath.Join(*testdataDir, "minikube_test.pem")
1213

1214 1215 1216
	// Write to a temp file for an atomic write
	tmpPem := localTestCertPath() + ".pem"
	if err := copy.Copy(testPem, tmpPem); err != nil {
1217 1218 1219
		t.Fatalf("failed to copy %s: %v", testPem, err)
	}

1220 1221 1222 1223
	if err := os.Rename(tmpPem, localTestCertPath()); err != nil {
		t.Fatalf("failed to rename %s: %v", tmpPem, err)
	}

1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
	want, err := os.Stat(testPem)
	if err != nil {
		t.Fatalf("stat failed: %v", err)
	}

	got, err := os.Stat(localTestCertPath())
	if err != nil {
		t.Fatalf("stat failed: %v", err)
	}

	if want.Size() != got.Size() {
		t.Errorf("%s size=%d, want %d", localTestCertPath(), got.Size(), want.Size())
	}

	// Create an empty file just to mess with people
	if _, err := os.Create(localEmptyCertPath()); err != nil {
		t.Fatalf("create failed: %v", err)
1241
	}
1242 1243 1244 1245
}

// validateFileSync to check existence of the test file
func validateFileSync(ctx context.Context, t *testing.T, profile string) {
1246 1247
	defer PostMortemLogs(t, profile)

1248 1249 1250
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
1251 1252 1253

	vp := vmSyncTestPath()
	t.Logf("Checking for existence of %s within VM", vp)
1254
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
1255
	if err != nil {
M
Medya Gh 已提交
1256
		t.Errorf("%s failed: %v", rr.Command(), err)
1257
	}
1258 1259
	got := rr.Stdout.String()
	t.Logf("file sync test content: %s", got)
1260

1261 1262
	syncFile := filepath.Join(*testdataDir, "sync.test")
	expected, err := ioutil.ReadFile(syncFile)
1263
	if err != nil {
1264
		t.Errorf("failed to read test file 'testdata/sync.test' : %v", err)
1265 1266
	}

1267
	if diff := cmp.Diff(string(expected), got); diff != "" {
1268 1269 1270 1271
		t.Errorf("/etc/sync.test content mismatch (-want +got):\n%s", diff)
	}
}

1272 1273
// validateCertSync to check existence of the test certificate
func validateCertSync(ctx context.Context, t *testing.T, profile string) {
1274 1275
	defer PostMortemLogs(t, profile)

1276 1277 1278 1279
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}

1280 1281
	testPem := filepath.Join(*testdataDir, "minikube_test.pem")
	want, err := ioutil.ReadFile(testPem)
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294
	if err != nil {
		t.Errorf("test file not found: %v", err)
	}

	// Check both the installed & reference certs (they should be symlinked)
	paths := []string{
		path.Join("/etc/ssl/certs", testCert()),
		path.Join("/usr/share/ca-certificates", testCert()),
		// hashed path generated by: 'openssl x509 -hash -noout -in testCert()'
		"/etc/ssl/certs/51391683.0",
	}
	for _, vp := range paths {
		t.Logf("Checking for existence of %s within VM", vp)
1295
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
1296
		if err != nil {
M
Medya Gh 已提交
1297
			t.Errorf("failed to check existence of %q inside minikube. args %q: %v", vp, rr.Command(), err)
1298 1299 1300 1301 1302
		}

		// Strip carriage returned by ssh
		got := strings.Replace(rr.Stdout.String(), "\r", "", -1)
		if diff := cmp.Diff(string(want), got); diff != "" {
1303
			t.Errorf("failed verify pem file. minikube_test.pem -> %s mismatch (-want +got):\n%s", vp, diff)
1304 1305 1306 1307
		}
	}
}

1308 1309
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd(ctx context.Context, t *testing.T, profile string) {
1310 1311
	defer PostMortemLogs(t, profile)

K
Kazuki Suda 已提交
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
	tests := []struct {
		name       string
		kubeconfig []byte
		want       []byte
	}{
		{
			name:       "no changes",
			kubeconfig: nil,
			want:       []byte("No changes"),
		},
		{
			name: "no minikube cluster",
			kubeconfig: []byte(`
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/la-croix/apiserver.crt
    server: 192.168.1.1:8080
  name: la-croix
contexts:
- context:
    cluster: la-croix
    user: la-croix
  name: la-croix
current-context: la-croix
kind: Config
preferences: {}
users:
- name: la-croix
  user:
    client-certificate: /home/la-croix/apiserver.crt
    client-key: /home/la-croix/apiserver.key
`),
			want: []byte("context has been updated"),
		},
		{
			name: "no clusters",
			kubeconfig: []byte(`
apiVersion: v1
clusters:
contexts:
kind: Config
preferences: {}
users:
`),
			want: []byte("context has been updated"),
		},
1359 1360
	}

K
Kazuki Suda 已提交
1361 1362
	for _, tc := range tests {
		tc := tc
1363 1364 1365 1366 1367

		if ctx.Err() == context.DeadlineExceeded {
			t.Fatalf("Unable to run more tests (deadline exceeded)")
		}

K
Kazuki Suda 已提交
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			c := exec.CommandContext(ctx, Target(), "-p", profile, "update-context", "--alsologtostderr", "-v=2")
			if tc.kubeconfig != nil {
				tf, err := ioutil.TempFile("", "kubeconfig")
				if err != nil {
					t.Fatal(err)
				}

				if err := ioutil.WriteFile(tf.Name(), tc.kubeconfig, 0644); err != nil {
					t.Fatal(err)
				}

				t.Cleanup(func() {
					os.Remove(tf.Name())
				})

				c.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", tf.Name()))
			}

			rr, err := Run(t, c)
			if err != nil {
				t.Errorf("failed to run minikube update-context: args %q: %v", rr.Command(), err)
			}

			if !bytes.Contains(rr.Stdout.Bytes(), tc.want) {
				t.Errorf("update-context: got=%q, want=*%q*", rr.Stdout.Bytes(), tc.want)
			}
		})
1397 1398 1399
	}
}

1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415
// startHTTPProxy runs a local http proxy and sets the env vars for it.
func startHTTPProxy(t *testing.T) (*http.Server, error) {
	port, err := freeport.GetFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "Failed to get an open port")
	}

	addr := fmt.Sprintf("localhost:%d", port)
	proxy := goproxy.NewProxyHttpServer()
	srv := &http.Server{Addr: addr, Handler: proxy}
	go func(s *http.Server, t *testing.T) {
		if err := s.ListenAndServe(); err != http.ErrServerClosed {
			t.Errorf("Failed to start http server for proxy mock")
		}
	}(srv, t)
	return srv, nil
1416
}