Merge pull request #48 from Random-Liu/apply-sandbox-image-config
Apply sandbox image config
This commit is contained in:
commit
98bc3edf50
@ -49,6 +49,7 @@ func (c *criContainerdService) startEventMonitor() error {
|
|||||||
func (c *criContainerdService) handleEventStream(events execution.ContainerService_EventsClient) {
|
func (c *criContainerdService) handleEventStream(events execution.ContainerService_EventsClient) {
|
||||||
// TODO(random-liu): [P1] Should backoff on this error, or else this will
|
// TODO(random-liu): [P1] Should backoff on this error, or else this will
|
||||||
// cause a busy loop.
|
// cause a busy loop.
|
||||||
|
// TODO(random-liu): Handle io.EOF.
|
||||||
e, err := events.Recv()
|
e, err := events.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to receive event: %v", err)
|
glog.Errorf("Failed to receive event: %v", err)
|
||||||
|
@ -55,6 +55,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// defaultSandboxImage is the image used by sandbox container.
|
||||||
|
// TODO(random-liu): [P1] Build schema 2 pause image and use it here.
|
||||||
|
defaultSandboxImage = "gcr.io/google.com/noogler-kubernetes/pause-amd64:3.0"
|
||||||
// relativeRootfsPath is the rootfs path relative to bundle path.
|
// relativeRootfsPath is the rootfs path relative to bundle path.
|
||||||
relativeRootfsPath = "rootfs"
|
relativeRootfsPath = "rootfs"
|
||||||
// defaultRuntime is the runtime to use in containerd. We may support
|
// defaultRuntime is the runtime to use in containerd. We may support
|
||||||
@ -305,31 +308,39 @@ func getRepoDigestAndTag(namedRef reference.Named, digest imagedigest.Digest) (s
|
|||||||
return repoDigest, repoTag
|
return repoDigest, repoTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// localResolve resolves image reference to image id locally. It returns empty string
|
// localResolve resolves image reference locally and returns corresponding image metadata. It returns
|
||||||
// without error if the reference doesn't exist.
|
// nil without error if the reference doesn't exist.
|
||||||
func (c *criContainerdService) localResolve(ctx context.Context, ref string) (string, error) {
|
func (c *criContainerdService) localResolve(ctx context.Context, ref string) (*metadata.ImageMetadata, error) {
|
||||||
_, err := imagedigest.Parse(ref)
|
_, err := imagedigest.Parse(ref)
|
||||||
if err == nil {
|
|
||||||
return ref, nil
|
|
||||||
}
|
|
||||||
// ref is not image id, try to resolve it locally.
|
|
||||||
normalized, err := normalizeImageRef(ref)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid image reference %q: %v", ref, err)
|
// ref is not image id, try to resolve it locally.
|
||||||
}
|
normalized, err := normalizeImageRef(ref)
|
||||||
image, err := c.imageStoreService.Get(ctx, normalized.String())
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("invalid image reference %q: %v", ref, err)
|
||||||
if images.IsNotFound(err) {
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("an error occurred when getting image %q from containerd image store: %v",
|
image, err := c.imageStoreService.Get(ctx, normalized.String())
|
||||||
normalized.String(), err)
|
if err != nil {
|
||||||
|
if images.IsNotFound(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("an error occurred when getting image %q from containerd image store: %v",
|
||||||
|
normalized.String(), err)
|
||||||
|
}
|
||||||
|
desc, err := image.Config(ctx, c.contentStoreService)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get image config descriptor: %v", err)
|
||||||
|
}
|
||||||
|
ref = desc.Digest.String()
|
||||||
}
|
}
|
||||||
desc, err := image.Config(ctx, c.contentStoreService)
|
imageID := ref
|
||||||
|
meta, err := c.imageMetadataStore.Get(imageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get image config descriptor: %v", err)
|
if metadata.IsNotExistError(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get image %q metadata: %v", imageID, err)
|
||||||
}
|
}
|
||||||
return desc.Digest.String(), nil
|
return meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUserFromImage gets uid or user name of the image user.
|
// getUserFromImage gets uid or user name of the image user.
|
||||||
@ -350,3 +361,27 @@ func getUserFromImage(user string) (*int64, string) {
|
|||||||
// If user is a numeric uid.
|
// If user is a numeric uid.
|
||||||
return &uid, ""
|
return &uid, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureImageExists returns corresponding metadata of the image reference, if image is not
|
||||||
|
// pulled yet, the function will pull the image.
|
||||||
|
func (c *criContainerdService) ensureImageExists(ctx context.Context, ref string) (*metadata.ImageMetadata, error) {
|
||||||
|
meta, err := c.localResolve(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve image %q: %v", ref, err)
|
||||||
|
}
|
||||||
|
if meta != nil {
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
// Pull image to ensure the image exists
|
||||||
|
resp, err := c.PullImage(ctx, &runtime.PullImageRequest{Image: &runtime.ImageSpec{Image: ref}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to pull image %q: %v", ref, err)
|
||||||
|
}
|
||||||
|
imageID := resp.GetImageRef()
|
||||||
|
meta, err = c.imageMetadataStore.Get(imageID)
|
||||||
|
if err != nil {
|
||||||
|
// It's still possible that someone removed the image right after it is pulled.
|
||||||
|
return nil, fmt.Errorf("failed to get image %q metadata after pulling: %v", imageID, err)
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
@ -41,21 +41,14 @@ func (c *criContainerdService) RemoveImage(ctx context.Context, r *runtime.Remov
|
|||||||
glog.V(2).Infof("RemoveImage %q returns successfully", r.GetImage().GetImage())
|
glog.V(2).Infof("RemoveImage %q returns successfully", r.GetImage().GetImage())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
imageID, err := c.localResolve(ctx, r.GetImage().GetImage())
|
meta, err := c.localResolve(ctx, r.GetImage().GetImage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can not resolve %q locally: %v", r.GetImage().GetImage(), err)
|
return nil, fmt.Errorf("can not resolve %q locally: %v", r.GetImage().GetImage(), err)
|
||||||
}
|
}
|
||||||
if imageID == "" {
|
if meta == nil {
|
||||||
// return empty without error when image not found.
|
// return empty without error when image not found.
|
||||||
return &runtime.RemoveImageResponse{}, nil
|
return &runtime.RemoveImageResponse{}, nil
|
||||||
}
|
}
|
||||||
meta, err := c.imageMetadataStore.Get(imageID)
|
|
||||||
if err != nil {
|
|
||||||
if metadata.IsNotExistError(err) {
|
|
||||||
return &runtime.RemoveImageResponse{}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("an error occurred when get image %q metadata: %v", imageID, err)
|
|
||||||
}
|
|
||||||
// Also include repo digest, because if user pull image with digest,
|
// Also include repo digest, because if user pull image with digest,
|
||||||
// there will also be a corresponding repo digest reference.
|
// there will also be a corresponding repo digest reference.
|
||||||
for _, ref := range append(meta.RepoTags, meta.RepoDigests...) {
|
for _, ref := range append(meta.RepoTags, meta.RepoDigests...) {
|
||||||
@ -65,14 +58,14 @@ func (c *criContainerdService) RemoveImage(ctx context.Context, r *runtime.Remov
|
|||||||
if err == nil || images.IsNotFound(err) {
|
if err == nil || images.IsNotFound(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to delete image reference %q for image %q: %v", ref, imageID, err)
|
return nil, fmt.Errorf("failed to delete image reference %q for image %q: %v", ref, meta.ID, err)
|
||||||
}
|
}
|
||||||
err = c.imageMetadataStore.Delete(imageID)
|
err = c.imageMetadataStore.Delete(meta.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if metadata.IsNotExistError(err) {
|
if metadata.IsNotExistError(err) {
|
||||||
return &runtime.RemoveImageResponse{}, nil
|
return &runtime.RemoveImageResponse{}, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("an error occurred when delete image %q matadata: %v", imageID, err)
|
return nil, fmt.Errorf("an error occurred when delete image %q matadata: %v", meta.ID, err)
|
||||||
}
|
}
|
||||||
return &runtime.RemoveImageResponse{}, nil
|
return &runtime.RemoveImageResponse{}, nil
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,6 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
||||||
|
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageStatus returns the status of the image, returns nil if the image isn't present.
|
// ImageStatus returns the status of the image, returns nil if the image isn't present.
|
||||||
@ -37,23 +35,14 @@ func (c *criContainerdService) ImageStatus(ctx context.Context, r *runtime.Image
|
|||||||
r.GetImage().GetImage(), retRes.GetImage())
|
r.GetImage().GetImage(), retRes.GetImage())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
imageID, err := c.localResolve(ctx, r.GetImage().GetImage())
|
meta, err := c.localResolve(ctx, r.GetImage().GetImage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can not resolve %q locally: %v", r.GetImage().GetImage(), err)
|
return nil, fmt.Errorf("can not resolve %q locally: %v", r.GetImage().GetImage(), err)
|
||||||
}
|
}
|
||||||
if imageID == "" {
|
if meta == nil {
|
||||||
// return empty without error when image not found.
|
// return empty without error when image not found.
|
||||||
return &runtime.ImageStatusResponse{}, nil
|
return &runtime.ImageStatusResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := c.imageMetadataStore.Get(imageID)
|
|
||||||
if err != nil {
|
|
||||||
if metadata.IsNotExistError(err) {
|
|
||||||
return &runtime.ImageStatusResponse{}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("an error occurred during get image %q metadata: %v",
|
|
||||||
imageID, err)
|
|
||||||
}
|
|
||||||
// TODO(random-liu): [P0] Make sure corresponding snapshot exists. What if snapshot
|
// TODO(random-liu): [P0] Make sure corresponding snapshot exists. What if snapshot
|
||||||
// doesn't exist?
|
// doesn't exist?
|
||||||
runtimeImage := &runtime.Image{
|
runtimeImage := &runtime.Image{
|
||||||
|
@ -35,7 +35,7 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
|
|||||||
glog.V(2).Infof("RemovePodSandbox for sandbox %q", r.GetPodSandboxId())
|
glog.V(2).Infof("RemovePodSandbox for sandbox %q", r.GetPodSandboxId())
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
glog.V(2).Info("RemovePodSandbox %q returns successfully", r.GetPodSandboxId())
|
glog.V(2).Infof("RemovePodSandbox %q returns successfully", r.GetPodSandboxId())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -65,6 +65,7 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
|
|||||||
return nil, fmt.Errorf("sandbox container %q is not fully stopped", id)
|
return nil, fmt.Errorf("sandbox container %q is not fully stopped", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Cleanup snapshot after switching to new snapshot api.
|
||||||
// TODO(random-liu): [P0] Cleanup shm created in RunPodSandbox.
|
// TODO(random-liu): [P0] Cleanup shm created in RunPodSandbox.
|
||||||
// TODO(random-liu): [P1] Remove permanent namespace once used.
|
// TODO(random-liu): [P1] Remove permanent namespace once used.
|
||||||
|
|
||||||
|
@ -21,17 +21,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
|
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
||||||
prototypes "github.com/gogo/protobuf/types"
|
prototypes "github.com/gogo/protobuf/types"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/opencontainers/runtime-tools/generate"
|
"github.com/opencontainers/runtime-tools/generate"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/containerd/containerd/api/types/mount"
|
|
||||||
|
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
||||||
|
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
@ -81,10 +82,22 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
Config: config,
|
Config: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(random-liu): [P0] Ensure pause image snapshot, apply default image config
|
// Ensure sandbox container image snapshot.
|
||||||
// and get snapshot mounts.
|
imageMeta, err := c.ensureImageExists(ctx, c.sandboxImage)
|
||||||
// Use fixed rootfs path and sleep command.
|
if err != nil {
|
||||||
const rootPath = "/"
|
return nil, fmt.Errorf("failed to get sandbox image %q: %v", defaultSandboxImage, err)
|
||||||
|
}
|
||||||
|
prepareResp, err := c.rootfsService.Prepare(ctx, &rootfsapi.PrepareRequest{
|
||||||
|
Name: id,
|
||||||
|
// We are sure that ChainID must be a digest.
|
||||||
|
ChainID: imagedigest.Digest(imageMeta.ChainID),
|
||||||
|
Readonly: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare sandbox rootfs %q: %v", imageMeta.ChainID, err)
|
||||||
|
}
|
||||||
|
// TODO(random-liu): [P0] Cleanup snapshot on failure after switching to new rootfs api.
|
||||||
|
rootfsMounts := prepareResp.Mounts
|
||||||
|
|
||||||
// Create sandbox container root directory.
|
// Create sandbox container root directory.
|
||||||
// Prepare streaming named pipe.
|
// Prepare streaming named pipe.
|
||||||
@ -124,7 +137,10 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start sandbox container.
|
// Start sandbox container.
|
||||||
spec := c.generateSandboxContainerSpec(id, config)
|
spec, err := c.generateSandboxContainerSpec(id, config, imageMeta.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate sandbox container spec: %v", err)
|
||||||
|
}
|
||||||
rawSpec, err := json.Marshal(spec)
|
rawSpec, err := json.Marshal(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal oci spec %+v: %v", spec, err)
|
return nil, fmt.Errorf("failed to marshal oci spec %+v: %v", spec, err)
|
||||||
@ -137,16 +153,7 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
Value: rawSpec,
|
Value: rawSpec,
|
||||||
},
|
},
|
||||||
// TODO(random-liu): [P0] Get rootfs mount from containerd.
|
// TODO(random-liu): [P0] Get rootfs mount from containerd.
|
||||||
Rootfs: []*mount.Mount{
|
Rootfs: rootfsMounts,
|
||||||
{
|
|
||||||
Type: "bind",
|
|
||||||
Source: rootPath,
|
|
||||||
Options: []string{
|
|
||||||
"rw",
|
|
||||||
"rbind",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Runtime: defaultRuntime,
|
Runtime: defaultRuntime,
|
||||||
// No stdin for sandbox container.
|
// No stdin for sandbox container.
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
@ -205,20 +212,35 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *criContainerdService) generateSandboxContainerSpec(id string, config *runtime.PodSandboxConfig) *runtimespec.Spec {
|
func (c *criContainerdService) generateSandboxContainerSpec(id string, config *runtime.PodSandboxConfig,
|
||||||
// TODO(random-liu): [P0] Get command from image config.
|
imageConfig *imagespec.ImageConfig) (*runtimespec.Spec, error) {
|
||||||
pauseCommand := []string{"sh", "-c", "while true; do sleep 1000000000; done"}
|
|
||||||
|
|
||||||
// Creates a spec Generator with the default spec.
|
// Creates a spec Generator with the default spec.
|
||||||
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
||||||
g := generate.New()
|
g := generate.New()
|
||||||
|
|
||||||
|
// Apply default config from image config.
|
||||||
|
for _, e := range imageConfig.Env {
|
||||||
|
kv := strings.Split(e, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid environment variable in image config %+v", imageConfig)
|
||||||
|
}
|
||||||
|
g.AddProcessEnv(kv[0], kv[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageConfig.WorkingDir != "" {
|
||||||
|
g.SetProcessCwd(imageConfig.WorkingDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imageConfig.Entrypoint) == 0 {
|
||||||
|
// Pause image must have entrypoint.
|
||||||
|
return nil, fmt.Errorf("invalid empty entrypoint in image config %+v", imageConfig)
|
||||||
|
}
|
||||||
|
// Set process commands.
|
||||||
|
g.SetProcessArgs(append(imageConfig.Entrypoint, imageConfig.Cmd...))
|
||||||
|
|
||||||
// Set relative root path.
|
// Set relative root path.
|
||||||
g.SetRootPath(relativeRootfsPath)
|
g.SetRootPath(relativeRootfsPath)
|
||||||
|
|
||||||
// Set process commands.
|
|
||||||
g.SetProcessArgs(pauseCommand)
|
|
||||||
|
|
||||||
// Make root of sandbox container read-only.
|
// Make root of sandbox container read-only.
|
||||||
g.SetRootReadonly(true)
|
g.SetRootReadonly(true)
|
||||||
|
|
||||||
@ -276,5 +298,5 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
|
|||||||
|
|
||||||
// TODO(random-liu): [P1] Set default sandbox container resource limit.
|
// TODO(random-liu): [P1] Set default sandbox container resource limit.
|
||||||
|
|
||||||
return g.Spec()
|
return g.Spec(), nil
|
||||||
}
|
}
|
||||||
|
@ -23,20 +23,22 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
|
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
||||||
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
|
|
||||||
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
|
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
|
||||||
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
|
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
|
||||||
|
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, func(*testing.T, string, *runtimespec.Spec)) {
|
func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, *imagespec.ImageConfig, func(*testing.T, string, *runtimespec.Spec)) {
|
||||||
config := &runtime.PodSandboxConfig{
|
config := &runtime.PodSandboxConfig{
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Name: "test-name",
|
Name: "test-name",
|
||||||
@ -52,20 +54,31 @@ func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, func(*testing.T, str
|
|||||||
CgroupParent: "/test/cgroup/parent",
|
CgroupParent: "/test/cgroup/parent",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
imageConfig := &imagespec.ImageConfig{
|
||||||
|
Env: []string{"a=b", "c=d"},
|
||||||
|
Entrypoint: []string{"/pause"},
|
||||||
|
Cmd: []string{"forever"},
|
||||||
|
WorkingDir: "/workspace",
|
||||||
|
}
|
||||||
specCheck := func(t *testing.T, id string, spec *runtimespec.Spec) {
|
specCheck := func(t *testing.T, id string, spec *runtimespec.Spec) {
|
||||||
assert.Equal(t, "test-hostname", spec.Hostname)
|
assert.Equal(t, "test-hostname", spec.Hostname)
|
||||||
assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
|
assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
|
||||||
assert.Equal(t, relativeRootfsPath, spec.Root.Path)
|
assert.Equal(t, relativeRootfsPath, spec.Root.Path)
|
||||||
assert.Equal(t, true, spec.Root.Readonly)
|
assert.Equal(t, true, spec.Root.Readonly)
|
||||||
|
assert.Contains(t, spec.Process.Env, "a=b", "c=d")
|
||||||
|
assert.Equal(t, []string{"/pause", "forever"}, spec.Process.Args)
|
||||||
|
assert.Equal(t, "/workspace", spec.Process.Cwd)
|
||||||
}
|
}
|
||||||
return config, specCheck
|
return config, imageConfig, specCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateSandboxContainerSpec(t *testing.T) {
|
func TestGenerateSandboxContainerSpec(t *testing.T) {
|
||||||
testID := "test-id"
|
testID := "test-id"
|
||||||
for desc, test := range map[string]struct {
|
for desc, test := range map[string]struct {
|
||||||
configChange func(*runtime.PodSandboxConfig)
|
configChange func(*runtime.PodSandboxConfig)
|
||||||
specCheck func(*testing.T, *runtimespec.Spec)
|
imageConfigChange func(*imagespec.ImageConfig)
|
||||||
|
specCheck func(*testing.T, *runtimespec.Spec)
|
||||||
|
expectErr bool
|
||||||
}{
|
}{
|
||||||
"spec should reflect original config": {
|
"spec should reflect original config": {
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
@ -106,14 +119,36 @@ func TestGenerateSandboxContainerSpec(t *testing.T) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"should return error when entrypoint is empty": {
|
||||||
|
imageConfigChange: func(c *imagespec.ImageConfig) {
|
||||||
|
c.Entrypoint = nil
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
"should return error when env is invalid ": {
|
||||||
|
imageConfigChange: func(c *imagespec.ImageConfig) {
|
||||||
|
c.Env = []string{"a"}
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Logf("TestCase %q", desc)
|
t.Logf("TestCase %q", desc)
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
config, specCheck := getRunPodSandboxTestData()
|
config, imageConfig, specCheck := getRunPodSandboxTestData()
|
||||||
if test.configChange != nil {
|
if test.configChange != nil {
|
||||||
test.configChange(config)
|
test.configChange(config)
|
||||||
}
|
}
|
||||||
spec := c.generateSandboxContainerSpec(testID, config)
|
if test.imageConfigChange != nil {
|
||||||
|
test.imageConfigChange(imageConfig)
|
||||||
|
}
|
||||||
|
spec, err := c.generateSandboxContainerSpec(testID, config, imageConfig)
|
||||||
|
if test.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, spec)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, spec)
|
||||||
specCheck(t, testID, spec)
|
specCheck(t, testID, spec)
|
||||||
if test.specCheck != nil {
|
if test.specCheck != nil {
|
||||||
test.specCheck(t, spec)
|
test.specCheck(t, spec)
|
||||||
@ -122,8 +157,9 @@ func TestGenerateSandboxContainerSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunPodSandbox(t *testing.T) {
|
func TestRunPodSandbox(t *testing.T) {
|
||||||
config, specCheck := getRunPodSandboxTestData()
|
config, imageConfig, specCheck := getRunPodSandboxTestData()
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
|
fakeRootfsClient := c.rootfsService.(*servertesting.FakeRootfsClient)
|
||||||
fakeExecutionClient := c.containerService.(*servertesting.FakeExecutionClient)
|
fakeExecutionClient := c.containerService.(*servertesting.FakeExecutionClient)
|
||||||
fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin)
|
fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin)
|
||||||
fakeOS := c.os.(*ostesting.FakeOS)
|
fakeOS := c.os.(*ostesting.FakeOS)
|
||||||
@ -140,6 +176,17 @@ func TestRunPodSandbox(t *testing.T) {
|
|||||||
assert.Equal(t, os.FileMode(0700), perm)
|
assert.Equal(t, os.FileMode(0700), perm)
|
||||||
return nopReadWriteCloser{}, nil
|
return nopReadWriteCloser{}, nil
|
||||||
}
|
}
|
||||||
|
testChainID := imagedigest.Digest("test-sandbox-chain-id")
|
||||||
|
imageMetadata := metadata.ImageMetadata{
|
||||||
|
ID: testSandboxImage,
|
||||||
|
ChainID: testChainID.String(),
|
||||||
|
Config: imageConfig,
|
||||||
|
}
|
||||||
|
// Insert sandbox image metadata.
|
||||||
|
assert.NoError(t, c.imageMetadataStore.Create(imageMetadata))
|
||||||
|
// Insert fake chainID
|
||||||
|
fakeRootfsClient.SetFakeChainIDs([]imagedigest.Digest{testChainID})
|
||||||
|
expectRootfsClientCalls := []string{"prepare"}
|
||||||
expectExecutionClientCalls := []string{"create", "start"}
|
expectExecutionClientCalls := []string{"create", "start"}
|
||||||
|
|
||||||
res, err := c.RunPodSandbox(context.Background(), &runtime.RunPodSandboxRequest{Config: config})
|
res, err := c.RunPodSandbox(context.Background(), &runtime.RunPodSandboxRequest{Config: config})
|
||||||
@ -155,13 +202,24 @@ func TestRunPodSandbox(t *testing.T) {
|
|||||||
assert.Contains(t, pipes, stdout, "sandbox stdout pipe should be created")
|
assert.Contains(t, pipes, stdout, "sandbox stdout pipe should be created")
|
||||||
assert.Contains(t, pipes, stderr, "sandbox stderr pipe should be created")
|
assert.Contains(t, pipes, stderr, "sandbox stderr pipe should be created")
|
||||||
|
|
||||||
|
assert.Equal(t, expectRootfsClientCalls, fakeRootfsClient.GetCalledNames(), "expect rootfs functions should be called")
|
||||||
|
calls := fakeRootfsClient.GetCalledDetails()
|
||||||
|
prepareOpts := calls[0].Argument.(*rootfsapi.PrepareRequest)
|
||||||
|
assert.Equal(t, &rootfsapi.PrepareRequest{
|
||||||
|
Name: id,
|
||||||
|
ChainID: testChainID,
|
||||||
|
Readonly: true,
|
||||||
|
}, prepareOpts, "prepare request should be correct")
|
||||||
|
|
||||||
assert.Equal(t, expectExecutionClientCalls, fakeExecutionClient.GetCalledNames(), "expect containerd functions should be called")
|
assert.Equal(t, expectExecutionClientCalls, fakeExecutionClient.GetCalledNames(), "expect containerd functions should be called")
|
||||||
calls := fakeExecutionClient.GetCalledDetails()
|
calls = fakeExecutionClient.GetCalledDetails()
|
||||||
createOpts := calls[0].Argument.(*execution.CreateRequest)
|
createOpts := calls[0].Argument.(*execution.CreateRequest)
|
||||||
assert.Equal(t, id, createOpts.ID, "create id should be correct")
|
assert.Equal(t, id, createOpts.ID, "create id should be correct")
|
||||||
// TODO(random-liu): Test rootfs mount when image management part is integrated.
|
|
||||||
assert.Equal(t, stdout, createOpts.Stdout, "stdout pipe should be passed to containerd")
|
assert.Equal(t, stdout, createOpts.Stdout, "stdout pipe should be passed to containerd")
|
||||||
assert.Equal(t, stderr, createOpts.Stderr, "stderr pipe should be passed to containerd")
|
assert.Equal(t, stderr, createOpts.Stderr, "stderr pipe should be passed to containerd")
|
||||||
|
mountsResp, err := fakeRootfsClient.Mounts(context.Background(), &rootfsapi.MountsRequest{Name: id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, mountsResp.Mounts, createOpts.Rootfs, "rootfs mount should be correct")
|
||||||
spec := &runtimespec.Spec{}
|
spec := &runtimespec.Spec{}
|
||||||
assert.NoError(t, json.Unmarshal(createOpts.Spec.Value, spec))
|
assert.NoError(t, json.Unmarshal(createOpts.Spec.Value, spec))
|
||||||
t.Logf("oci spec check")
|
t.Logf("oci spec check")
|
||||||
|
@ -34,7 +34,7 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
|
|||||||
glog.V(2).Infof("StopPodSandbox for sandbox %q", r.GetPodSandboxId())
|
glog.V(2).Infof("StopPodSandbox for sandbox %q", r.GetPodSandboxId())
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
glog.V(2).Info("StopPodSandbox %q returns successfully", r.GetPodSandboxId())
|
glog.V(2).Infof("StopPodSandbox %q returns successfully", r.GetPodSandboxId())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
|
|||||||
} else if !os.IsNotExist(err) { // It's ok for sandbox.NetNS to *not* exist
|
} else if !os.IsNotExist(err) { // It's ok for sandbox.NetNS to *not* exist
|
||||||
return nil, fmt.Errorf("failed to stat netns path for sandbox %q before tearing down the network: %v", id, err)
|
return nil, fmt.Errorf("failed to stat netns path for sandbox %q before tearing down the network: %v", id, err)
|
||||||
}
|
}
|
||||||
glog.V(2).Info("TearDown network for sandbox %q successfully", id)
|
glog.V(2).Infof("TearDown network for sandbox %q successfully", id)
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Handle sandbox container graceful deletion.
|
// TODO(random-liu): [P1] Handle sandbox container graceful deletion.
|
||||||
// Delete the sandbox container from containerd.
|
// Delete the sandbox container from containerd.
|
||||||
|
@ -65,6 +65,9 @@ type criContainerdService struct {
|
|||||||
os osinterface.OS
|
os osinterface.OS
|
||||||
// rootDir is the directory for managing cri-containerd files.
|
// rootDir is the directory for managing cri-containerd files.
|
||||||
rootDir string
|
rootDir string
|
||||||
|
// sandboxImage is the image to use for sandbox container.
|
||||||
|
// TODO(random-liu): Make this configurable via flag.
|
||||||
|
sandboxImage string
|
||||||
// sandboxStore stores all sandbox metadata.
|
// sandboxStore stores all sandbox metadata.
|
||||||
sandboxStore metadata.SandboxStore
|
sandboxStore metadata.SandboxStore
|
||||||
// imageMetadataStore stores all image metadata.
|
// imageMetadataStore stores all image metadata.
|
||||||
@ -100,6 +103,7 @@ func NewCRIContainerdService(conn *grpc.ClientConn, rootDir, networkPluginBinDir
|
|||||||
c := &criContainerdService{
|
c := &criContainerdService{
|
||||||
os: osinterface.RealOS{},
|
os: osinterface.RealOS{},
|
||||||
rootDir: rootDir,
|
rootDir: rootDir,
|
||||||
|
sandboxImage: defaultSandboxImage,
|
||||||
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
||||||
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
||||||
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
||||||
|
@ -32,6 +32,8 @@ import (
|
|||||||
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
|
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/registrar"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/registrar"
|
||||||
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
|
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
|
||||||
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
|
||||||
)
|
)
|
||||||
@ -43,20 +45,28 @@ func (nopReadWriteCloser) Read(p []byte) (n int, err error) { return 0, io.EOF
|
|||||||
func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return 0, io.ErrShortWrite }
|
func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return 0, io.ErrShortWrite }
|
||||||
func (nopReadWriteCloser) Close() error { return nil }
|
func (nopReadWriteCloser) Close() error { return nil }
|
||||||
|
|
||||||
const testRootDir = "/test/rootfs"
|
const (
|
||||||
|
testRootDir = "/test/rootfs"
|
||||||
|
// Use an image id as test sandbox image to avoid image name resolve.
|
||||||
|
// TODO(random-liu): Change this to image name after we have complete image
|
||||||
|
// management unit test framework.
|
||||||
|
testSandboxImage = "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113798"
|
||||||
|
)
|
||||||
|
|
||||||
// newTestCRIContainerdService creates a fake criContainerdService for test.
|
// newTestCRIContainerdService creates a fake criContainerdService for test.
|
||||||
func newTestCRIContainerdService() *criContainerdService {
|
func newTestCRIContainerdService() *criContainerdService {
|
||||||
return &criContainerdService{
|
return &criContainerdService{
|
||||||
os: ostesting.NewFakeOS(),
|
os: ostesting.NewFakeOS(),
|
||||||
rootDir: testRootDir,
|
rootDir: testRootDir,
|
||||||
containerService: servertesting.NewFakeExecutionClient(),
|
sandboxImage: testSandboxImage,
|
||||||
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
||||||
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
||||||
sandboxNameIndex: registrar.NewRegistrar(),
|
sandboxNameIndex: registrar.NewRegistrar(),
|
||||||
sandboxIDIndex: truncindex.NewTruncIndex(nil),
|
sandboxIDIndex: truncindex.NewTruncIndex(nil),
|
||||||
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
||||||
containerNameIndex: registrar.NewRegistrar(),
|
containerNameIndex: registrar.NewRegistrar(),
|
||||||
|
containerService: servertesting.NewFakeExecutionClient(),
|
||||||
|
rootfsService: servertesting.NewFakeRootfsClient(),
|
||||||
netPlugin: servertesting.NewFakeCNIPlugin(),
|
netPlugin: servertesting.NewFakeCNIPlugin(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,11 +75,21 @@ func newTestCRIContainerdService() *criContainerdService {
|
|||||||
func TestSandboxOperations(t *testing.T) {
|
func TestSandboxOperations(t *testing.T) {
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
fake := c.containerService.(*servertesting.FakeExecutionClient)
|
fake := c.containerService.(*servertesting.FakeExecutionClient)
|
||||||
|
fakeRootfsClient := c.rootfsService.(*servertesting.FakeRootfsClient)
|
||||||
fakeOS := c.os.(*ostesting.FakeOS)
|
fakeOS := c.os.(*ostesting.FakeOS)
|
||||||
fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin)
|
fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin)
|
||||||
fakeOS.OpenFifoFn = func(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
fakeOS.OpenFifoFn = func(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
return nopReadWriteCloser{}, nil
|
return nopReadWriteCloser{}, nil
|
||||||
}
|
}
|
||||||
|
// Insert sandbox image metadata.
|
||||||
|
assert.NoError(t, c.imageMetadataStore.Create(metadata.ImageMetadata{
|
||||||
|
ID: testSandboxImage,
|
||||||
|
ChainID: "test-chain-id",
|
||||||
|
Config: &imagespec.ImageConfig{Entrypoint: []string{"/pause"}},
|
||||||
|
}))
|
||||||
|
// Insert fake chainID
|
||||||
|
fakeRootfsClient.SetFakeChainIDs([]imagedigest.Digest{imagedigest.Digest("test-chain-id")})
|
||||||
|
|
||||||
config := &runtime.PodSandboxConfig{
|
config := &runtime.PodSandboxConfig{
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Name: "test-name",
|
Name: "test-name",
|
||||||
|
@ -169,7 +169,11 @@ func (f *FakeRootfsClient) Prepare(ctx context.Context, prepareOpts *rootfs.Prep
|
|||||||
if ok {
|
if ok {
|
||||||
return nil, fmt.Errorf("mounts already exist")
|
return nil, fmt.Errorf("mounts already exist")
|
||||||
}
|
}
|
||||||
f.MountList[prepareOpts.Name] = []*mount.Mount{}
|
f.MountList[prepareOpts.Name] = []*mount.Mount{{
|
||||||
|
Type: "bind",
|
||||||
|
Source: prepareOpts.Name,
|
||||||
|
// TODO(random-liu): Fake options based on Readonly option.
|
||||||
|
}}
|
||||||
return &rootfs.MountResponse{
|
return &rootfs.MountResponse{
|
||||||
Mounts: f.MountList[prepareOpts.Name],
|
Mounts: f.MountList[prepareOpts.Name],
|
||||||
}, nil
|
}, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user