functional_test.go 35.8 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
	defer cancel()
	// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
M
Medya Gh 已提交
173 174 175 176 177 178 179 180
	if runtime.GOOS == "windows" { // golang exec powershell needs some tricks !
		c = exec.CommandContext(mctx, Target(), "-p "+profile+" docker-env | Invoke-Expression ; docker images")
		rr, err = Run(t, c, true)
	} else {
		c = exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && docker images")
		rr, err = Run(t, c)
	}

181
	if err != nil {
M
Medya Gh 已提交
182
		t.Fatalf("failed to run minikube docker-env. args %q : %v ", rr.Command(), err)
183 184 185 186
	}

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

}

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

195 196
	srv, err := startHTTPProxy(t)
	if err != nil {
197
		t.Fatalf("failed to set up the test proxy: %s", err)
198
	}
199 200

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

	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 已提交
224
// validateSoftStart validates that after minikube already started, a "minikube start" should not change the configs.
M
lint  
Medya Gh 已提交
225
func validateSoftStart(ctx context.Context, t *testing.T, profile string) {
226 227
	defer PostMortemLogs(t, profile)

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

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

M
lint  
Medya Gh 已提交
246 247 248 249 250
	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

M
Medya Gh 已提交
251 252
	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 已提交
253
	}
M
Medya Gh 已提交
254

M
Medya Gh 已提交
255 256
}

257 258
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext(ctx context.Context, t *testing.T, profile string) {
259 260
	defer PostMortemLogs(t, profile)

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

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

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

286 287
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl(ctx context.Context, t *testing.T, profile string) {
288 289
	defer PostMortemLogs(t, profile)

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

298 299
// validateComponentHealth asserts that all Kubernetes components are healthy
func validateComponentHealth(ctx context.Context, t *testing.T, profile string) {
300 301
	defer PostMortemLogs(t, profile)

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

	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 已提交
326
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
327 328
	defer PostMortemLogs(t, profile)

329
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status"))
J
Josh Woodcock 已提交
330
	if err != nil {
M
Medya Gh 已提交
331
		t.Errorf("failed to run minikube status. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
332 333 334
	}

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

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

369 370
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
371 372
	defer PostMortemLogs(t, profile)

373 374 375
	args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
376
		t.Errorf("failed to run minikube dashboard. args %q : %v", args, err)
377 378 379 380 381 382
	}
	defer func() {
		ss.Stop(t)
	}()

	start := time.Now()
M
Medya Gh 已提交
383
	s, err := ReadLineWithTimeout(ss.Stdout, Seconds(300))
384
	if err != nil {
T
tstromberg 已提交
385 386 387 388
		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)
389 390 391 392 393 394 395 396 397
	}

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

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

410 411
// validateDNS asserts that all Kubernetes DNS is healthy
func validateDNS(ctx context.Context, t *testing.T, profile string) {
412 413
	defer PostMortemLogs(t, profile)

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

M
typo  
Medya Gh 已提交
419
	names, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(4))
420
	if err != nil {
421
		t.Fatalf("failed waiting for busybox pod : %v", err)
422 423
	}

424 425 426 427 428 429
	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.
430
	if err = retry.Expo(nslookup, 1*time.Second, Minutes(1)); err != nil {
431
		t.Errorf("failed to do nslookup on kubernetes.default: %v", err)
432 433 434 435
	}

	want := []byte("10.96.0.1")
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
436
		t.Errorf("failed nslookup: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
437 438 439
	}
}

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

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

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

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

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

		})
M
Medya Gh 已提交
512

M
Medya Ghazizadeh 已提交
513
		t.Run("cache_reload", func(t *testing.T) { // deleting image inside minikube node manually and expecting reload to bring it back
M
Medya Gh 已提交
514 515
			img := "busybox:latest"
			// deleting image inside minikube node manually
M
Medya Gh 已提交
516
			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 已提交
517 518 519 520 521 522
			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 {
523
				t.Errorf("expected an error. because image should not exist. but got *nil error* ! cmd: %q", rr.Command())
M
Medya Gh 已提交
524
			}
M
Medya Gh 已提交
525
			// minikube cache reload.
M
Medya Gh 已提交
526 527
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "reload"))
			if err != nil {
528
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
529
			}
M
Medya Gh 已提交
530
			// make sure 'cache reload' brought back the manually deleted image.
M
Medya Gh 已提交
531 532
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err != nil {
533
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
534 535 536
			}
		})

M
Medya Gh 已提交
537
	})
538 539 540 541 542 543 544 545 546 547 548
}

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

		got := strings.TrimSpace(rr.Stdout.String())
		if got != tc.wantOut {
564
			t.Errorf("expected config output for %q to be -%q- but got *%q*", rr.Command(), tc.wantOut, got)
565 566 567
		}
		got = strings.TrimSpace(rr.Stderr.String())
		if got != tc.wantErr {
568
			t.Errorf("expected config error for %q to be -%q- but got *%q*", rr.Command(), tc.wantErr, got)
569 570 571 572 573 574 575 576
		}
	}
}

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

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

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

M
Medya Gh 已提交
621 622 623 624 625 626 627 628 629 630 631
		// 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 已提交
632
			t.Errorf("expected 'profile list' output to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
J
Josh Woodcock 已提交
633
		}
M
Medya Gh 已提交
634 635 636 637
	})

	t.Run("profile_json_output", func(t *testing.T) {
		// Json output
M
lint  
Medya Gh 已提交
638
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
M
Medya Gh 已提交
639
		if err != nil {
M
Medya Gh 已提交
640
			t.Errorf("failed to list profiles with json format. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
641
		}
M
Medya Gh 已提交
642 643 644
		var jsonObject map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
		if err != nil {
M
Medya Gh 已提交
645
			t.Errorf("failed to decode json from profile list: args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
646 647
		}
		validProfiles := jsonObject["valid"]
M
lint  
Medya Gh 已提交
648
		profileExists := false
M
Medya Gh 已提交
649 650 651 652 653 654 655
		for _, profileObject := range validProfiles {
			if profileObject["Name"] == profile {
				profileExists = true
				break
			}
		}
		if !profileExists {
M
Medya Gh 已提交
656
			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 已提交
657 658 659
		}

	})
660 661 662
}

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

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
	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)
		}
	}()

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

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

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

711 712 713 714
	if NeedsPortForward() {
		t.Skipf("test is broken for port-forwarded drivers: https://github.com/kubernetes/minikube/issues/7383")
	}

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

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

727 728
	u, err := url.Parse(endpoint)
	if err != nil {
729
		t.Fatalf("failed to parse service url endpoint %q: %v", endpoint, err)
730 731
	}
	if u.Scheme != "https" {
732
		t.Errorf("expected scheme for %s to be 'https' but got %q", endpoint, u.Scheme)
733 734 735
	}

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

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

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

753 754 755 756
	u, err = url.Parse(endpoint)
	if err != nil {
		t.Fatalf("failed to parse %q: %v", endpoint, err)
	}
757

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

762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
	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
784
	}
785 786 787

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

791 792
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd(ctx context.Context, t *testing.T, profile string) {
793 794
	defer PostMortemLogs(t, profile)

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

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

818 819
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd(ctx context.Context, t *testing.T, profile string) {
820 821
	defer PostMortemLogs(t, profile)

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

835 836
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL(ctx context.Context, t *testing.T, profile string) {
837 838
	defer PostMortemLogs(t, profile)

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

844
	names, err := PodWait(ctx, t, profile, "default", "app=mysql", Minutes(10))
845
	if err != nil {
846
		t.Fatalf("failed waiting for mysql pod: %v", err)
847 848
	}

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

859 860 861 862 863 864 865 866 867 868
// 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())
}

869 870 871 872 873 874 875 876 877 878
// 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())
}

879 880 881 882 883
// 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()))
}

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

893 894
	testPem := "./testdata/minikube_test.pem"

895 896 897
	// Write to a temp file for an atomic write
	tmpPem := localTestCertPath() + ".pem"
	if err := copy.Copy(testPem, tmpPem); err != nil {
898 899 900
		t.Fatalf("failed to copy %s: %v", testPem, err)
	}

901 902 903 904
	if err := os.Rename(tmpPem, localTestCertPath()); err != nil {
		t.Fatalf("failed to rename %s: %v", tmpPem, err)
	}

905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
	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)
922
	}
923 924 925 926
}

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

929 930 931
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
932 933 934

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

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

947
	if diff := cmp.Diff(string(expected), got); diff != "" {
948 949 950 951
		t.Errorf("/etc/sync.test content mismatch (-want +got):\n%s", diff)
	}
}

952 953
// validateCertSync to check existence of the test certificate
func validateCertSync(ctx context.Context, t *testing.T, profile string) {
954 955
	defer PostMortemLogs(t, profile)

956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973
	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)
974
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
975
		if err != nil {
M
Medya Gh 已提交
976
			t.Errorf("failed to check existence of %q inside minikube. args %q: %v", vp, rr.Command(), err)
977 978 979 980 981
		}

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

987 988
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd(ctx context.Context, t *testing.T, profile string) {
989 990
	defer PostMortemLogs(t, profile)

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

T
Thomas Stromberg 已提交
996
	want := []byte("No changes")
997 998 999 1000 1001
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
		t.Errorf("update-context: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
	}
}

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
// 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
1018
}