178 lines
4.7 KiB
Go
178 lines
4.7 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
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 server
|
|
|
|
import (
|
|
gocontext "context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/containers"
|
|
"github.com/golang/glog"
|
|
"golang.org/x/net/context"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/kubernetes-incubator/cri-containerd/pkg/util"
|
|
)
|
|
|
|
// TODO(random-liu): Remove this after debugging.
|
|
// checkSnapshot checks whether rootfs is empty, or whether entrypoint exists. If not,
|
|
// it dumps the root fs.
|
|
func (c *criContainerdService) checkSnapshot(ctx context.Context, snapshot, entrypoint string, env []string, mustDump bool) error {
|
|
glog.V(0).Infof("Check snapshot %q with entrypoint %q", snapshot, entrypoint)
|
|
snapshotter := c.client.SnapshotService(c.config.ContainerdConfig.Snapshotter)
|
|
mounts, err := snapshotter.Mounts(ctx, snapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
root, err := ioutil.TempDir("", "ctd-rootfs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(root) // nolint: errcheck
|
|
for _, m := range mounts {
|
|
if err := m.Mount(root); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
defer unix.Unmount(root, 0) // nolint: errcheck
|
|
fs, err := ioutil.ReadDir(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(fs) == 0 {
|
|
dumpDir(root) // nolint: errcheck
|
|
return fmt.Errorf("rootfs is empty")
|
|
}
|
|
if entrypoint != "" {
|
|
paths := append(getPath(env), "")
|
|
found := false
|
|
for _, p := range paths {
|
|
e := filepath.Join(root, p, entrypoint)
|
|
_, err = os.Stat(e)
|
|
if err == nil {
|
|
found = true
|
|
continue
|
|
}
|
|
}
|
|
if !found {
|
|
dumpDir(root) // nolint: errcheck
|
|
return fmt.Errorf("entrypoint %q not found", entrypoint)
|
|
}
|
|
}
|
|
if mustDump {
|
|
dumpDir(root) // nolint: errcheck
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkImage create a snapshot from the image and check rootfs.
|
|
func (c *criContainerdService) checkImage(ctx context.Context, imageID, entrypoint string, env []string, mustDump bool) error {
|
|
glog.V(0).Infof("Check image %q with entrypoint %q", imageID, entrypoint)
|
|
snapshotter := c.client.SnapshotService(c.config.ContainerdConfig.Snapshotter)
|
|
ctx, done, err := c.client.WithLease(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer done() // nolint: errcheck
|
|
img, err := c.imageStore.Get(imageID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sid := util.GenerateID()
|
|
_, err = snapshotter.Prepare(ctx, sid, img.ChainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := snapshotter.Remove(ctx, sid); err != nil {
|
|
glog.Errorf("Failed to remove snapshot id %q: %v", sid, err)
|
|
}
|
|
}()
|
|
if entrypoint == "" {
|
|
if len(img.Config.Entrypoint) > 0 {
|
|
entrypoint = img.Config.Entrypoint[0]
|
|
}
|
|
}
|
|
if len(env) == 0 {
|
|
if len(img.Config.Env) > 0 {
|
|
env = img.Config.Env
|
|
}
|
|
}
|
|
return c.checkSnapshot(ctx, sid, entrypoint, env, mustDump)
|
|
}
|
|
|
|
func withSnapshotCheck(c *criContainerdService, id, entrypoint string, env []string) containerd.NewContainerOpts {
|
|
return func(ctx gocontext.Context, client *containerd.Client, _ *containers.Container) error {
|
|
glog.V(0).Infof("Check snapshot %q after snapshot creation", id)
|
|
if err := c.checkSnapshot(ctx, id, entrypoint, env, false); err != nil {
|
|
glog.Errorf("Failed to check snapshot %q: %v", id, err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func getPath(env []string) []string {
|
|
for _, e := range env {
|
|
kv := strings.SplitN(e, "=", 2)
|
|
if len(kv) != 2 {
|
|
continue
|
|
}
|
|
k, v := kv[0], kv[1]
|
|
if k != "PATH" {
|
|
continue
|
|
}
|
|
return strings.Split(v, ":")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dumpDir(root string) error {
|
|
return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fi.Mode()&os.ModeSymlink != 0 {
|
|
target, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
glog.V(0).Info(fi.Mode(), fmt.Sprintf("%10s", ""), path, "->", target)
|
|
} else if fi.Mode().IsRegular() {
|
|
p, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
glog.Errorf("Error reading file %q: %v", path, err)
|
|
return nil
|
|
}
|
|
|
|
if len(p) > 64 { // just display a little bit.
|
|
p = p[:64]
|
|
}
|
|
glog.V(0).Info(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path, "[", strconv.Quote(string(p)), "...]")
|
|
} else {
|
|
glog.V(0).Info(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path)
|
|
}
|
|
return nil
|
|
})
|
|
}
|