oci.go 17.0 KB
Newer Older
M
Medya Gh 已提交
1 2
/*
Copyright 2019 The Kubernetes Authors All rights reserved.
M
Medya Gh 已提交
3

M
Medya Gh 已提交
4 5 6
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
M
Medya Gh 已提交
7

M
Medya Gh 已提交
8
    http://www.apache.org/licenses/LICENSE-2.0
M
Medya Gh 已提交
9

M
Medya Gh 已提交
10 11 12 13 14 15 16 17 18 19
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 oci

import (
M
Medya Gh 已提交
20
	"context"
M
Medya Gh 已提交
21
	"os"
M
Medya Ghazizadeh 已提交
22
	"path/filepath"
M
Medya Gh 已提交
23
	"time"
M
Medya Gh 已提交
24 25 26 27

	"bufio"
	"bytes"

M
Medya Gh 已提交
28
	"github.com/golang/glog"
M
Medya Gh 已提交
29
	"github.com/pkg/errors"
M
Medya Gh 已提交
30
	"k8s.io/minikube/pkg/minikube/constants"
M
Medya Ghazizadeh 已提交
31
	"k8s.io/minikube/pkg/minikube/localpath"
32
	"k8s.io/minikube/pkg/minikube/out"
33
	"k8s.io/minikube/pkg/util/retry"
M
Medya Gh 已提交
34 35 36 37 38 39

	"fmt"
	"os/exec"
	"strings"
)

M
Medya Gh 已提交
40
// DeleteContainersByLabel deletes all containers that have a specific label
41
// if there no containers found with the given 	label, it will return nil
M
Medya Gh 已提交
42
func DeleteContainersByLabel(ociBin string, label string) []error {
43
	var deleteErrs []error
44

45 46
	cs, err := listContainersByLabel(ociBin, label)
	if err != nil {
M
Medya Gh 已提交
47
		return []error{fmt.Errorf("listing containers by label %q", label)}
48
	}
49

M
Medya Gh 已提交
50 51 52
	if len(cs) == 0 {
		return nil
	}
53

M
lint  
Medya Gh 已提交
54
	for _, c := range cs {
M
Medya Gh 已提交
55
		_, err := ContainerStatus(ociBin, c)
M
Medya Gh 已提交
56
		// only try to delete if docker/podman inspect returns
M
Medya Gh 已提交
57
		// if it doesn't it means docker daemon is stuck and needs restart
M
Medya Gh 已提交
58
		if err != nil {
M
Medya Gh 已提交
59
			deleteErrs = append(deleteErrs, errors.Wrapf(err, "delete container %s: %s daemon is stuck. please try again!", c, ociBin))
M
Medya Gh 已提交
60
			glog.Errorf("%s daemon seems to be stuck. Please try restarting your %s. :%v", ociBin, ociBin, err)
M
Medya Gh 已提交
61 62
			continue
		}
M
Medya Gh 已提交
63
		if err := ShutDown(ociBin, c); err != nil {
M
lint  
Medya Gh 已提交
64
			glog.Infof("couldn't shut down %s (might be okay): %v ", c, err)
M
Medya Gh 已提交
65
		}
M
Medya Gh 已提交
66 67 68
		cmd := exec.Command(ociBin, "rm", "-f", "-v", c)
		if out, err := cmd.CombinedOutput(); err != nil {
			deleteErrs = append(deleteErrs, errors.Wrapf(err, "delete container %s: output %s", c, out))
69
		}
M
Medya Gh 已提交
70

71 72 73 74
	}
	return deleteErrs
}

M
Medya Gh 已提交
75 76
// DeleteContainer deletes a container by ID or Name
func DeleteContainer(ociBin string, name string) error {
77

M
Medya Gh 已提交
78 79
	_, err := ContainerStatus(ociBin, name)
	if err != nil {
M
Medya Gh 已提交
80
		glog.Errorf("%s daemon seems to be stuck. Please try restarting your %s. Will try to delete anyways: %v", ociBin, ociBin, err)
M
Medya Gh 已提交
81 82
	}
	// try to delete anyways
M
Medya Gh 已提交
83
	if err := ShutDown(ociBin, name); err != nil {
M
lint  
Medya Gh 已提交
84
		glog.Infof("couldn't shut down %s (might be okay): %v ", name, err)
M
Medya Gh 已提交
85
	}
M
Medya Gh 已提交
86 87 88 89 90 91 92
	cmd := exec.Command(ociBin, "rm", "-f", "-v", name)
	if out, err := cmd.CombinedOutput(); err != nil {
		return errors.Wrapf(err, "delete container %s: output %s", name, out)
	}
	return nil
}

P
Priya Wadhwa 已提交
93 94
// PrepareContainerNode sets up the container node before CreateContainerNode is called.
// For the docker runtime, it creates a docker volume which will be mounted into kic
P
Priya Wadhwa 已提交
95
func PrepareContainerNode(p CreateParams) error {
96 97 98 99 100 101 102 103 104 105
	if p.OCIBinary != Docker {
		return nil
	}
	if err := createDockerVolume(p.Name, p.Name); err != nil {
		return errors.Wrapf(err, "creating volume for %s container", p.Name)
	}
	glog.Infof("Successfully created a docker volume %s", p.Name)
	return nil
}

M
Medya Gh 已提交
106 107 108 109 110 111 112 113 114 115 116
// CreateContainerNode creates a new container node
func CreateContainerNode(p CreateParams) error {
	runArgs := []string{
		"-d", // run the container detached
		"-t", // allocate a tty for entrypoint logs
		// running containers in a container requires privileged
		// NOTE: we could try to replicate this with --cap-add, and use less
		// privileges, but this flag also changes some mounts that are necessary
		// including some ones docker would otherwise do by default.
		// for now this is what we want. in the future we may revisit this.
		"--privileged",
M
Medya Gh 已提交
117 118 119
		"--security-opt", "seccomp=unconfined", //  ignore seccomp
		// ignore apparmore github actions docker: https://github.com/kubernetes/minikube/issues/7624
		"--security-opt", "apparmor=unconfined",
M
Medya Gh 已提交
120 121 122 123 124 125 126
		"--tmpfs", "/tmp", // various things depend on working /tmp
		"--tmpfs", "/run", // systemd wants a writable /run
		// logs,pods be stroed on  filesystem vs inside container,
		// some k8s things want /lib/modules
		"-v", "/lib/modules:/lib/modules:ro",
		"--hostname", p.Name, // make hostname match container name
		"--name", p.Name, // ... and set the container name
127
		"--label", fmt.Sprintf("%s=%s", CreatedByLabelKey, "true"),
M
Medya Gh 已提交
128 129 130
		// label the node with the cluster ID
		"--label", p.ClusterLabel,
		// label the node with the role ID
131
		"--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, p.Role),
S
Sharif Elgamal 已提交
132 133
		// label th enode wuth the node ID
		"--label", p.NodeLabel,
M
Medya Ghazizadeh 已提交
134 135 136
	}

	if p.OCIBinary == Podman { // enable execing in /var
137 138 139 140 141
		// volume path in minikube home folder to mount to /var
		hostVarVolPath := filepath.Join(localpath.MiniPath(), "machines", p.Name, "var")
		if err := os.MkdirAll(hostVarVolPath, 0711); err != nil {
			return errors.Wrapf(err, "create var dir %s", hostVarVolPath)
		}
M
Medya Ghazizadeh 已提交
142 143 144 145
		// podman mounts var/lib with no-exec by default  https://github.com/containers/libpod/issues/5103
		runArgs = append(runArgs, "--volume", fmt.Sprintf("%s:/var:exec", hostVarVolPath))
	}
	if p.OCIBinary == Docker {
146
		runArgs = append(runArgs, "--volume", fmt.Sprintf("%s:/var", p.Name))
M
Medya Ghazizadeh 已提交
147 148 149 150 151
		// setting resource limit in privileged mode is only supported by docker
		// podman error: "Error: invalid configuration, cannot set resources with rootless containers not using cgroups v2 unified mode"
		runArgs = append(runArgs, fmt.Sprintf("--cpus=%s", p.CPUs), fmt.Sprintf("--memory=%s", p.Memory))
	}

M
Medya Gh 已提交
152 153 154 155 156 157 158
	for key, val := range p.Envs {
		runArgs = append(runArgs, "-e", fmt.Sprintf("%s=%s", key, val))
	}

	// adds node specific args
	runArgs = append(runArgs, p.ExtraArgs...)

T
Thomas Stromberg 已提交
159
	if enabled := isUsernsRemapEnabled(p.OCIBinary); enabled {
M
Medya Gh 已提交
160 161 162 163 164
		// We need this argument in order to make this command work
		// in systems that have userns-remap enabled on the docker daemon
		runArgs = append(runArgs, "--userns=host")
	}

T
Thomas Stromberg 已提交
165 166
	if err := createContainer(p.OCIBinary, p.Image, withRunArgs(runArgs...), withMounts(p.Mounts), withPortMappings(p.PortMappings)); err != nil {
		return errors.Wrap(err, "create container")
M
Medya Gh 已提交
167
	}
168 169 170 171 172 173 174 175 176

	checkRunning := func() error {
		s, err := ContainerStatus(p.OCIBinary, p.Name)
		if err != nil {
			return fmt.Errorf("temporary error checking status for %q : %v", p.Name, err)
		}
		if s != "running" {
			return fmt.Errorf("temporary error created container %q is not running yet", p.Name)
		}
M
Medya Ghazizadeh 已提交
177
		glog.Infof("the created container %q has a running status.", p.Name)
178 179 180
		return nil
	}

M
Medya Gh 已提交
181 182
	// retry up to up 13 seconds to make sure the created container status is running.
	if err := retry.Expo(checkRunning, 13*time.Millisecond, time.Second*13); err != nil {
M
Medya Gh 已提交
183
		return errors.Wrapf(err, "check container %q running", p.Name)
184 185
	}

M
Medya Gh 已提交
186 187 188 189
	return nil
}

// CreateContainer creates a container with "docker/podman run"
T
Thomas Stromberg 已提交
190
func createContainer(ociBinary string, image string, opts ...createOpt) error {
M
Medya Gh 已提交
191 192 193 194 195 196 197 198 199 200 201 202 203 204
	o := &createOpts{}
	for _, opt := range opts {
		o = opt(o)
	}
	// convert mounts to container run args
	runArgs := o.RunArgs
	for _, mount := range o.Mounts {
		runArgs = append(runArgs, generateMountBindings(mount)...)
	}
	for _, portMapping := range o.PortMappings {
		runArgs = append(runArgs, generatePortMappings(portMapping)...)
	}
	// construct the actual docker run argv
	args := []string{"run"}
205

M
Medya Ghazizadeh 已提交
206 207 208 209
	// to run nested container from privileged container in podman https://bugzilla.redhat.com/show_bug.cgi?id=1687713
	if ociBinary == Podman {
		args = append(args, "--cgroup-manager", "cgroupfs")
	}
210

M
Medya Gh 已提交
211 212 213 214
	args = append(args, runArgs...)
	args = append(args, image)
	args = append(args, o.ContainerArgs...)

T
Thomas Stromberg 已提交
215
	out, err := exec.Command(ociBinary, args...).CombinedOutput()
M
Medya Gh 已提交
216
	if err != nil {
T
Thomas Stromberg 已提交
217
		return errors.Wrapf(err, "failed args: %v output: %s", args, out)
M
Medya Gh 已提交
218
	}
219

T
Thomas Stromberg 已提交
220
	return nil
M
Medya Gh 已提交
221 222 223 224 225 226 227
}

// Copy copies a local asset into the container
func Copy(ociBinary string, ociID string, targetDir string, fName string) error {
	if _, err := os.Stat(fName); os.IsNotExist(err) {
		return errors.Wrapf(err, "error source %s does not exist", fName)
	}
228

M
Medya Gh 已提交
229 230
	destination := fmt.Sprintf("%s:%s", ociID, targetDir)
	cmd := exec.Command(ociBinary, "cp", fName, destination)
231
	if err := cmd.Run(); err != nil {
M
Medya Gh 已提交
232 233
		return errors.Wrapf(err, "error copying %s into node", fName)
	}
234

M
Medya Gh 已提交
235 236 237
	return nil
}

M
Medya Gh 已提交
238 239 240
// ContainerID returns id of a container name
func ContainerID(ociBinary string, nameOrID string) (string, error) {
	cmd := exec.Command(ociBinary, "inspect", "-f", "{{.Id}}", nameOrID)
M
Medya Gh 已提交
241
	out, err := cmd.CombinedOutput()
242

M
Medya Gh 已提交
243 244 245 246 247 248
	if err != nil { // don't return error if not found, only return empty string
		if strings.Contains(string(out), "Error: No such object:") || strings.Contains(string(out), "unable to find") {
			err = nil
		}
		out = []byte{}
	}
249

M
Medya Gh 已提交
250 251 252
	return string(out), err
}

253
// WarnIfSlow runs an oci command, warning about performance issues
M
Medya Gh 已提交
254
func WarnIfSlow(args ...string) ([]byte, error) {
M
Medya Gh 已提交
255
	killTime := 19 * time.Second
T
Thomas Stromberg 已提交
256
	warnTime := 2 * time.Second
257

M
Medya Gh 已提交
258 259 260 261 262
	if args[1] == "volume" || args[1] == "ps" { // volume and ps requires more time than inspect
		killTime = 30 * time.Second
		warnTime = 3 * time.Second
	}

263
	ctx, cancel := context.WithTimeout(context.Background(), killTime)
M
Medya Gh 已提交
264
	defer cancel()
265

266
	start := time.Now()
M
Medya Gh 已提交
267 268
	glog.Infof("executing with %s timeout: %v", args, killTime)
	cmd := exec.CommandContext(ctx, args[0], args[1:]...)
269 270 271 272
	stdout, err := cmd.Output()
	d := time.Since(start)
	if d > warnTime {
		out.WarningT(`Executing "{{.command}}" took an unusually long time: {{.duration}}`, out.V{"command": strings.Join(cmd.Args, " "), "duration": d})
M
Medya Gh 已提交
273
		out.ErrT(out.Tip, `Restarting the {{.name}} service may improve performance.`, out.V{"name": args[0]})
274
	}
275

M
Medya Gh 已提交
276
	if ctx.Err() == context.DeadlineExceeded {
277 278 279 280 281 282 283
		return stdout, fmt.Errorf("%q timed out after %s", strings.Join(cmd.Args, " "), killTime)
	}
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			return stdout, fmt.Errorf("%q failed: %v: %s", strings.Join(cmd.Args, " "), exitErr, exitErr.Stderr)
		}
		return stdout, fmt.Errorf("%q failed: %v", strings.Join(cmd.Args, " "), err)
M
Medya Gh 已提交
284
	}
285 286
	return stdout, nil
}
287

288 289 290
// ContainerExists checks if container name exists (either running or exited)
func ContainerExists(ociBin string, name string) (bool, error) {
	out, err := WarnIfSlow(ociBin, "ps", "-a", "--format", "{{.Names}}")
M
Medya Gh 已提交
291 292 293
	if err != nil {
		return false, errors.Wrapf(err, string(out))
	}
294

M
Medya Gh 已提交
295 296 297 298 299 300
	containers := strings.Split(string(out), "\n")
	for _, c := range containers {
		if strings.TrimSpace(c) == name {
			return true, nil
		}
	}
301

M
Medya Gh 已提交
302 303 304
	return false, nil
}

M
Medya Gh 已提交
305 306 307 308 309
// IsCreatedByMinikube returns true if the container was created by minikube
// with default assumption that it is not created by minikube when we don't know for sure
func IsCreatedByMinikube(ociBinary string, nameOrID string) bool {
	cmd := exec.Command(ociBinary, "inspect", nameOrID, "--format", "{{.Config.Labels}}")
	out, err := cmd.CombinedOutput()
310

M
Medya Gh 已提交
311
	if err != nil {
M
Medya Gh 已提交
312 313
		return false
	}
314

M
Medya Gh 已提交
315 316
	if strings.Contains(string(out), fmt.Sprintf("%s:true", CreatedByLabelKey)) {
		return true
M
Medya Gh 已提交
317
	}
318

M
Medya Gh 已提交
319
	return false
M
Medya Gh 已提交
320 321
}

M
Medya Gh 已提交
322 323
// ListOwnedContainers lists all the containres that kic driver created on user's machine using a label
func ListOwnedContainers(ociBinary string) ([]string, error) {
324
	return listContainersByLabel(ociBinary, ProfileLabelKey)
M
Medya Gh 已提交
325 326 327 328
}

// inspect return low-level information on containers
func inspect(ociBinary string, containerNameOrID, format string) ([]string, error) {
329

M
Medya Gh 已提交
330
	cmd := exec.Command(ociBinary, "inspect",
M
Medya Gh 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
		"-f", format,
		containerNameOrID) // ... against the "node" container
	var buff bytes.Buffer
	cmd.Stdout = &buff
	cmd.Stderr = &buff
	err := cmd.Run()
	scanner := bufio.NewScanner(&buff)
	var lines []string
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, err
}

/*
This is adapated from:
https://github.com/kubernetes/kubernetes/blob/07a5488b2a8f67add543da72e8819407d8314204/pkg/kubelet/dockershim/helpers.go#L115-L155
*/
// generateMountBindings converts the mount list to a list of strings that
// can be understood by docker
// '<HostPath>:<ContainerPath>[:options]', where 'options'
// is a comma-separated list of the following strings:
// 'ro', if the path is read only
// 'Z', if the volume requires SELinux relabeling
M
Medya Gh 已提交
355
func generateMountBindings(mounts ...Mount) []string {
M
Medya Gh 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
	result := make([]string, 0, len(mounts))
	for _, m := range mounts {
		bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath)
		var attrs []string
		if m.Readonly {
			attrs = append(attrs, "ro")
		}
		// Only request relabeling if the pod provides an SELinux context. If the pod
		// does not provide an SELinux context relabeling will label the volume with
		// the container's randomly allocated MCS label. This would restrict access
		// to the volume to the container which mounts it first.
		if m.SelinuxRelabel {
			attrs = append(attrs, "Z")
		}
		switch m.Propagation {
M
Medya Gh 已提交
371
		case MountPropagationNone:
M
Medya Gh 已提交
372
			// noop, private is default
M
Medya Gh 已提交
373
		case MountPropagationBidirectional:
M
Medya Gh 已提交
374
			attrs = append(attrs, "rshared")
M
Medya Gh 已提交
375
		case MountPropagationHostToContainer:
M
Medya Gh 已提交
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
			attrs = append(attrs, "rslave")
		default:
			// Falls back to "private"
		}

		if len(attrs) > 0 {
			bind = fmt.Sprintf("%s:%s", bind, strings.Join(attrs, ","))
		}
		// our specific modification is the following line: make this a docker flag
		bind = fmt.Sprintf("--volume=%s", bind)
		result = append(result, bind)
	}
	return result
}

M
Medya Gh 已提交
391
// isUsernsRemapEnabled checks if userns-remap is enabled in docker
T
Thomas Stromberg 已提交
392
func isUsernsRemapEnabled(ociBinary string) bool {
M
Medya Gh 已提交
393
	cmd := exec.Command(ociBinary, "info", "--format", "'{{json .SecurityOptions}}'")
M
Medya Gh 已提交
394 395 396 397
	var buff bytes.Buffer
	cmd.Stdout = &buff
	cmd.Stderr = &buff
	err := cmd.Run()
398
	if err != nil {
T
Thomas Stromberg 已提交
399
		return false
400 401
	}

M
Medya Gh 已提交
402 403
	scanner := bufio.NewScanner(&buff)
	var lines []string
404

M
Medya Gh 已提交
405 406 407
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
408

M
Medya Gh 已提交
409 410
	if len(lines) > 0 {
		if strings.Contains(lines[0], "name=userns") {
T
Thomas Stromberg 已提交
411
			return true
M
Medya Gh 已提交
412 413
		}
	}
414

T
Thomas Stromberg 已提交
415
	return false
M
Medya Gh 已提交
416 417
}

M
Medya Gh 已提交
418
func generatePortMappings(portMappings ...PortMapping) []string {
M
Medya Gh 已提交
419 420
	result := make([]string, 0, len(portMappings))
	for _, pm := range portMappings {
421 422 423
		// let docker pick a host port by leaving it as ::
		// example --publish=127.0.0.17::8443 will get a random host port for 8443
		publish := fmt.Sprintf("--publish=%s::%d", pm.ListenAddress, pm.ContainerPort)
M
Medya Gh 已提交
424 425 426 427 428
		result = append(result, publish)
	}
	return result
}

M
Medya Gh 已提交
429
// withRunArgs sets the args for docker run
M
Medya Gh 已提交
430
// as in the args portion of `docker run args... image containerArgs...`
M
Medya Gh 已提交
431
func withRunArgs(args ...string) createOpt {
M
Medya Gh 已提交
432 433 434 435 436 437
	return func(r *createOpts) *createOpts {
		r.RunArgs = args
		return r
	}
}

M
Medya Gh 已提交
438 439
// withMounts sets the container mounts
func withMounts(mounts []Mount) createOpt {
M
Medya Gh 已提交
440 441 442 443 444 445
	return func(r *createOpts) *createOpts {
		r.Mounts = mounts
		return r
	}
}

M
Medya Gh 已提交
446 447
// withPortMappings sets the container port mappings to the host
func withPortMappings(portMappings []PortMapping) createOpt {
M
Medya Gh 已提交
448 449 450 451 452 453
	return func(r *createOpts) *createOpts {
		r.PortMappings = portMappings
		return r
	}
}

M
Medya Gh 已提交
454
// listContainersByLabel returns all the container names with a specified label
455
func listContainersByLabel(ociBinary string, label string) ([]string, error) {
456 457 458 459
	stdout, err := WarnIfSlow(ociBinary, "ps", "-a", "--filter", fmt.Sprintf("label=%s", label), "--format", "{{.Names}}")
	if err != nil {
		return nil, err
	}
460
	s := bufio.NewScanner(bytes.NewReader(stdout))
461
	var names []string
462 463 464 465
	for s.Scan() {
		n := strings.TrimSpace(s.Text())
		if n != "" {
			names = append(names, n)
M
Medya Gh 已提交
466
		}
M
Medya Gh 已提交
467
	}
468
	return names, err
M
Medya Gh 已提交
469
}
M
Medya Gh 已提交
470 471 472 473 474 475

// PointToHostDockerDaemon will unset env variables that point to docker inside minikube
// to make sure it points to the docker daemon installed by user.
func PointToHostDockerDaemon() error {
	p := os.Getenv(constants.MinikubeActiveDockerdEnv)
	if p != "" {
M
Medya Gh 已提交
476
		glog.Infof("shell is pointing to dockerd inside minikube. will unset to use host")
M
Medya Gh 已提交
477 478 479 480 481 482 483 484 485 486 487 488
	}

	for i := range constants.DockerDaemonEnvs {
		e := constants.DockerDaemonEnvs[i]
		err := os.Setenv(e, "")
		if err != nil {
			return errors.Wrapf(err, "resetting %s env", e)
		}

	}
	return nil
}
M
Medya Gh 已提交
489

M
Medya Gh 已提交
490 491
// ContainerStatus returns status of a container running,exited,...
func ContainerStatus(ociBin string, name string) (string, error) {
492 493
	out, err := WarnIfSlow(ociBin, "inspect", name, "--format={{.State.Status}}")
	return strings.TrimSpace(string(out)), err
M
Medya Gh 已提交
494
}
M
Medya Gh 已提交
495 496 497 498 499

// Shutdown will run command to shut down the container
// to ensure the containers process and networking bindings are all closed
// to avoid containers getting stuck before delete https://github.com/kubernetes/minikube/issues/7657
func ShutDown(ociBin string, name string) error {
M
lint  
Medya Gh 已提交
500
	cmd := exec.Command(ociBin, "exec", "--privileged", "-t", name, "sudo", "init", "0")
M
Medya Gh 已提交
501 502 503
	if out, err := cmd.CombinedOutput(); err != nil {
		return errors.Wrapf(err, "shutdown %s: output %q", name, out)
	}
M
lint  
Medya Gh 已提交
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520

	// wait till it is stoped
	stopped := func() error {
		st, err := ContainerStatus(ociBin, name)
		if st == "exited" {
			return nil
		}
		if err != nil {
			glog.Infof("temporary error verifying shutdown: %v", err)
		}
		glog.Infof("temporary error: container %s status is %s but expect it to be exited", name, st)
		return errors.Wrap(err, "couldn't verify cointainer is exited. %v")
	}
	if err := retry.Expo(stopped, time.Millisecond*500, time.Second*20); err != nil {
		return errors.Wrap(err, "verify shutdown")
	}
	glog.Infof("Successfully shutdown container %s", name)
M
Medya Gh 已提交
521 522
	return nil
}