Avoid containerd access as much as possible.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
11042a4141
commit
df58d6825d
@ -18,6 +18,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -50,9 +51,13 @@ func TestSandboxCleanRemove(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Logf("Sandbox state should be NOTREADY")
|
t.Logf("Sandbox state should be NOTREADY")
|
||||||
status, err := runtimeService.PodSandboxStatus(sb)
|
assert.NoError(t, Eventually(func() (bool, error) {
|
||||||
require.NoError(t, err)
|
status, err := runtimeService.PodSandboxStatus(sb)
|
||||||
assert.Equal(t, runtime.PodSandboxState_SANDBOX_NOTREADY, status.GetState())
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return status.GetState() == runtime.PodSandboxState_SANDBOX_NOTREADY, nil
|
||||||
|
}, time.Second, 30*time.Second), "sandbox state should become NOTREADY")
|
||||||
|
|
||||||
t.Logf("Should not be able to remove the sandbox when netns is not closed")
|
t.Logf("Should not be able to remove the sandbox when netns is not closed")
|
||||||
assert.Error(t, runtimeService.RemovePodSandbox(sb))
|
assert.Error(t, runtimeService.RemovePodSandbox(sb))
|
||||||
|
@ -170,20 +170,26 @@ func (c containerForTest) toContainer() (containerstore.Container, error) {
|
|||||||
func TestListContainers(t *testing.T) {
|
func TestListContainers(t *testing.T) {
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
sandboxesInStore := []sandboxstore.Sandbox{
|
sandboxesInStore := []sandboxstore.Sandbox{
|
||||||
{
|
sandboxstore.NewSandbox(
|
||||||
Metadata: sandboxstore.Metadata{
|
sandboxstore.Metadata{
|
||||||
ID: "s-1abcdef1234",
|
ID: "s-1abcdef1234",
|
||||||
Name: "sandboxname-1",
|
Name: "sandboxname-1",
|
||||||
Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-1"}},
|
Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-1"}},
|
||||||
},
|
},
|
||||||
},
|
sandboxstore.Status{
|
||||||
{
|
State: sandboxstore.StateReady,
|
||||||
Metadata: sandboxstore.Metadata{
|
},
|
||||||
|
),
|
||||||
|
sandboxstore.NewSandbox(
|
||||||
|
sandboxstore.Metadata{
|
||||||
ID: "s-2abcdef1234",
|
ID: "s-2abcdef1234",
|
||||||
Name: "sandboxname-2",
|
Name: "sandboxname-2",
|
||||||
Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-2"}},
|
Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-2"}},
|
||||||
},
|
},
|
||||||
},
|
sandboxstore.Status{
|
||||||
|
State: sandboxstore.StateNotReady,
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
createdAt := time.Now().UnixNano()
|
createdAt := time.Now().UnixNano()
|
||||||
startedAt := time.Now().UnixNano()
|
startedAt := time.Now().UnixNano()
|
||||||
|
@ -23,12 +23,14 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
containerdio "github.com/containerd/containerd/cio"
|
containerdio "github.com/containerd/containerd/cio"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
cio "github.com/containerd/cri-containerd/pkg/server/io"
|
cio "github.com/containerd/cri-containerd/pkg/server/io"
|
||||||
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
||||||
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartContainer starts the container.
|
// StartContainer starts the container.
|
||||||
@ -89,18 +91,7 @@ func (c *criContainerdService) startContainer(ctx context.Context,
|
|||||||
return fmt.Errorf("sandbox %q not found: %v", meta.SandboxID, err)
|
return fmt.Errorf("sandbox %q not found: %v", meta.SandboxID, err)
|
||||||
}
|
}
|
||||||
sandboxID := meta.SandboxID
|
sandboxID := meta.SandboxID
|
||||||
// Make sure sandbox is running.
|
if sandbox.Status.Get().State != sandboxstore.StateReady {
|
||||||
s, err := sandbox.Container.Task(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get sandbox container %q info: %v", sandboxID, err)
|
|
||||||
}
|
|
||||||
// This is only a best effort check, sandbox may still exit after this. If sandbox fails
|
|
||||||
// before starting the container, the start will fail.
|
|
||||||
taskStatus, err := s.Status(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get task status for sandbox container %q: %v", id, err)
|
|
||||||
}
|
|
||||||
if taskStatus.Status != containerd.Running {
|
|
||||||
return fmt.Errorf("sandbox container %q is not running", sandboxID)
|
return fmt.Errorf("sandbox container %q is not running", sandboxID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +123,8 @@ func (c *criContainerdService) startContainer(ctx context.Context,
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
if _, err := task.Delete(ctx, containerd.WithProcessKill); err != nil {
|
// It's possible that task is deleted by event monitor.
|
||||||
|
if _, err := task.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
|
||||||
logrus.WithError(err).Errorf("Failed to delete containerd task %q", id)
|
logrus.WithError(err).Errorf("Failed to delete containerd task %q", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ type containerInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toCRIContainerInfo converts internal container object information to CRI container status response info map.
|
// toCRIContainerInfo converts internal container object information to CRI container status response info map.
|
||||||
|
// TODO(random-liu): Return error instead of logging.
|
||||||
func toCRIContainerInfo(ctx context.Context, container containerstore.Container, verbose bool) (map[string]string, error) {
|
func toCRIContainerInfo(ctx context.Context, container containerstore.Container, verbose bool) (map[string]string, error) {
|
||||||
if !verbose {
|
if !verbose {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -31,11 +31,9 @@ import (
|
|||||||
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// killContainerTimeout is the timeout that we wait for the container to
|
||||||
// killContainerTimeout is the timeout that we wait for the container to
|
// be SIGKILLed.
|
||||||
// be SIGKILLed.
|
const killContainerTimeout = 2 * time.Minute
|
||||||
killContainerTimeout = 2 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
// StopContainer stops a running container with a grace period (i.e., timeout).
|
// StopContainer stops a running container with a grace period (i.e., timeout).
|
||||||
func (c *criContainerdService) StopContainer(ctx context.Context, r *runtime.StopContainerRequest) (*runtime.StopContainerResponse, error) {
|
func (c *criContainerdService) StopContainer(ctx context.Context, r *runtime.StopContainerRequest) (*runtime.StopContainerResponse, error) {
|
||||||
|
@ -28,12 +28,14 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/containerd/cri-containerd/pkg/store"
|
||||||
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
containerstore "github.com/containerd/cri-containerd/pkg/store/container"
|
||||||
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// eventMonitor monitors containerd event and updates internal state correspondingly.
|
// eventMonitor monitors containerd event and updates internal state correspondingly.
|
||||||
// TODO(random-liu): [P1] Is it possible to drop event during containerd is running?
|
// TODO(random-liu): [P1] Figure out is it possible to drop event during containerd
|
||||||
|
// is running. If it is, we should do periodically list to sync state with containerd.
|
||||||
type eventMonitor struct {
|
type eventMonitor struct {
|
||||||
containerStore *containerstore.Store
|
containerStore *containerstore.Store
|
||||||
sandboxStore *sandboxstore.Store
|
sandboxStore *sandboxstore.Store
|
||||||
@ -106,57 +108,22 @@ func (em *eventMonitor) handleEvent(evt *events.Envelope) {
|
|||||||
e := any.(*eventtypes.TaskExit)
|
e := any.(*eventtypes.TaskExit)
|
||||||
logrus.Infof("TaskExit event %+v", e)
|
logrus.Infof("TaskExit event %+v", e)
|
||||||
cntr, err := em.containerStore.Get(e.ContainerID)
|
cntr, err := em.containerStore.Get(e.ContainerID)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
if _, err := em.sandboxStore.Get(e.ContainerID); err == nil {
|
handleContainerExit(e, cntr)
|
||||||
return
|
return
|
||||||
}
|
} else if err != store.ErrNotExist {
|
||||||
logrus.WithError(err).Errorf("Failed to get container %q", e.ContainerID)
|
logrus.WithError(err).Errorf("Failed to get container %q", e.ContainerID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if e.Pid != cntr.Status.Get().Pid {
|
// Use GetAll to include sandbox in unknown state.
|
||||||
// Non-init process died, ignore the event.
|
sb, err := em.sandboxStore.GetAll(e.ContainerID)
|
||||||
|
if err == nil {
|
||||||
|
handleSandboxExit(e, sb)
|
||||||
|
return
|
||||||
|
} else if err != store.ErrNotExist {
|
||||||
|
logrus.WithError(err).Errorf("Failed to get sandbox %q", e.ContainerID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Attach container IO so that `Delete` could cleanup the stream properly.
|
|
||||||
task, err := cntr.Container.Task(context.Background(),
|
|
||||||
func(*containerdio.FIFOSet) (containerdio.IO, error) {
|
|
||||||
return cntr.IO, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if !errdefs.IsNotFound(err) {
|
|
||||||
logrus.WithError(err).Errorf("failed to stop container, task not found for container %q", e.ContainerID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
|
|
||||||
if _, err = task.Delete(context.Background()); err != nil {
|
|
||||||
// TODO(random-liu): [P0] Enqueue the event and retry.
|
|
||||||
if !errdefs.IsNotFound(err) {
|
|
||||||
logrus.WithError(err).Errorf("failed to stop container %q", e.ContainerID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Move on to make sure container status is updated.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
|
|
||||||
// If FinishedAt has been set (e.g. with start failure), keep as
|
|
||||||
// it is.
|
|
||||||
if status.FinishedAt != 0 {
|
|
||||||
return status, nil
|
|
||||||
}
|
|
||||||
status.Pid = 0
|
|
||||||
status.FinishedAt = e.ExitedAt.UnixNano()
|
|
||||||
status.ExitCode = int32(e.ExitStatus)
|
|
||||||
return status, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Errorf("Failed to update container %q state", e.ContainerID)
|
|
||||||
// TODO(random-liu): [P0] Enqueue the event and retry.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Using channel to propagate the information of container stop
|
|
||||||
cntr.Stop()
|
|
||||||
case *eventtypes.TaskOOM:
|
case *eventtypes.TaskOOM:
|
||||||
e := any.(*eventtypes.TaskOOM)
|
e := any.(*eventtypes.TaskOOM)
|
||||||
logrus.Infof("TaskOOM event %+v", e)
|
logrus.Infof("TaskOOM event %+v", e)
|
||||||
@ -177,3 +144,95 @@ func (em *eventMonitor) handleEvent(evt *events.Envelope) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleContainerExit handles TaskExit event for container.
|
||||||
|
func handleContainerExit(e *eventtypes.TaskExit, cntr containerstore.Container) {
|
||||||
|
if e.Pid != cntr.Status.Get().Pid {
|
||||||
|
// Non-init process died, ignore the event.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Attach container IO so that `Delete` could cleanup the stream properly.
|
||||||
|
task, err := cntr.Container.Task(context.Background(),
|
||||||
|
func(*containerdio.FIFOSet) (containerdio.IO, error) {
|
||||||
|
return cntr.IO, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if !errdefs.IsNotFound(err) {
|
||||||
|
logrus.WithError(err).Errorf("failed to load task for container %q", e.ContainerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
|
||||||
|
if _, err = task.Delete(context.Background()); err != nil {
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
if !errdefs.IsNotFound(err) {
|
||||||
|
logrus.WithError(err).Errorf("failed to stop container %q", e.ContainerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Move on to make sure container status is updated.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
|
||||||
|
// If FinishedAt has been set (e.g. with start failure), keep as
|
||||||
|
// it is.
|
||||||
|
if status.FinishedAt != 0 {
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
status.Pid = 0
|
||||||
|
status.FinishedAt = e.ExitedAt.UnixNano()
|
||||||
|
status.ExitCode = int32(e.ExitStatus)
|
||||||
|
return status, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Errorf("Failed to update container %q state", e.ContainerID)
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Using channel to propagate the information of container stop
|
||||||
|
cntr.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSandboxExit handles TaskExit event for sandbox.
|
||||||
|
func handleSandboxExit(e *eventtypes.TaskExit, sb sandboxstore.Sandbox) {
|
||||||
|
if e.Pid != sb.Status.Get().Pid {
|
||||||
|
// Non-init process died, ignore the event.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// No stream attached to sandbox container.
|
||||||
|
task, err := sb.Container.Task(context.Background(), nil)
|
||||||
|
if err != nil {
|
||||||
|
if !errdefs.IsNotFound(err) {
|
||||||
|
logrus.WithError(err).Errorf("failed to load task for sandbox %q", e.ContainerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
|
||||||
|
if _, err = task.Delete(context.Background()); err != nil {
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
if !errdefs.IsNotFound(err) {
|
||||||
|
logrus.WithError(err).Errorf("failed to stop sandbox %q", e.ContainerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Move on to make sure container status is updated.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = sb.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {
|
||||||
|
// NOTE(random-liu): We SHOULD NOT change UNKNOWN state here.
|
||||||
|
// If sandbox state is UNKNOWN when event monitor receives an TaskExit event,
|
||||||
|
// it means that sandbox start has failed. In that case, `RunPodSandbox` will
|
||||||
|
// cleanup everything immediately.
|
||||||
|
// Once sandbox state goes out of UNKNOWN, it becomes visable to the user, which
|
||||||
|
// is not what we want.
|
||||||
|
if status.State != sandboxstore.StateUnknown {
|
||||||
|
status.State = sandboxstore.StateNotReady
|
||||||
|
}
|
||||||
|
status.Pid = 0
|
||||||
|
return status, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Errorf("Failed to update sandbox %q state", e.ContainerID)
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -293,10 +293,63 @@ func loadSandbox(ctx context.Context, cntr containerd.Container) (sandboxstore.S
|
|||||||
return sandbox, fmt.Errorf("failed to unmarshal metadata extension %q: %v", ext, err)
|
return sandbox, fmt.Errorf("failed to unmarshal metadata extension %q: %v", ext, err)
|
||||||
}
|
}
|
||||||
meta := data.(*sandboxstore.Metadata)
|
meta := data.(*sandboxstore.Metadata)
|
||||||
sandbox = sandboxstore.Sandbox{
|
|
||||||
Metadata: *meta,
|
// Load sandbox created timestamp.
|
||||||
Container: cntr,
|
info, err := cntr.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return sandbox, fmt.Errorf("failed to get sandbox container info: %v", err)
|
||||||
}
|
}
|
||||||
|
createdAt := info.CreatedAt
|
||||||
|
|
||||||
|
// Load sandbox status.
|
||||||
|
t, err := cntr.Task(ctx, nil)
|
||||||
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
return sandbox, fmt.Errorf("failed to load task: %v", err)
|
||||||
|
}
|
||||||
|
var s containerd.Status
|
||||||
|
var notFound bool
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
// Task is not found.
|
||||||
|
notFound = true
|
||||||
|
} else {
|
||||||
|
// Task is found. Get task status.
|
||||||
|
s, err = t.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// It's still possible that task is deleted during this window.
|
||||||
|
if !errdefs.IsNotFound(err) {
|
||||||
|
return sandbox, fmt.Errorf("failed to get task status: %v", err)
|
||||||
|
}
|
||||||
|
notFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var state sandboxstore.State
|
||||||
|
var pid uint32
|
||||||
|
if notFound {
|
||||||
|
// Task does not exist, set sandbox state as NOTREADY.
|
||||||
|
state = sandboxstore.StateNotReady
|
||||||
|
} else {
|
||||||
|
if s.Status == containerd.Running {
|
||||||
|
// Task is running, set sandbox state as READY.
|
||||||
|
state = sandboxstore.StateReady
|
||||||
|
pid = t.Pid()
|
||||||
|
} else {
|
||||||
|
// Task is not running. Delete the task and set sandbox state as NOTREADY.
|
||||||
|
if _, err := t.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
return sandbox, fmt.Errorf("failed to delete task: %v", err)
|
||||||
|
}
|
||||||
|
state = sandboxstore.StateNotReady
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox = sandboxstore.NewSandbox(
|
||||||
|
*meta,
|
||||||
|
sandboxstore.Status{
|
||||||
|
Pid: pid,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
State: state,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
sandbox.Container = cntr
|
||||||
|
|
||||||
// Load network namespace.
|
// Load network namespace.
|
||||||
if meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork() {
|
if meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork() {
|
||||||
|
@ -17,12 +17,6 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
|
||||||
"github.com/containerd/containerd/api/types/task"
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
@ -33,39 +27,12 @@ import (
|
|||||||
func (c *criContainerdService) ListPodSandbox(ctx context.Context, r *runtime.ListPodSandboxRequest) (*runtime.ListPodSandboxResponse, error) {
|
func (c *criContainerdService) ListPodSandbox(ctx context.Context, r *runtime.ListPodSandboxRequest) (*runtime.ListPodSandboxResponse, error) {
|
||||||
// List all sandboxes from store.
|
// List all sandboxes from store.
|
||||||
sandboxesInStore := c.sandboxStore.List()
|
sandboxesInStore := c.sandboxStore.List()
|
||||||
|
|
||||||
response, err := c.client.TaskService().List(ctx, &tasks.ListTasksRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to list sandbox containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sandboxes []*runtime.PodSandbox
|
var sandboxes []*runtime.PodSandbox
|
||||||
for _, sandboxInStore := range sandboxesInStore {
|
for _, sandboxInStore := range sandboxesInStore {
|
||||||
var sandboxInContainerd *task.Process
|
sandboxes = append(sandboxes, toCRISandbox(
|
||||||
for _, s := range response.Tasks {
|
sandboxInStore.Metadata,
|
||||||
if s.ID == sandboxInStore.ID {
|
sandboxInStore.Status.Get(),
|
||||||
sandboxInContainerd = s
|
))
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sandbox state to NOTREADY by default.
|
|
||||||
state := runtime.PodSandboxState_SANDBOX_NOTREADY
|
|
||||||
// If the sandbox container is running, return the sandbox as READY.
|
|
||||||
if sandboxInContainerd != nil && sandboxInContainerd.Status == task.StatusRunning {
|
|
||||||
state = runtime.PodSandboxState_SANDBOX_READY
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := sandboxInStore.Container.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
// It's possible that container gets deleted during list.
|
|
||||||
if errdefs.IsNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to get sandbox container %q info: %v", sandboxInStore.ID, err)
|
|
||||||
}
|
|
||||||
createdAt := info.CreatedAt
|
|
||||||
sandboxes = append(sandboxes, toCRISandbox(sandboxInStore.Metadata, state, createdAt))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sandboxes = c.filterCRISandboxes(sandboxes, r.GetFilter())
|
sandboxes = c.filterCRISandboxes(sandboxes, r.GetFilter())
|
||||||
@ -73,12 +40,17 @@ func (c *criContainerdService) ListPodSandbox(ctx context.Context, r *runtime.Li
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toCRISandbox converts sandbox metadata into CRI pod sandbox.
|
// toCRISandbox converts sandbox metadata into CRI pod sandbox.
|
||||||
func toCRISandbox(meta sandboxstore.Metadata, state runtime.PodSandboxState, createdAt time.Time) *runtime.PodSandbox {
|
func toCRISandbox(meta sandboxstore.Metadata, status sandboxstore.Status) *runtime.PodSandbox {
|
||||||
|
// Set sandbox state to NOTREADY by default.
|
||||||
|
state := runtime.PodSandboxState_SANDBOX_NOTREADY
|
||||||
|
if status.State == sandboxstore.StateReady {
|
||||||
|
state = runtime.PodSandboxState_SANDBOX_READY
|
||||||
|
}
|
||||||
return &runtime.PodSandbox{
|
return &runtime.PodSandbox{
|
||||||
Id: meta.ID,
|
Id: meta.ID,
|
||||||
Metadata: meta.Config.GetMetadata(),
|
Metadata: meta.Config.GetMetadata(),
|
||||||
State: state,
|
State: state,
|
||||||
CreatedAt: createdAt.UnixNano(),
|
CreatedAt: status.CreatedAt.UnixNano(),
|
||||||
Labels: meta.Config.GetLabels(),
|
Labels: meta.Config.GetLabels(),
|
||||||
Annotations: meta.Config.GetAnnotations(),
|
Annotations: meta.Config.GetAnnotations(),
|
||||||
}
|
}
|
||||||
|
@ -44,90 +44,106 @@ func TestToCRISandbox(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
NetNSPath: "test-netns",
|
NetNSPath: "test-netns",
|
||||||
}
|
}
|
||||||
state := runtime.PodSandboxState_SANDBOX_READY
|
|
||||||
expect := &runtime.PodSandbox{
|
expect := &runtime.PodSandbox{
|
||||||
Id: "test-id",
|
Id: "test-id",
|
||||||
Metadata: config.GetMetadata(),
|
Metadata: config.GetMetadata(),
|
||||||
State: state,
|
|
||||||
CreatedAt: createdAt.UnixNano(),
|
CreatedAt: createdAt.UnixNano(),
|
||||||
Labels: config.GetLabels(),
|
Labels: config.GetLabels(),
|
||||||
Annotations: config.GetAnnotations(),
|
Annotations: config.GetAnnotations(),
|
||||||
}
|
}
|
||||||
s := toCRISandbox(meta, state, createdAt)
|
for desc, test := range map[string]struct {
|
||||||
assert.Equal(t, expect, s)
|
state sandboxstore.State
|
||||||
|
expectedState runtime.PodSandboxState
|
||||||
|
}{
|
||||||
|
"sandbox state ready": {
|
||||||
|
state: sandboxstore.StateReady,
|
||||||
|
expectedState: runtime.PodSandboxState_SANDBOX_READY,
|
||||||
|
},
|
||||||
|
"sandbox state not ready": {
|
||||||
|
state: sandboxstore.StateNotReady,
|
||||||
|
expectedState: runtime.PodSandboxState_SANDBOX_NOTREADY,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
status := sandboxstore.Status{
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
State: test.state,
|
||||||
|
}
|
||||||
|
expect.State = test.expectedState
|
||||||
|
s := toCRISandbox(meta, status)
|
||||||
|
assert.Equal(t, expect, s, desc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterSandboxes(t *testing.T) {
|
func TestFilterSandboxes(t *testing.T) {
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
sandboxes := []struct {
|
sandboxes := []sandboxstore.Sandbox{
|
||||||
sandbox sandboxstore.Sandbox
|
sandboxstore.NewSandbox(
|
||||||
state runtime.PodSandboxState
|
sandboxstore.Metadata{
|
||||||
}{
|
ID: "1abcdef",
|
||||||
{
|
Name: "sandboxname-1",
|
||||||
sandbox: sandboxstore.Sandbox{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Metadata: sandboxstore.Metadata{
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
ID: "1abcdef",
|
Name: "podname-1",
|
||||||
Name: "sandboxname-1",
|
Uid: "uid-1",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Namespace: "ns-1",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Attempt: 1,
|
||||||
Name: "podname-1",
|
|
||||||
Uid: "uid-1",
|
|
||||||
Namespace: "ns-1",
|
|
||||||
Attempt: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: runtime.PodSandboxState_SANDBOX_READY,
|
sandboxstore.Status{
|
||||||
},
|
CreatedAt: time.Now(),
|
||||||
{
|
State: sandboxstore.StateReady,
|
||||||
sandbox: sandboxstore.Sandbox{
|
},
|
||||||
Metadata: sandboxstore.Metadata{
|
),
|
||||||
ID: "2abcdef",
|
sandboxstore.NewSandbox(
|
||||||
Name: "sandboxname-2",
|
sandboxstore.Metadata{
|
||||||
Config: &runtime.PodSandboxConfig{
|
ID: "2abcdef",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Name: "sandboxname-2",
|
||||||
Name: "podname-2",
|
Config: &runtime.PodSandboxConfig{
|
||||||
Uid: "uid-2",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Namespace: "ns-2",
|
Name: "podname-2",
|
||||||
Attempt: 2,
|
Uid: "uid-2",
|
||||||
},
|
Namespace: "ns-2",
|
||||||
Labels: map[string]string{"a": "b"},
|
Attempt: 2,
|
||||||
},
|
},
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: runtime.PodSandboxState_SANDBOX_NOTREADY,
|
sandboxstore.Status{
|
||||||
},
|
CreatedAt: time.Now(),
|
||||||
{
|
State: sandboxstore.StateNotReady,
|
||||||
sandbox: sandboxstore.Sandbox{
|
},
|
||||||
Metadata: sandboxstore.Metadata{
|
),
|
||||||
ID: "3abcdef",
|
sandboxstore.NewSandbox(
|
||||||
Name: "sandboxname-3",
|
sandboxstore.Metadata{
|
||||||
Config: &runtime.PodSandboxConfig{
|
ID: "3abcdef",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Name: "sandboxname-3",
|
||||||
Name: "podname-2",
|
Config: &runtime.PodSandboxConfig{
|
||||||
Uid: "uid-2",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Namespace: "ns-2",
|
Name: "podname-2",
|
||||||
Attempt: 2,
|
Uid: "uid-2",
|
||||||
},
|
Namespace: "ns-2",
|
||||||
Labels: map[string]string{"c": "d"},
|
Attempt: 2,
|
||||||
},
|
},
|
||||||
|
Labels: map[string]string{"c": "d"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: runtime.PodSandboxState_SANDBOX_READY,
|
sandboxstore.Status{
|
||||||
},
|
CreatedAt: time.Now(),
|
||||||
|
State: sandboxstore.StateReady,
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create PodSandbox
|
// Create PodSandbox
|
||||||
testSandboxes := []*runtime.PodSandbox{}
|
testSandboxes := []*runtime.PodSandbox{}
|
||||||
createdAt := time.Now()
|
|
||||||
for _, sb := range sandboxes {
|
for _, sb := range sandboxes {
|
||||||
testSandboxes = append(testSandboxes, toCRISandbox(sb.sandbox.Metadata, sb.state, createdAt))
|
testSandboxes = append(testSandboxes, toCRISandbox(sb.Metadata, sb.Status.Get()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject test sandbox metadata
|
// Inject test sandbox metadata
|
||||||
for _, sb := range sandboxes {
|
for _, sb := range sandboxes {
|
||||||
assert.NoError(t, c.sandboxStore.Add(sb.sandbox))
|
assert.NoError(t, c.sandboxStore.Add(sb))
|
||||||
}
|
}
|
||||||
|
|
||||||
for desc, test := range map[string]struct {
|
for desc, test := range map[string]struct {
|
||||||
|
@ -24,10 +24,11 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
|
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
|
||||||
@ -37,16 +38,7 @@ func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortF
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to find sandbox %q: %v", r.GetPodSandboxId(), err)
|
return nil, fmt.Errorf("failed to find sandbox %q: %v", r.GetPodSandboxId(), err)
|
||||||
}
|
}
|
||||||
|
if sandbox.Status.Get().State != sandboxstore.StateReady {
|
||||||
t, err := sandbox.Container.Task(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get sandbox container task: %v", err)
|
|
||||||
}
|
|
||||||
status, err := t.Status(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get sandbox container status: %v", err)
|
|
||||||
}
|
|
||||||
if status.Status != containerd.Running {
|
|
||||||
return nil, errors.New("sandbox container is not running")
|
return nil, errors.New("sandbox container is not running")
|
||||||
}
|
}
|
||||||
// TODO(random-liu): Verify that ports are exposed.
|
// TODO(random-liu): Verify that ports are exposed.
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/cri-containerd/pkg/log"
|
"github.com/containerd/cri-containerd/pkg/log"
|
||||||
"github.com/containerd/cri-containerd/pkg/store"
|
"github.com/containerd/cri-containerd/pkg/store"
|
||||||
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemovePodSandbox removes the sandbox. If there are running containers in the
|
// RemovePodSandbox removes the sandbox. If there are running containers in the
|
||||||
@ -46,12 +47,8 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
|
|||||||
// Use the full sandbox id.
|
// Use the full sandbox id.
|
||||||
id := sandbox.ID
|
id := sandbox.ID
|
||||||
|
|
||||||
// Return error if sandbox container is not fully stopped.
|
// Return error if sandbox container is still running.
|
||||||
_, err = sandbox.Container.Task(ctx, nil)
|
if sandbox.Status.Get().State == sandboxstore.StateReady {
|
||||||
if err != nil && !errdefs.IsNotFound(err) {
|
|
||||||
return nil, fmt.Errorf("failed to get sandbox container info for %q: %v", id, err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return nil, fmt.Errorf("sandbox container %q is not fully stopped", id)
|
return nil, fmt.Errorf("sandbox container %q is not fully stopped", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
containerdio "github.com/containerd/containerd/cio"
|
containerdio "github.com/containerd/containerd/cio"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/linux/runctypes"
|
"github.com/containerd/containerd/linux/runctypes"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
@ -68,13 +69,16 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create initial internal sandbox object.
|
// Create initial internal sandbox object.
|
||||||
sandbox := sandboxstore.Sandbox{
|
sandbox := sandboxstore.NewSandbox(
|
||||||
Metadata: sandboxstore.Metadata{
|
sandboxstore.Metadata{
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: config,
|
Config: config,
|
||||||
},
|
},
|
||||||
}
|
sandboxstore.Status{
|
||||||
|
State: sandboxstore.StateUnknown,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Ensure sandbox container image snapshot.
|
// Ensure sandbox container image snapshot.
|
||||||
image, err := c.ensureImageExists(ctx, c.config.SandboxImage)
|
image, err := c.ensureImageExists(ctx, c.config.SandboxImage)
|
||||||
@ -224,33 +228,86 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create sandbox task in containerd.
|
// Update sandbox created timestamp.
|
||||||
log.Tracef("Create sandbox container (id=%q, name=%q).",
|
info, err := container.Info(ctx)
|
||||||
id, name)
|
|
||||||
// We don't need stdio for sandbox container.
|
|
||||||
task, err := container.NewTask(ctx, containerdio.NullIO)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create containerd task: %v", err)
|
return nil, fmt.Errorf("failed to get sandbox container info: %v", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
if err := sandbox.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {
|
||||||
if retErr != nil {
|
status.CreatedAt = info.CreatedAt
|
||||||
// Cleanup the sandbox container if an error is returned.
|
return status, nil
|
||||||
if _, err := task.Delete(ctx, containerd.WithProcessKill); err != nil {
|
}); err != nil {
|
||||||
logrus.WithError(err).Errorf("Failed to delete sandbox container %q", id)
|
return nil, fmt.Errorf("failed to update sandbox created timestamp: %v", err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = task.Start(ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to start sandbox container task %q: %v",
|
|
||||||
id, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sandbox into sandbox store.
|
// Add sandbox into sandbox store in UNKNOWN state.
|
||||||
sandbox.Container = container
|
sandbox.Container = container
|
||||||
if err := c.sandboxStore.Add(sandbox); err != nil {
|
if err := c.sandboxStore.Add(sandbox); err != nil {
|
||||||
return nil, fmt.Errorf("failed to add sandbox %+v into store: %v", sandbox, err)
|
return nil, fmt.Errorf("failed to add sandbox %+v into store: %v", sandbox, err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
// Delete sandbox from sandbox store if there is an error.
|
||||||
|
if retErr != nil {
|
||||||
|
c.sandboxStore.Delete(id)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// NOTE(random-liu): Sandbox state only stay in UNKNOWN state after this point
|
||||||
|
// and before the end of this function.
|
||||||
|
// * If `Update` succeeds, sandbox state will become READY in one transaction.
|
||||||
|
// * If `Update` fails, sandbox will be removed from the store in the defer above.
|
||||||
|
// * If cri-containerd stops at any point before `Update` finishes, because sandbox
|
||||||
|
// state is not checkpointed, it will be recovered from corresponding containerd task
|
||||||
|
// status during restart:
|
||||||
|
// * If the task is running, sandbox state will be READY,
|
||||||
|
// * Or else, sandbox state will be NOTREADY.
|
||||||
|
//
|
||||||
|
// In any case, sandbox will leave UNKNOWN state, so it's safe to ignore sandbox
|
||||||
|
// in UNKNOWN state in other functions.
|
||||||
|
|
||||||
|
// Start sandbox container in one transaction to avoid race condition with
|
||||||
|
// event monitor.
|
||||||
|
if err := sandbox.Status.Update(func(status sandboxstore.Status) (_ sandboxstore.Status, retErr error) {
|
||||||
|
// NOTE(random-liu): We should not change the sandbox state to NOTREADY
|
||||||
|
// if `Update` fails.
|
||||||
|
//
|
||||||
|
// If `Update` fails, the sandbox will be cleaned up by all the defers
|
||||||
|
// above. We should not let user see this sandbox, or else they will
|
||||||
|
// see the sandbox disappear after the defer clean up, which may confuse
|
||||||
|
// them.
|
||||||
|
//
|
||||||
|
// Given so, we should keep the sandbox in UNKNOWN state if `Update` fails,
|
||||||
|
// and ignore sandbox in UNKNOWN state in all the inspection functions.
|
||||||
|
|
||||||
|
// Create sandbox task in containerd.
|
||||||
|
log.Tracef("Create sandbox container (id=%q, name=%q).",
|
||||||
|
id, name)
|
||||||
|
// We don't need stdio for sandbox container.
|
||||||
|
task, err := container.NewTask(ctx, containerdio.NullIO)
|
||||||
|
if err != nil {
|
||||||
|
return status, fmt.Errorf("failed to create containerd task: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
// Cleanup the sandbox container if an error is returned.
|
||||||
|
// It's possible that task is deleted by event monitor.
|
||||||
|
if _, err := task.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
logrus.WithError(err).Errorf("Failed to delete sandbox container %q", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := task.Start(ctx); err != nil {
|
||||||
|
return status, fmt.Errorf("failed to start sandbox container task %q: %v",
|
||||||
|
id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pod sandbox as ready after successfully start sandbox container.
|
||||||
|
status.Pid = task.Pid()
|
||||||
|
status.State = sandboxstore.StateReady
|
||||||
|
return status, nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start sandbox container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,10 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
@ -38,32 +36,14 @@ func (c *criContainerdService) PodSandboxStatus(ctx context.Context, r *runtime.
|
|||||||
return nil, fmt.Errorf("an error occurred when try to find sandbox: %v", err)
|
return nil, fmt.Errorf("an error occurred when try to find sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err := sandbox.Container.Task(ctx, nil)
|
|
||||||
if err != nil && !errdefs.IsNotFound(err) {
|
|
||||||
return nil, fmt.Errorf("failed to get sandbox container task: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pid uint32
|
|
||||||
var processStatus containerd.ProcessStatus
|
|
||||||
// If the sandbox container is running, treat it as READY.
|
|
||||||
if task != nil {
|
|
||||||
taskStatus, err := task.Status(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get task status: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = task.Pid()
|
|
||||||
processStatus = taskStatus.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := c.getIP(sandbox)
|
ip := c.getIP(sandbox)
|
||||||
ctrInfo, err := sandbox.Container.Info(ctx)
|
status := toCRISandboxStatus(sandbox.Metadata, sandbox.Status.Get(), ip)
|
||||||
if err != nil {
|
if !r.GetVerbose() {
|
||||||
return nil, fmt.Errorf("failed to get sandbox container info: %v", err)
|
return &runtime.PodSandboxStatusResponse{Status: status}, nil
|
||||||
}
|
}
|
||||||
createdAt := ctrInfo.CreatedAt
|
|
||||||
status := toCRISandboxStatus(sandbox.Metadata, processStatus, createdAt, ip)
|
// Generate verbose information.
|
||||||
info, err := toCRISandboxInfo(ctx, sandbox, pid, processStatus, r.GetVerbose())
|
info, err := toCRISandboxInfo(ctx, sandbox)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get verbose sandbox container info: %v", err)
|
return nil, fmt.Errorf("failed to get verbose sandbox container info: %v", err)
|
||||||
}
|
}
|
||||||
@ -92,10 +72,10 @@ func (c *criContainerdService) getIP(sandbox sandboxstore.Sandbox) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toCRISandboxStatus converts sandbox metadata into CRI pod sandbox status.
|
// toCRISandboxStatus converts sandbox metadata into CRI pod sandbox status.
|
||||||
func toCRISandboxStatus(meta sandboxstore.Metadata, status containerd.ProcessStatus, createdAt time.Time, ip string) *runtime.PodSandboxStatus {
|
func toCRISandboxStatus(meta sandboxstore.Metadata, status sandboxstore.Status, ip string) *runtime.PodSandboxStatus {
|
||||||
// Set sandbox state to NOTREADY by default.
|
// Set sandbox state to NOTREADY by default.
|
||||||
state := runtime.PodSandboxState_SANDBOX_NOTREADY
|
state := runtime.PodSandboxState_SANDBOX_NOTREADY
|
||||||
if status == containerd.Running {
|
if status.State == sandboxstore.StateReady {
|
||||||
state = runtime.PodSandboxState_SANDBOX_READY
|
state = runtime.PodSandboxState_SANDBOX_READY
|
||||||
}
|
}
|
||||||
nsOpts := meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions()
|
nsOpts := meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions()
|
||||||
@ -103,7 +83,7 @@ func toCRISandboxStatus(meta sandboxstore.Metadata, status containerd.ProcessSta
|
|||||||
Id: meta.ID,
|
Id: meta.ID,
|
||||||
Metadata: meta.Config.GetMetadata(),
|
Metadata: meta.Config.GetMetadata(),
|
||||||
State: state,
|
State: state,
|
||||||
CreatedAt: createdAt.UnixNano(),
|
CreatedAt: status.CreatedAt.UnixNano(),
|
||||||
Network: &runtime.PodSandboxNetworkStatus{Ip: ip},
|
Network: &runtime.PodSandboxNetworkStatus{Ip: ip},
|
||||||
Linux: &runtime.LinuxPodSandboxStatus{
|
Linux: &runtime.LinuxPodSandboxStatus{
|
||||||
Namespaces: &runtime.Namespace{
|
Namespaces: &runtime.Namespace{
|
||||||
@ -132,14 +112,25 @@ type sandboxInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toCRISandboxInfo converts internal container object information to CRI sandbox status response info map.
|
// toCRISandboxInfo converts internal container object information to CRI sandbox status response info map.
|
||||||
func toCRISandboxInfo(ctx context.Context, sandbox sandboxstore.Sandbox,
|
func toCRISandboxInfo(ctx context.Context, sandbox sandboxstore.Sandbox) (map[string]string, error) {
|
||||||
pid uint32, processStatus containerd.ProcessStatus, verbose bool) (map[string]string, error) {
|
container := sandbox.Container
|
||||||
if !verbose {
|
task, err := container.Task(ctx, nil)
|
||||||
return nil, nil
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
return nil, fmt.Errorf("failed to get sandbox container task: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var processStatus containerd.ProcessStatus
|
||||||
|
if task != nil {
|
||||||
|
taskStatus, err := task.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get task status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
processStatus = taskStatus.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
si := &sandboxInfo{
|
si := &sandboxInfo{
|
||||||
Pid: pid,
|
Pid: sandbox.Status.Get().Pid,
|
||||||
Status: string(processStatus),
|
Status: string(processStatus),
|
||||||
Config: sandbox.Config,
|
Config: sandbox.Config,
|
||||||
}
|
}
|
||||||
@ -155,25 +146,22 @@ func toCRISandboxInfo(ctx context.Context, sandbox sandboxstore.Sandbox,
|
|||||||
si.NetNSClosed = (sandbox.NetNS == nil || sandbox.NetNS.Closed())
|
si.NetNSClosed = (sandbox.NetNS == nil || sandbox.NetNS.Closed())
|
||||||
}
|
}
|
||||||
|
|
||||||
container := sandbox.Container
|
|
||||||
spec, err := container.Spec(ctx)
|
spec, err := container.Spec(ctx)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
si.RuntimeSpec = spec
|
return nil, fmt.Errorf("failed to get sandbox container runtime spec: %v", err)
|
||||||
} else {
|
|
||||||
logrus.WithError(err).Errorf("Failed to get sandbox container %q runtime spec", sandbox.ID)
|
|
||||||
}
|
}
|
||||||
|
si.RuntimeSpec = spec
|
||||||
|
|
||||||
ctrInfo, err := container.Info(ctx)
|
ctrInfo, err := container.Info(ctx)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
// Do not use config.SandboxImage because the configuration might
|
return nil, fmt.Errorf("failed to get sandbox container info: %v", err)
|
||||||
// be changed during restart. It may not reflect the actual image
|
|
||||||
// used by the sandbox container.
|
|
||||||
si.Image = ctrInfo.Image
|
|
||||||
si.SnapshotKey = ctrInfo.SnapshotKey
|
|
||||||
si.Snapshotter = ctrInfo.Snapshotter
|
|
||||||
} else {
|
|
||||||
logrus.WithError(err).Errorf("Failed to get sandbox container %q info", sandbox.ID)
|
|
||||||
}
|
}
|
||||||
|
// Do not use config.SandboxImage because the configuration might
|
||||||
|
// be changed during restart. It may not reflect the actual image
|
||||||
|
// used by the sandbox container.
|
||||||
|
si.Image = ctrInfo.Image
|
||||||
|
si.SnapshotKey = ctrInfo.SnapshotKey
|
||||||
|
si.Snapshotter = ctrInfo.Snapshotter
|
||||||
|
|
||||||
infoBytes, err := json.Marshal(si)
|
infoBytes, err := json.Marshal(si)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
@ -52,12 +51,10 @@ func TestPodSandboxStatus(t *testing.T) {
|
|||||||
Labels: map[string]string{"a": "b"},
|
Labels: map[string]string{"a": "b"},
|
||||||
Annotations: map[string]string{"c": "d"},
|
Annotations: map[string]string{"c": "d"},
|
||||||
}
|
}
|
||||||
sandbox := &sandboxstore.Sandbox{
|
metadata := sandboxstore.Metadata{
|
||||||
Metadata: sandboxstore.Metadata{
|
ID: id,
|
||||||
ID: id,
|
Name: "test-name",
|
||||||
Name: "test-name",
|
Config: config,
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := &runtime.PodSandboxStatus{
|
expected := &runtime.PodSandboxStatus{
|
||||||
@ -77,21 +74,26 @@ func TestPodSandboxStatus(t *testing.T) {
|
|||||||
Labels: config.GetLabels(),
|
Labels: config.GetLabels(),
|
||||||
Annotations: config.GetAnnotations(),
|
Annotations: config.GetAnnotations(),
|
||||||
}
|
}
|
||||||
for _, status := range []containerd.ProcessStatus{
|
for desc, test := range map[string]struct {
|
||||||
"",
|
state sandboxstore.State
|
||||||
containerd.Running,
|
expectedState runtime.PodSandboxState
|
||||||
containerd.Created,
|
}{
|
||||||
containerd.Stopped,
|
"sandbox state ready": {
|
||||||
containerd.Paused,
|
state: sandboxstore.StateReady,
|
||||||
containerd.Pausing,
|
expectedState: runtime.PodSandboxState_SANDBOX_READY,
|
||||||
containerd.Unknown,
|
},
|
||||||
|
"sandbox state not ready": {
|
||||||
|
state: sandboxstore.StateNotReady,
|
||||||
|
expectedState: runtime.PodSandboxState_SANDBOX_NOTREADY,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
state := runtime.PodSandboxState_SANDBOX_NOTREADY
|
t.Logf("TestCase: %s", desc)
|
||||||
if status == containerd.Running {
|
status := sandboxstore.Status{
|
||||||
state = runtime.PodSandboxState_SANDBOX_READY
|
CreatedAt: createdAt,
|
||||||
|
State: test.state,
|
||||||
}
|
}
|
||||||
expected.State = state
|
expected.State = test.expectedState
|
||||||
got := toCRISandboxStatus(sandbox.Metadata, status, createdAt, ip)
|
got := toCRISandboxStatus(metadata, status, ip)
|
||||||
assert.Equal(t, expected, got)
|
assert.Equal(t, expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
@ -26,8 +27,14 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// stopCheckPollInterval is the the interval to check whether a sandbox
|
||||||
|
// is stopped successfully.
|
||||||
|
const stopCheckPollInterval = 100 * time.Millisecond
|
||||||
|
|
||||||
// StopPodSandbox stops the sandbox. If there are any running containers in the
|
// StopPodSandbox stops the sandbox. If there are any running containers in the
|
||||||
// sandbox, they should be forcibly terminated.
|
// sandbox, they should be forcibly terminated.
|
||||||
func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.StopPodSandboxRequest) (*runtime.StopPodSandboxResponse, error) {
|
func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.StopPodSandboxRequest) (*runtime.StopPodSandboxResponse, error) {
|
||||||
@ -89,14 +96,18 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
|
|||||||
return nil, fmt.Errorf("failed to unmount sandbox files in %q: %v", sandboxRoot, err)
|
return nil, fmt.Errorf("failed to unmount sandbox files in %q: %v", sandboxRoot, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.stopSandboxContainer(ctx, sandbox.Container); err != nil {
|
// Only stop sandbox container when it's running.
|
||||||
return nil, fmt.Errorf("failed to stop sandbox container %q: %v", id, err)
|
if sandbox.Status.Get().State == sandboxstore.StateReady {
|
||||||
|
if err := c.stopSandboxContainer(ctx, sandbox); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to stop sandbox container %q: %v", id, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &runtime.StopPodSandboxResponse{}, nil
|
return &runtime.StopPodSandboxResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopSandboxContainer kills and deletes sandbox container.
|
// stopSandboxContainer kills and deletes sandbox container.
|
||||||
func (c *criContainerdService) stopSandboxContainer(ctx context.Context, container containerd.Container) error {
|
func (c *criContainerdService) stopSandboxContainer(ctx context.Context, sandbox sandboxstore.Sandbox) error {
|
||||||
|
container := sandbox.Container
|
||||||
task, err := container.Task(ctx, nil)
|
task, err := container.Task(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
@ -111,5 +122,28 @@ func (c *criContainerdService) stopSandboxContainer(ctx context.Context, contain
|
|||||||
return fmt.Errorf("failed to delete sandbox container: %v", err)
|
return fmt.Errorf("failed to delete sandbox container: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return c.waitSandboxStop(ctx, sandbox, killContainerTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitSandboxStop polls sandbox state until timeout exceeds or sandbox is stopped.
|
||||||
|
func (c *criContainerdService) waitSandboxStop(ctx context.Context, sandbox sandboxstore.Sandbox, timeout time.Duration) error {
|
||||||
|
ticker := time.NewTicker(stopCheckPollInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
timeoutTimer := time.NewTimer(timeout)
|
||||||
|
defer timeoutTimer.Stop()
|
||||||
|
for {
|
||||||
|
// Poll once before waiting for stopCheckPollInterval.
|
||||||
|
// TODO(random-liu): Use channel with event handler instead of polling.
|
||||||
|
if sandbox.Status.Get().State == sandboxstore.StateNotReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return fmt.Errorf("wait sandbox container %q is cancelled", sandbox.ID)
|
||||||
|
case <-timeoutTimer.C:
|
||||||
|
return fmt.Errorf("wait sandbox container %q stop timeout", sandbox.ID)
|
||||||
|
case <-ticker.C:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
68
pkg/server/sandbox_stop_test.go
Normal file
68
pkg/server/sandbox_stop_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Containerd 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 (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
sandboxstore "github.com/containerd/cri-containerd/pkg/store/sandbox"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWaitSandboxStop(t *testing.T) {
|
||||||
|
id := "test-id"
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
state sandboxstore.State
|
||||||
|
cancel bool
|
||||||
|
timeout time.Duration
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
"should return error if timeout exceeds": {
|
||||||
|
state: sandboxstore.StateReady,
|
||||||
|
timeout: 2 * stopCheckPollInterval,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
"should return error if context is cancelled": {
|
||||||
|
state: sandboxstore.StateReady,
|
||||||
|
timeout: time.Hour,
|
||||||
|
cancel: true,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
"should not return error if sandbox is stopped before timeout": {
|
||||||
|
state: sandboxstore.StateNotReady,
|
||||||
|
timeout: time.Hour,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
c := newTestCRIContainerdService()
|
||||||
|
sandbox := sandboxstore.NewSandbox(
|
||||||
|
sandboxstore.Metadata{ID: id},
|
||||||
|
sandboxstore.Status{State: test.state},
|
||||||
|
)
|
||||||
|
ctx := context.Background()
|
||||||
|
if test.cancel {
|
||||||
|
cancelledCtx, cancel := context.WithCancel(ctx)
|
||||||
|
cancel()
|
||||||
|
ctx = cancelledCtx
|
||||||
|
}
|
||||||
|
err := c.waitSandboxStop(ctx, sandbox, test.timeout)
|
||||||
|
assert.Equal(t, test.expectErr, err != nil, desc)
|
||||||
|
}
|
||||||
|
}
|
@ -119,8 +119,6 @@ type StatusStorage interface {
|
|||||||
Delete() error
|
Delete() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(random-liu): Add factory function and configure checkpoint path.
|
|
||||||
|
|
||||||
// StoreStatus creates the storage containing the passed in container status with the
|
// StoreStatus creates the storage containing the passed in container status with the
|
||||||
// specified id.
|
// specified id.
|
||||||
// The status MUST be created in one transaction.
|
// The status MUST be created in one transaction.
|
||||||
|
@ -52,6 +52,8 @@ type Metadata struct {
|
|||||||
Config *runtime.PodSandboxConfig
|
Config *runtime.PodSandboxConfig
|
||||||
// NetNSPath is the network namespace used by the sandbox.
|
// NetNSPath is the network namespace used by the sandbox.
|
||||||
NetNSPath string
|
NetNSPath string
|
||||||
|
// IP of Pod if it is attached to non host network
|
||||||
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON encodes Metadata into bytes in json format.
|
// MarshalJSON encodes Metadata into bytes in json format.
|
||||||
|
@ -30,12 +30,21 @@ import (
|
|||||||
type Sandbox struct {
|
type Sandbox struct {
|
||||||
// Metadata is the metadata of the sandbox, it is immutable after created.
|
// Metadata is the metadata of the sandbox, it is immutable after created.
|
||||||
Metadata
|
Metadata
|
||||||
|
// Status stores the status of the sandbox.
|
||||||
|
Status StatusStorage
|
||||||
// Container is the containerd sandbox container client
|
// Container is the containerd sandbox container client
|
||||||
Container containerd.Container
|
Container containerd.Container
|
||||||
// CNI network namespace client
|
// CNI network namespace client
|
||||||
NetNS *NetNS
|
NetNS *NetNS
|
||||||
// IP of Pod if it is attached to non host network
|
}
|
||||||
IP string
|
|
||||||
|
// NewSandbox creates an internally used sandbox type. This functions reminds
|
||||||
|
// the caller that a sandbox must have a status.
|
||||||
|
func NewSandbox(metadata Metadata, status Status) Sandbox {
|
||||||
|
return Sandbox{
|
||||||
|
Metadata: metadata,
|
||||||
|
Status: StoreStatus(status),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store stores all sandboxes.
|
// Store stores all sandboxes.
|
||||||
@ -67,9 +76,22 @@ func (s *Store) Add(sb Sandbox) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the sandbox with specified id. Returns nil
|
// Get returns the sandbox with specified id. Returns store.ErrNotExist
|
||||||
// if the sandbox doesn't exist.
|
// if the sandbox doesn't exist.
|
||||||
func (s *Store) Get(id string) (Sandbox, error) {
|
func (s *Store) Get(id string) (Sandbox, error) {
|
||||||
|
sb, err := s.GetAll(id)
|
||||||
|
if err != nil {
|
||||||
|
return sb, err
|
||||||
|
}
|
||||||
|
if sb.Status.Get().State == StateUnknown {
|
||||||
|
return Sandbox{}, store.ErrNotExist
|
||||||
|
}
|
||||||
|
return sb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns the sandbox with specified id, including sandbox in unknown
|
||||||
|
// state. Returns store.ErrNotExist if the sandbox doesn't exist.
|
||||||
|
func (s *Store) GetAll(id string) (Sandbox, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
id, err := s.idIndex.Get(id)
|
id, err := s.idIndex.Get(id)
|
||||||
@ -91,6 +113,9 @@ func (s *Store) List() []Sandbox {
|
|||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
var sandboxes []Sandbox
|
var sandboxes []Sandbox
|
||||||
for _, sb := range s.sandboxes {
|
for _, sb := range s.sandboxes {
|
||||||
|
if sb.Status.Get().State == StateUnknown {
|
||||||
|
continue
|
||||||
|
}
|
||||||
sandboxes = append(sandboxes, sb)
|
sandboxes = append(sandboxes, sb)
|
||||||
}
|
}
|
||||||
return sandboxes
|
return sandboxes
|
||||||
|
@ -26,72 +26,96 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSandboxStore(t *testing.T) {
|
func TestSandboxStore(t *testing.T) {
|
||||||
metadatas := map[string]Metadata{
|
sandboxes := map[string]Sandbox{
|
||||||
"1": {
|
"1": NewSandbox(
|
||||||
ID: "1",
|
Metadata{
|
||||||
Name: "Sandbox-1",
|
ID: "1",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Name: "Sandbox-1",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Name: "TestPod-1",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Uid: "TestUid-1",
|
Name: "TestPod-1",
|
||||||
Namespace: "TestNamespace-1",
|
Uid: "TestUid-1",
|
||||||
Attempt: 1,
|
Namespace: "TestNamespace-1",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
NetNSPath: "TestNetNS-1",
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-1",
|
Status{State: StateReady},
|
||||||
},
|
),
|
||||||
"2abcd": {
|
"2abcd": NewSandbox(
|
||||||
ID: "2abcd",
|
Metadata{
|
||||||
Name: "Sandbox-2abcd",
|
ID: "2abcd",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Name: "Sandbox-2abcd",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Name: "TestPod-2abcd",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Uid: "TestUid-2abcd",
|
Name: "TestPod-2abcd",
|
||||||
Namespace: "TestNamespace-2abcd",
|
Uid: "TestUid-2abcd",
|
||||||
Attempt: 2,
|
Namespace: "TestNamespace-2abcd",
|
||||||
|
Attempt: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
NetNSPath: "TestNetNS-2",
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-2",
|
Status{State: StateNotReady},
|
||||||
},
|
),
|
||||||
"4a333": {
|
"4a333": NewSandbox(
|
||||||
ID: "4a333",
|
Metadata{
|
||||||
Name: "Sandbox-4a333",
|
ID: "4a333",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Name: "Sandbox-4a333",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Name: "TestPod-4a333",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Uid: "TestUid-4a333",
|
Name: "TestPod-4a333",
|
||||||
Namespace: "TestNamespace-4a333",
|
Uid: "TestUid-4a333",
|
||||||
Attempt: 3,
|
Namespace: "TestNamespace-4a333",
|
||||||
|
Attempt: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
NetNSPath: "TestNetNS-3",
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-3",
|
Status{State: StateNotReady},
|
||||||
},
|
),
|
||||||
"4abcd": {
|
"4abcd": NewSandbox(
|
||||||
ID: "4abcd",
|
Metadata{
|
||||||
Name: "Sandbox-4abcd",
|
ID: "4abcd",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Name: "Sandbox-4abcd",
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Name: "TestPod-4abcd",
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Uid: "TestUid-4abcd",
|
Name: "TestPod-4abcd",
|
||||||
Namespace: "TestNamespace-4abcd",
|
Uid: "TestUid-4abcd",
|
||||||
Attempt: 1,
|
Namespace: "TestNamespace-4abcd",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
NetNSPath: "TestNetNS-4abcd",
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-4abcd",
|
Status{State: StateReady},
|
||||||
},
|
),
|
||||||
}
|
}
|
||||||
|
unknown := NewSandbox(
|
||||||
|
Metadata{
|
||||||
|
ID: "3defg",
|
||||||
|
Name: "Sandbox-3defg",
|
||||||
|
Config: &runtime.PodSandboxConfig{
|
||||||
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
|
Name: "TestPod-3defg",
|
||||||
|
Uid: "TestUid-3defg",
|
||||||
|
Namespace: "TestNamespace-3defg",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetNSPath: "TestNetNS-3defg",
|
||||||
|
},
|
||||||
|
Status{State: StateUnknown},
|
||||||
|
)
|
||||||
assert := assertlib.New(t)
|
assert := assertlib.New(t)
|
||||||
sandboxes := map[string]Sandbox{}
|
|
||||||
for id := range metadatas {
|
|
||||||
sandboxes[id] = Sandbox{Metadata: metadatas[id]}
|
|
||||||
}
|
|
||||||
|
|
||||||
s := NewStore()
|
s := NewStore()
|
||||||
|
|
||||||
t.Logf("should be able to add sandbox")
|
t.Logf("should be able to add sandbox")
|
||||||
for _, sb := range sandboxes {
|
for _, sb := range sandboxes {
|
||||||
assert.NoError(s.Add(sb))
|
assert.NoError(s.Add(sb))
|
||||||
}
|
}
|
||||||
|
assert.NoError(s.Add(unknown))
|
||||||
|
|
||||||
t.Logf("should be able to get sandbox")
|
t.Logf("should be able to get sandbox")
|
||||||
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||||
@ -101,6 +125,16 @@ func TestSandboxStore(t *testing.T) {
|
|||||||
assert.Equal(sb, got)
|
assert.Equal(sb, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("should not be able to get unknown sandbox")
|
||||||
|
got, err := s.Get(unknown.ID)
|
||||||
|
assert.Equal(store.ErrNotExist, err)
|
||||||
|
assert.Equal(Sandbox{}, got)
|
||||||
|
|
||||||
|
t.Logf("should be able to get unknown sandbox with GetAll")
|
||||||
|
got, err = s.GetAll(unknown.ID)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(unknown, got)
|
||||||
|
|
||||||
t.Logf("should be able to list sandboxes")
|
t.Logf("should be able to list sandboxes")
|
||||||
sbs := s.List()
|
sbs := s.List()
|
||||||
assert.Len(sbs, len(sandboxes))
|
assert.Len(sbs, len(sandboxes))
|
||||||
|
100
pkg/store/sandbox/status.go
Normal file
100
pkg/store/sandbox/status.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Containerd 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 sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State is the sandbox state we use in cri-containerd.
|
||||||
|
// It has unknown state defined.
|
||||||
|
type State uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StateUnknown is unknown state of sandbox. Sandbox
|
||||||
|
// is in unknown state before its corresponding sandbox container
|
||||||
|
// is created. Sandbox in unknown state should be ignored by most
|
||||||
|
// functions, unless the caller needs to update sandbox state.
|
||||||
|
StateUnknown State = iota
|
||||||
|
// StateReady is ready state, it means sandbox container
|
||||||
|
// is running.
|
||||||
|
StateReady
|
||||||
|
// StateNotReady is notready state, it ONLY means sandbox
|
||||||
|
// container is not running.
|
||||||
|
// StopPodSandbox should still be called for NOTREADY sandbox to
|
||||||
|
// cleanup resources other than sandbox container, e.g. network namespace.
|
||||||
|
// This is an assumption made in CRI.
|
||||||
|
StateNotReady
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status is the status of a sandbox.
|
||||||
|
type Status struct {
|
||||||
|
// Pid is the init process id of the sandbox container.
|
||||||
|
Pid uint32
|
||||||
|
// CreatedAt is the created timestamp.
|
||||||
|
CreatedAt time.Time
|
||||||
|
// State is the state of the sandbox.
|
||||||
|
State State
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFunc is function used to update the sandbox status. If there
|
||||||
|
// is an error, the update will be rolled back.
|
||||||
|
type UpdateFunc func(Status) (Status, error)
|
||||||
|
|
||||||
|
// StatusStorage manages the sandbox status.
|
||||||
|
// The status storage for sandbox is different from container status storage,
|
||||||
|
// because we don't checkpoint sandbox status. If we need checkpoint in the
|
||||||
|
// future, we should combine this with container status storage.
|
||||||
|
type StatusStorage interface {
|
||||||
|
// Get a sandbox status.
|
||||||
|
Get() Status
|
||||||
|
// Update the sandbox status. Note that the update MUST be applied
|
||||||
|
// in one transaction.
|
||||||
|
Update(UpdateFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreStatus creates the storage containing the passed in sandbox status with the
|
||||||
|
// specified id.
|
||||||
|
// The status MUST be created in one transaction.
|
||||||
|
func StoreStatus(status Status) StatusStorage {
|
||||||
|
return &statusStorage{status: status}
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusStorage struct {
|
||||||
|
sync.RWMutex
|
||||||
|
status Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a copy of sandbox status.
|
||||||
|
func (s *statusStorage) Get() Status {
|
||||||
|
s.RLock()
|
||||||
|
defer s.RUnlock()
|
||||||
|
return s.status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the sandbox status.
|
||||||
|
func (s *statusStorage) Update(u UpdateFunc) error {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
newStatus, err := u(s.status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.status = newStatus
|
||||||
|
return nil
|
||||||
|
}
|
61
pkg/store/sandbox/status_test.go
Normal file
61
pkg/store/sandbox/status_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Containerd 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 sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assertlib "github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatus(t *testing.T) {
|
||||||
|
testStatus := Status{
|
||||||
|
Pid: 123,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
State: StateUnknown,
|
||||||
|
}
|
||||||
|
updateStatus := Status{
|
||||||
|
Pid: 456,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
State: StateReady,
|
||||||
|
}
|
||||||
|
updateErr := errors.New("update error")
|
||||||
|
assert := assertlib.New(t)
|
||||||
|
|
||||||
|
t.Logf("simple store and get")
|
||||||
|
s := StoreStatus(testStatus)
|
||||||
|
old := s.Get()
|
||||||
|
assert.Equal(testStatus, old)
|
||||||
|
|
||||||
|
t.Logf("failed update should not take effect")
|
||||||
|
err := s.Update(func(o Status) (Status, error) {
|
||||||
|
o = updateStatus
|
||||||
|
return o, updateErr
|
||||||
|
})
|
||||||
|
assert.Equal(updateErr, err)
|
||||||
|
assert.Equal(testStatus, s.Get())
|
||||||
|
|
||||||
|
t.Logf("successful update should take effect but not checkpoint")
|
||||||
|
err = s.Update(func(o Status) (Status, error) {
|
||||||
|
o = updateStatus
|
||||||
|
return o, nil
|
||||||
|
})
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(updateStatus, s.Get())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user