functional_test.go 35.5 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 23 24 25 26 27 28 29 30
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
31
	"path"
32
	"path/filepath"
33
	"regexp"
T
tstromberg 已提交
34
	"runtime"
35
	"strings"
36
	"testing"
37 38
	"time"

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

M
Medya Gh 已提交
41
	"k8s.io/minikube/pkg/minikube/config"
42
	"k8s.io/minikube/pkg/minikube/localpath"
M
lint  
Medya Gh 已提交
43
	"k8s.io/minikube/pkg/util/retry"
44

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

53 54 55
// validateFunc are for subtests that share a single setup
type validateFunc func(context.Context, *testing.T, string)

M
Medya Gh 已提交
56 57 58
// used in validateStartWithProxy and validateSoftStart
var apiPortTest = 8441

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

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

73
		Cleanup(t, profile, cancel)
74
	}()
75 76 77 78 79 80 81

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
82 83
			{"CopySyncFile", setupFileSync},                 // Set file for the file sync test case
			{"StartWithProxy", validateStartWithProxy},      // Set everything else up for success
M
Medya Gh 已提交
84
			{"SoftStart", validateSoftStart},                // do a soft start. ensure config didnt change.
85 86 87 88
			{"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
89 90 91 92
		}
		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
M
lint  
Medya Gh 已提交
93
				tc.validator(ctx, t, profile)
94
			})
M
Medya Gh 已提交
95
		}
96
	})
M
Medya Gh 已提交
97

98 99 100 101 102 103 104 105 106 107 108 109 110
	// Now that we are out of the woods, lets go.
	MaybeParallel(t)

	// Parallelized tests
	t.Run("parallel", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
			{"ComponentHealth", validateComponentHealth},
			{"ConfigCmd", validateConfigCmd},
			{"DashboardCmd", validateDashboardCmd},
			{"DNS", validateDNS},
T
Thomas Stromberg 已提交
111
			{"DryRun", validateDryRun},
J
Josh Woodcock 已提交
112
			{"StatusCmd", validateStatusCmd},
113 114 115
			{"LogsCmd", validateLogsCmd},
			{"MountCmd", validateMountCmd},
			{"ProfileCmd", validateProfileCmd},
116
			{"ServiceCmd", validateServiceCmd},
117
			{"AddonsCmd", validateAddonsCmd},
118 119 120
			{"PersistentVolumeClaim", validatePersistentVolumeClaim},
			{"TunnelCmd", validateTunnelCmd},
			{"SSHCmd", validateSSHCmd},
121
			{"MySQL", validateMySQL},
122
			{"FileSync", validateFileSync},
123
			{"CertSync", validateCertSync},
124
			{"UpdateContextCmd", validateUpdateContextCmd},
125
			{"DockerEnv", validateDockerEnv},
M
Medya Gh 已提交
126
			{"NodeLabels", validateNodeLabels},
127 128 129 130 131 132 133 134
		}
		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				MaybeParallel(t)
				tc.validator(ctx, t, profile)
			})
		}
M
Medya Gh 已提交
135
	})
136 137
}

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

M
Medya Gh 已提交
142
	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 已提交
143
	if err != nil {
M
Medya Gh 已提交
144
		t.Errorf("failed to 'kubectl get nodes' with args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
145 146 147
	}
	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 已提交
148
		if !strings.Contains(rr.Output(), el) {
M
Medya Gh 已提交
149
			t.Errorf("expected to have label %q in node labels but got : %s", el, rr.Output())
M
Medya Gh 已提交
150 151 152 153
		}
	}
}

154 155
// check functionality of minikube after evaling docker-env
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {
156 157
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
158
	mctx, cancel := context.WithTimeout(ctx, Seconds(13))
159 160 161 162 163
	defer cancel()
	// we should be able to get minikube status with a bash which evaled docker-env
	c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && "+Target()+" status -p "+profile)
	rr, err := Run(t, c)
	if err != nil {
164
		t.Fatalf("failed to do minikube status after eval-ing docker-env %s", err)
165 166
	}
	if !strings.Contains(rr.Output(), "Running") {
M
Medya Gh 已提交
167
		t.Fatalf("expected status output to include 'Running' after eval docker-env but got: *%s*", rr.Output())
168 169
	}

M
Medya Gh 已提交
170
	mctx, cancel = context.WithTimeout(ctx, Seconds(13))
171 172 173 174 175
	defer cancel()
	// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
	c = exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && docker images")
	rr, err = Run(t, c)
	if err != nil {
M
Medya Gh 已提交
176
		t.Fatalf("failed to run minikube docker-env. args %q : %v ", rr.Command(), err)
177 178 179 180
	}

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

}

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

189 190
	srv, err := startHTTPProxy(t)
	if err != nil {
191
		t.Fatalf("failed to set up the test proxy: %s", err)
192
	}
193 194

	// Use more memory so that we may reliably fit MySQL and nginx
M
Medya Gh 已提交
195
	// changing api server so later in soft start we verify it didn't change
196
	startArgs := append([]string{"start", "-p", profile, "--memory=2800", fmt.Sprintf("--apiserver-port=%d", apiPortTest), "--wait=true"}, StartArgs()...)
197
	c := exec.CommandContext(ctx, Target(), startArgs...)
198 199 200 201 202 203
	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 已提交
204
		t.Errorf("failed minikube start. args %q: %v", rr.Command(), err)
205 206 207 208 209 210 211 212 213 214 215 216 217
	}

	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)
	}
}

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

M
Medya Gh 已提交
222
	start := time.Now()
M
Medya Gh 已提交
223
	// the test before this had been start with --apiserver-port=8441
M
lint  
Medya Gh 已提交
224 225 226 227
	beforeCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config before soft start: %v", err)
	}
M
Medya Gh 已提交
228 229
	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 已提交
230 231
	}

M
lint  
Medya Gh 已提交
232
	softStartArgs := []string{"start", "-p", profile}
M
Medya Gh 已提交
233
	c := exec.CommandContext(ctx, Target(), softStartArgs...)
M
lint  
Medya Gh 已提交
234
	rr, err := Run(t, c)
M
Medya Gh 已提交
235 236 237
	if err != nil {
		t.Errorf("failed to soft start minikube. args %q: %v", rr.Command(), err)
	}
M
Medya Gh 已提交
238
	t.Logf("soft start took %s for %q cluster.", time.Since(start), profile)
M
Medya Gh 已提交
239

M
lint  
Medya Gh 已提交
240 241 242 243 244
	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

M
Medya Gh 已提交
245 246
	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 已提交
247
	}
M
Medya Gh 已提交
248

M
Medya Gh 已提交
249 250
}

251 252
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext(ctx context.Context, t *testing.T, profile string) {
253 254
	defer PostMortemLogs(t, profile)

255 256
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "config", "current-context"))
	if err != nil {
M
Medya Gh 已提交
257
		t.Errorf("failed to get current-context. args %q : %v", rr.Command(), err)
258 259
	}
	if !strings.Contains(rr.Stdout.String(), profile) {
260
		t.Errorf("expected current-context = %q, but got *%q*", profile, rr.Stdout.String())
261 262 263
	}
}

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

268
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "po", "-A"))
P
Priya Wadhwa 已提交
269
	if err != nil {
M
Medya Gh 已提交
270
		t.Errorf("failed to get kubectl pods: args %q : %v", rr.Command(), err)
P
Priya Wadhwa 已提交
271
	}
272
	if rr.Stderr.String() != "" {
273
		t.Errorf("expected stderr to be empty but got *%q*: args %q", rr.Stderr, rr.Command())
274
	}
T
Thomas Stromberg 已提交
275
	if !strings.Contains(rr.Stdout.String(), "kube-system") {
276
		t.Errorf("expected stdout to include *kube-system* but got *%q*. args: %q", rr.Stdout, rr.Command())
P
Priya Wadhwa 已提交
277 278 279
	}
}

280 281
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl(ctx context.Context, t *testing.T, profile string) {
282 283
	defer PostMortemLogs(t, profile)

284 285
	// 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 已提交
286
	rr, err := Run(t, exec.CommandContext(ctx, Target(), kubectlArgs...))
287
	if err != nil {
M
Medya Gh 已提交
288
		t.Fatalf("failed to get pods. args %q: %v", rr.Command(), err)
289 290 291
	}
}

292 293
// validateComponentHealth asserts that all Kubernetes components are healthy
func validateComponentHealth(ctx context.Context, t *testing.T, profile string) {
294 295
	defer PostMortemLogs(t, profile)

296 297
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "cs", "-o=json"))
	if err != nil {
M
Medya Gh 已提交
298
		t.Fatalf("failed to get components. args %q: %v", rr.Command(), err)
299 300 301 302
	}
	cs := api.ComponentStatusList{}
	d := json.NewDecoder(bytes.NewReader(rr.Stdout.Bytes()))
	if err := d.Decode(&cs); err != nil {
M
Medya Gh 已提交
303
		t.Fatalf("failed to decode kubectl json output: args %q : %v", rr.Command(), err)
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
	}

	for _, i := range cs.Items {
		status := api.ConditionFalse
		for _, c := range i.Conditions {
			if c.Type != api.ComponentHealthy {
				continue
			}
			status = c.Status
		}
		if status != api.ConditionTrue {
			t.Errorf("unexpected status: %v - item: %+v", status, i)
		}
	}
}

J
Josh Woodcock 已提交
320
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
321 322
	defer PostMortemLogs(t, profile)

323
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status"))
J
Josh Woodcock 已提交
324
	if err != nil {
M
Medya Gh 已提交
325
		t.Errorf("failed to run minikube status. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
326 327 328
	}

	// Custom format
329
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}"))
J
Josh Woodcock 已提交
330
	if err != nil {
M
Medya Gh 已提交
331
		t.Errorf("failed to run minikube status with custom format: args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
332
	}
333 334
	re := `host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+)`
	match, _ := regexp.MatchString(re, rr.Stdout.String())
J
Josh Woodcock 已提交
335
	if !match {
M
Medya Gh 已提交
336
		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 已提交
337 338 339
	}

	// Json output
340
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-o", "json"))
J
Josh Woodcock 已提交
341
	if err != nil {
M
Medya Gh 已提交
342
		t.Errorf("failed to run minikube status with json output. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
343 344 345 346
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
M
Medya Gh 已提交
347
		t.Errorf("failed to decode json from minikube status. args %q. %v", rr.Command(), err)
J
Josh Woodcock 已提交
348 349
	}
	if _, ok := jsonObject["Host"]; !ok {
M
Medya Gh 已提交
350
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Host")
J
Josh Woodcock 已提交
351 352
	}
	if _, ok := jsonObject["Kubelet"]; !ok {
M
Medya Gh 已提交
353
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubelet")
J
Josh Woodcock 已提交
354 355
	}
	if _, ok := jsonObject["APIServer"]; !ok {
M
Medya Gh 已提交
356
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "APIServer")
J
Josh Woodcock 已提交
357 358
	}
	if _, ok := jsonObject["Kubeconfig"]; !ok {
M
Medya Gh 已提交
359
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubeconfig")
J
Josh Woodcock 已提交
360 361 362
	}
}

363 364
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
365 366
	defer PostMortemLogs(t, profile)

367 368 369
	args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
370
		t.Errorf("failed to run minikube dashboard. args %q : %v", args, err)
371 372 373 374 375 376
	}
	defer func() {
		ss.Stop(t)
	}()

	start := time.Now()
M
Medya Gh 已提交
377
	s, err := ReadLineWithTimeout(ss.Stdout, Seconds(300))
378
	if err != nil {
T
tstromberg 已提交
379 380 381 382
		if runtime.GOOS == "windows" {
			t.Skipf("failed to read url within %s: %v\noutput: %q\n", time.Since(start), err, s)
		}
		t.Fatalf("failed to read url within %s: %v\noutput: %q\n", time.Since(start), err, s)
383 384 385 386 387 388 389 390 391
	}

	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 {
392
		t.Fatalf("failed to http get %q: %v\nresponse: %+v", u.String(), err, resp)
393
	}
394

395 396 397
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
398
			t.Errorf("failed to read http response body from dashboard %q: %v", u.String(), err)
399 400 401 402
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}
M
Medya Gh 已提交
403

404 405
// validateDNS asserts that all Kubernetes DNS is healthy
func validateDNS(ctx context.Context, t *testing.T, profile string) {
406 407
	defer PostMortemLogs(t, profile)

408 409
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "replace", "--force", "-f", filepath.Join(*testdataDir, "busybox.yaml")))
	if err != nil {
M
Medya Gh 已提交
410
		t.Fatalf("failed to kubectl replace busybox : args %q: %v", rr.Command(), err)
411 412
	}

M
typo  
Medya Gh 已提交
413
	names, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(4))
414
	if err != nil {
415
		t.Fatalf("failed waiting for busybox pod : %v", err)
416 417
	}

418 419 420 421 422 423
	nslookup := func() error {
		rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "exec", names[0], "nslookup", "kubernetes.default"))
		return err
	}

	// If the coredns process was stable, this retry wouldn't be necessary.
424
	if err = retry.Expo(nslookup, 1*time.Second, Minutes(1)); err != nil {
425
		t.Errorf("failed to do nslookup on kubernetes.default: %v", err)
426 427 428 429
	}

	want := []byte("10.96.0.1")
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
430
		t.Errorf("failed nslookup: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
431 432 433
	}
}

T
Thomas Stromberg 已提交
434 435
// validateDryRun asserts that the dry-run mode quickly exits with the right code
func validateDryRun(ctx context.Context, t *testing.T, profile string) {
436
	// dry-run mode should always be able to finish quickly (<5s)
M
Medya Gh 已提交
437
	mctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
438 439 440
	defer cancel()

	// Too little memory!
441
	startArgs := append([]string{"start", "-p", profile, "--dry-run", "--memory", "250MB", "--alsologtostderr"}, StartArgs()...)
T
Thomas Stromberg 已提交
442 443 444 445 446 447 448 449
	c := exec.CommandContext(mctx, Target(), startArgs...)
	rr, err := Run(t, c)

	wantCode := 78 // exit.Config
	if rr.ExitCode != wantCode {
		t.Errorf("dry-run(250MB) exit code = %d, wanted = %d: %v", rr.ExitCode, wantCode, err)
	}

M
Medya Gh 已提交
450
	dctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
451
	defer cancel()
452
	startArgs = append([]string{"start", "-p", profile, "--dry-run", "--alsologtostderr", "-v=1"}, StartArgs()...)
T
Thomas Stromberg 已提交
453 454 455 456 457 458 459
	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 已提交
460
// validateCacheCmd tests functionality of cache command (cache add, delete, list)
461
func validateCacheCmd(ctx context.Context, t *testing.T, profile string) {
462 463
	defer PostMortemLogs(t, profile)

464 465 466
	if NoneDriver() {
		t.Skipf("skipping: cache unsupported by none")
	}
M
Medya Gh 已提交
467 468
	t.Run("cache", func(t *testing.T) {
		t.Run("add", func(t *testing.T) {
469
			for _, img := range []string{"busybox:latest", "busybox:1.28.4-glibc", "k8s.gcr.io/pause:latest"} {
470
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
M
Medya Gh 已提交
471
				if err != nil {
M
Medya Gh 已提交
472
					t.Errorf("failed to cache add image %q. args %q err %v", img, rr.Command(), err)
M
Medya Gh 已提交
473 474 475
				}
			}
		})
M
Medya Ghazizadeh 已提交
476
		t.Run("delete_busybox:1.28.4-glibc", func(t *testing.T) {
477
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", "busybox:1.28.4-glibc"))
M
Medya Gh 已提交
478
			if err != nil {
M
Medya Gh 已提交
479
				t.Errorf("failed to delete image busybox:1.28.4-glibc from cache. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
480 481
			}
		})
M
Medya Gh 已提交
482

M
Medya Gh 已提交
483 484 485
		t.Run("list", func(t *testing.T) {
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "list"))
			if err != nil {
M
Medya Gh 已提交
486
				t.Errorf("failed to do cache list. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
487 488
			}
			if !strings.Contains(rr.Output(), "k8s.gcr.io/pause") {
M
Medya Gh 已提交
489
				t.Errorf("expected 'cache list' output to include 'k8s.gcr.io/pause' but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
490 491
			}
			if strings.Contains(rr.Output(), "busybox:1.28.4-glibc") {
M
Medya Gh 已提交
492
				t.Errorf("expected 'cache list' output not to include busybox:1.28.4-glibc but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
493 494
			}
		})
M
Medya Gh 已提交
495

M
Medya Ghazizadeh 已提交
496
		t.Run("verify_cache_inside_node", func(t *testing.T) {
M
Medya Gh 已提交
497
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "images"))
M
Medya Gh 已提交
498
			if err != nil {
M
Medya Gh 已提交
499
				t.Errorf("failed to get images by %q ssh %v", rr.Command(), err)
M
Medya Gh 已提交
500
			}
M
Medya Gh 已提交
501
			if !strings.Contains(rr.Output(), "1.28.4-glibc") {
M
Medya Gh 已提交
502
				t.Errorf("expected '1.28.4-glibc' to be in the output but got *%s*", rr.Output())
M
Medya Gh 已提交
503 504 505
			}

		})
M
Medya Gh 已提交
506

M
Medya Ghazizadeh 已提交
507
		t.Run("cache_reload", func(t *testing.T) { // deleting image inside minikube node manually and expecting reload to bring it back
M
Medya Gh 已提交
508 509
			img := "busybox:latest"
			// deleting image inside minikube node manually
M
Medya Gh 已提交
510
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "docker", "rmi", img)) // for some reason crictl rmi doesn't work
M
Medya Gh 已提交
511 512 513 514 515 516
			if err != nil {
				t.Errorf("failed to delete inside the node %q : %v", rr.Command(), err)
			}
			// make sure the image is deleted.
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err == nil {
517
				t.Errorf("expected an error. because image should not exist. but got *nil error* ! cmd: %q", rr.Command())
M
Medya Gh 已提交
518
			}
M
Medya Gh 已提交
519
			// minikube cache reload.
M
Medya Gh 已提交
520 521
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "reload"))
			if err != nil {
522
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
523
			}
M
Medya Gh 已提交
524
			// make sure 'cache reload' brought back the manually deleted image.
M
Medya Gh 已提交
525 526
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err != nil {
527
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
528 529 530
			}
		})

M
Medya Gh 已提交
531
	})
532 533 534 535 536 537 538 539 540 541 542
}

// 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 已提交
543
		{[]string{"set", "cpus", "2"}, "", "! These changes will take effect upon a minikube delete and then a minikube start"},
544 545 546 547 548 549 550 551 552
		{[]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 已提交
553
			t.Errorf("failed to config minikube. args %q : %v", rr.Command(), err)
554 555 556 557
		}

		got := strings.TrimSpace(rr.Stdout.String())
		if got != tc.wantOut {
558
			t.Errorf("expected config output for %q to be -%q- but got *%q*", rr.Command(), tc.wantOut, got)
559 560 561
		}
		got = strings.TrimSpace(rr.Stderr.String())
		if got != tc.wantErr {
562
			t.Errorf("expected config error for %q to be -%q- but got *%q*", rr.Command(), tc.wantErr, got)
563 564 565 566 567 568 569 570
		}
	}
}

// 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 已提交
571
		t.Errorf("%s failed: %v", rr.Command(), err)
572 573 574
	}
	for _, word := range []string{"Docker", "apiserver", "Linux", "kubelet"} {
		if !strings.Contains(rr.Stdout.String(), word) {
M
Medya Gh 已提交
575
			t.Errorf("excpeted minikube logs to include word: -%q- but got \n***%s***\n", word, rr.Output())
576 577 578 579
		}
	}
}

J
Josh Woodcock 已提交
580
// validateProfileCmd asserts "profile" command functionality
581
func validateProfileCmd(ctx context.Context, t *testing.T, profile string) {
M
Medya Gh 已提交
582 583 584 585 586
	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 已提交
587
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
588 589 590
		}
		rr, err = Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
		if err != nil {
M
Medya Gh 已提交
591
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
592 593 594 595
		}
		var profileJSON map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &profileJSON)
		if err != nil {
M
Medya Gh 已提交
596
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
597 598 599 600 601 602 603
		}
		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)
				}
604
			}
605
		}
M
Medya Gh 已提交
606
	})
607

M
Medya Gh 已提交
608 609
	t.Run("profile_list", func(t *testing.T) {
		// List profiles
M
lint  
Medya Gh 已提交
610
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list"))
M
Medya Gh 已提交
611
		if err != nil {
M
Medya Gh 已提交
612
			t.Errorf("failed to list profiles: args %q : %v", rr.Command(), err)
M
Medya Gh 已提交
613
		}
J
Josh Woodcock 已提交
614

M
Medya Gh 已提交
615 616 617 618 619 620 621 622 623 624 625
		// Table output
		listLines := strings.Split(strings.TrimSpace(rr.Stdout.String()), "\n")
		profileExists := false
		for i := 3; i < (len(listLines) - 1); i++ {
			profileLine := listLines[i]
			if strings.Contains(profileLine, profile) {
				profileExists = true
				break
			}
		}
		if !profileExists {
M
Medya Gh 已提交
626
			t.Errorf("expected 'profile list' output to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
J
Josh Woodcock 已提交
627
		}
M
Medya Gh 已提交
628 629 630 631
	})

	t.Run("profile_json_output", func(t *testing.T) {
		// Json output
M
lint  
Medya Gh 已提交
632
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
M
Medya Gh 已提交
633
		if err != nil {
M
Medya Gh 已提交
634
			t.Errorf("failed to list profiles with json format. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
635
		}
M
Medya Gh 已提交
636 637 638
		var jsonObject map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
		if err != nil {
M
Medya Gh 已提交
639
			t.Errorf("failed to decode json from profile list: args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
640 641
		}
		validProfiles := jsonObject["valid"]
M
lint  
Medya Gh 已提交
642
		profileExists := false
M
Medya Gh 已提交
643 644 645 646 647 648 649
		for _, profileObject := range validProfiles {
			if profileObject["Name"] == profile {
				profileExists = true
				break
			}
		}
		if !profileExists {
M
Medya Gh 已提交
650
			t.Errorf("expected the json of 'profile list' to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
M
Medya Gh 已提交
651 652 653
		}

	})
654 655 656
}

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

660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
	defer func() {
		if t.Failed() {
			t.Logf("service test failed - dumping debug information")

			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)
		}
	}()

684
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "deployment", "hello-node", "--image=k8s.gcr.io/echoserver:1.4"))
685
	if err != nil {
M
Medya Gh 已提交
686
		t.Logf("%q failed: %v (may not be an error).", rr.Command(), err)
687
	}
688
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "expose", "deployment", "hello-node", "--type=NodePort", "--port=8080"))
689
	if err != nil {
M
Medya Gh 已提交
690
		t.Logf("%q failed: %v (may not be an error)", rr.Command(), err)
691 692
	}

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

697
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "list"))
698
	if err != nil {
M
Medya Gh 已提交
699
		t.Errorf("failed to do service list. args %q : %v", rr.Command(), err)
700
	}
701
	if !strings.Contains(rr.Stdout.String(), "hello-node") {
702
		t.Errorf("expected 'service list' to contain *hello-node* but got -%q-", rr.Stdout.String())
703 704
	}

705 706 707 708
	if NeedsPortForward() {
		t.Skipf("test is broken for port-forwarded drivers: https://github.com/kubernetes/minikube/issues/7383")
	}

T
Thomas Stromberg 已提交
709
	// Test --https --url mode
710
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "--namespace=default", "--https", "--url", "hello-node"))
711
	if err != nil {
M
Medya Gh 已提交
712
		t.Fatalf("failed to get service url. args %q : %v", rr.Command(), err)
713 714
	}
	if rr.Stderr.String() != "" {
M
Medya Gh 已提交
715
		t.Errorf("expected stderr to be empty but got *%q* . args %q", rr.Stderr, rr.Command())
716
	}
717

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

721 722
	u, err := url.Parse(endpoint)
	if err != nil {
723
		t.Fatalf("failed to parse service url endpoint %q: %v", endpoint, err)
724 725
	}
	if u.Scheme != "https" {
726
		t.Errorf("expected scheme for %s to be 'https' but got %q", endpoint, u.Scheme)
727 728 729
	}

	// Test --format=IP
730
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url", "--format={{.IP}}"))
731
	if err != nil {
M
Medya Gh 已提交
732
		t.Errorf("failed to get service url with custom format. args %q: %v", rr.Command(), err)
733
	}
734
	if strings.TrimSpace(rr.Stdout.String()) != u.Hostname() {
M
Medya Gh 已提交
735
		t.Errorf("expected 'service --format={{.IP}}' output to be -%q- but got *%q* . args %q.", u.Hostname(), rr.Stdout.String(), rr.Command())
736 737
	}

738
	// Test a regular URL
739
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url"))
740
	if err != nil {
M
Medya Gh 已提交
741
		t.Errorf("failed to get service url. args: %q: %v", rr.Command(), err)
742
	}
743 744

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

747 748 749 750
	u, err = url.Parse(endpoint)
	if err != nil {
		t.Fatalf("failed to parse %q: %v", endpoint, err)
	}
751

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

756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
	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
778
	}
779 780 781

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

785 786
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd(ctx context.Context, t *testing.T, profile string) {
787 788
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
789
	// Table output
790 791
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list"))
	if err != nil {
M
Medya Gh 已提交
792
		t.Errorf("failed to do addon list: args %q : %v", rr.Command(), err)
793
	}
M
Medya Gh 已提交
794 795
	for _, a := range []string{"dashboard", "ingress", "ingress-dns"} {
		if !strings.Contains(rr.Output(), a) {
M
Medya Gh 已提交
796
			t.Errorf("expected 'addon list' output to include -%q- but got *%s*", a, rr.Output())
797 798
		}
	}
J
Josh Woodcock 已提交
799 800 801 802

	// Json output
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list", "-o", "json"))
	if err != nil {
M
Medya Gh 已提交
803
		t.Errorf("failed to do addon list with json output. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
804 805 806 807
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
808
		t.Errorf("failed to decode addon list json output : %v", err)
J
Josh Woodcock 已提交
809
	}
810 811
}

812 813
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd(ctx context.Context, t *testing.T, profile string) {
814 815
	defer PostMortemLogs(t, profile)

816 817 818
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
P
Priya Wadhwa 已提交
819
	want := "hello\n"
820 821
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("echo hello")))
	if err != nil {
M
Medya Gh 已提交
822
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
823 824
	}
	if rr.Stdout.String() != want {
M
Medya Gh 已提交
825
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
826 827 828
	}
}

829 830
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL(ctx context.Context, t *testing.T, profile string) {
831 832
	defer PostMortemLogs(t, profile)

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

838
	names, err := PodWait(ctx, t, profile, "default", "app=mysql", Minutes(10))
839
	if err != nil {
840
		t.Fatalf("failed waiting for mysql pod: %v", err)
841 842
	}

843 844 845 846
	// 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
847
	}
M
Medya Gh 已提交
848
	if err = retry.Expo(mysql, 1*time.Second, Minutes(5)); err != nil {
849
		t.Errorf("failed to exec 'mysql -ppassword -e show databases;': %v", err)
850 851 852
	}
}

853 854 855 856 857 858 859 860 861 862
// 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())
}

863 864 865 866 867 868 869 870 871 872
// 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())
}

873 874 875 876 877
// 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()))
}

878 879
// Copy extra file into minikube home folder for file sync test
func setupFileSync(ctx context.Context, t *testing.T, profile string) {
880 881 882
	p := localSyncTestPath()
	t.Logf("local sync path: %s", p)
	err := copy.Copy("./testdata/sync.test", p)
883
	if err != nil {
884
		t.Fatalf("failed to copy ./testdata/sync.test: %v", err)
885
	}
886

887 888
	testPem := "./testdata/minikube_test.pem"

889 890 891
	// Write to a temp file for an atomic write
	tmpPem := localTestCertPath() + ".pem"
	if err := copy.Copy(testPem, tmpPem); err != nil {
892 893 894
		t.Fatalf("failed to copy %s: %v", testPem, err)
	}

895 896 897 898
	if err := os.Rename(tmpPem, localTestCertPath()); err != nil {
		t.Fatalf("failed to rename %s: %v", tmpPem, err)
	}

899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
	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)
916
	}
917 918 919 920
}

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

923 924 925
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
926 927 928

	vp := vmSyncTestPath()
	t.Logf("Checking for existence of %s within VM", vp)
929
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
930
	if err != nil {
M
Medya Gh 已提交
931
		t.Errorf("%s failed: %v", rr.Command(), err)
932
	}
933 934
	got := rr.Stdout.String()
	t.Logf("file sync test content: %s", got)
935 936 937

	expected, err := ioutil.ReadFile("./testdata/sync.test")
	if err != nil {
938
		t.Errorf("failed to read test file '/testdata/sync.test' : %v", err)
939 940
	}

941
	if diff := cmp.Diff(string(expected), got); diff != "" {
942 943 944 945
		t.Errorf("/etc/sync.test content mismatch (-want +got):\n%s", diff)
	}
}

946 947
// validateCertSync to check existence of the test certificate
func validateCertSync(ctx context.Context, t *testing.T, profile string) {
948 949
	defer PostMortemLogs(t, profile)

950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}

	want, err := ioutil.ReadFile("./testdata/minikube_test.pem")
	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)
968
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
969
		if err != nil {
M
Medya Gh 已提交
970
			t.Errorf("failed to check existence of %q inside minikube. args %q: %v", vp, rr.Command(), err)
971 972 973 974 975
		}

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

981 982
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd(ctx context.Context, t *testing.T, profile string) {
983 984
	defer PostMortemLogs(t, profile)

985 986
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "update-context", "--alsologtostderr", "-v=2"))
	if err != nil {
M
Medya Gh 已提交
987
		t.Errorf("failed to run minikube update-context: args %q: %v", rr.Command(), err)
988 989
	}

T
Thomas Stromberg 已提交
990
	want := []byte("No changes")
991 992 993 994 995
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
		t.Errorf("update-context: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
	}
}

996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
// 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
1012
}