Implement UpdatePodSandboxResources CRI API handler

Signed-off-by: Chris Henzie <chrishenzie@google.com>
This commit is contained in:
Chris Henzie committed 2026-01-07 09:44:46 -08:00
1 parent a800acbac8
commit ffd3691c92
9 files changed
+258 -27

No files matched your search

+2
View File
@@ -85,6 +85,8 @@ type PodSandboxManager interface {
// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
RunPodSandbox(config *runtimeapi.PodSandboxConfig, runtimeHandler string, opts ...grpc.CallOption) (string, error)
// UpdatePodSandboxResources updates the cgroup resources for the sandbox.
UpdatePodSandboxResources(podSandboxID string, overhead *runtimeapi.LinuxContainerResources, resources *runtimeapi.LinuxContainerResources, opts ...grpc.CallOption) error
// StopPodSandbox stops the sandbox. If there are any running containers in the
// sandbox, they should be force terminated.
StopPodSandbox(podSandboxID string, opts ...grpc.CallOption) error
+107 -23
View File
@@ -531,6 +531,48 @@ func TestNriLinuxMemsetAdjustmentUpdate(t *testing.T) {
require.Equal(t, expected, string(chk), "check result")
}
// Test pod sandbox updates received by NRI plugins.
func TestNriUpdatePodSandbox(t *testing.T) {
skipNriTestIfNecessary(t)
t.Log("Test that NRI plugins receive pod sandbox updates.")
var (
tc = &nriTest{
t: t,
plugins: []*mockPlugin{{}},
}
overheadMemLimit int64
resMemLimit int64
updateMemLimit = func(mp *mockPlugin, pod *api.PodSandbox, overhead *api.LinuxResources, res *api.LinuxResources) error {
overheadMemLimit = overhead.GetMemory().GetLimit().GetValue()
resMemLimit = res.GetMemory().GetLimit().GetValue()
return nil
}
)
tc.plugins[0].updatePodSandbox = updateMemLimit
tc.setup()
podID := tc.runPod("pod0")
_ = tc.startContainer(podID, "ctr0")
overhead := &runtime.LinuxContainerResources{MemoryLimitInBytes: 10}
res := &runtime.LinuxContainerResources{MemoryLimitInBytes: 5}
err := tc.runtime.UpdatePodSandboxResources(podID, overhead, res)
assert.NoError(tc.t, err, "update pod sandbox")
timeout := time.After(pluginSyncTimeout)
err = tc.plugins[0].Wait(PodSandboxEvent(tc.plugins[0].pods[podID], UpdatePodSandbox), timeout)
assert.NoError(tc.t, err, "wait for update pod sandbox")
err = tc.plugins[0].Wait(PodSandboxEvent(tc.plugins[0].pods[podID], PostUpdatePodSandbox), timeout)
assert.NoError(tc.t, err, "wait for post update pod sandbox")
assert.Equal(t, overheadMemLimit, overhead.MemoryLimitInBytes)
assert.Equal(t, resMemLimit, res.MemoryLimitInBytes)
}
// Test NRI vs. containerd restart.
func TestNriPluginContainerdRestart(t *testing.T) {
skipNriTestIfNecessary(t)
@@ -706,18 +748,20 @@ type mockPlugin struct {
pods map[string]*api.PodSandbox
ctrs map[string]*api.Container
closed bool
namespace string
logf func(string, ...interface{})
synchronize func(*mockPlugin, []*api.PodSandbox, []*api.Container) ([]*api.ContainerUpdate, error)
runPodSandbox func(*mockPlugin, *api.PodSandbox) error
stopPodSandbox func(*mockPlugin, *api.PodSandbox) error
removePodSandbox func(*mockPlugin, *api.PodSandbox) error
createContainer func(*mockPlugin, *api.PodSandbox, *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error)
postCreateContainer func(*mockPlugin, *api.PodSandbox, *api.Container)
updateContainer func(*mockPlugin, *api.PodSandbox, *api.Container) ([]*api.ContainerUpdate, error)
postUpdateContainer func(*mockPlugin, *api.PodSandbox, *api.Container)
stopContainer func(*mockPlugin, *api.PodSandbox, *api.Container) ([]*api.ContainerUpdate, error)
closed bool
namespace string
logf func(string, ...interface{})
synchronize func(*mockPlugin, []*api.PodSandbox, []*api.Container) ([]*api.ContainerUpdate, error)
runPodSandbox func(*mockPlugin, *api.PodSandbox) error
updatePodSandbox func(*mockPlugin, *api.PodSandbox, *api.LinuxResources, *api.LinuxResources) error
postUpdatePodSandbox func(*mockPlugin, *api.PodSandbox) error
stopPodSandbox func(*mockPlugin, *api.PodSandbox) error
removePodSandbox func(*mockPlugin, *api.PodSandbox) error
createContainer func(*mockPlugin, *api.PodSandbox, *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error)
postCreateContainer func(*mockPlugin, *api.PodSandbox, *api.Container)
updateContainer func(*mockPlugin, *api.PodSandbox, *api.Container) ([]*api.ContainerUpdate, error)
postUpdateContainer func(*mockPlugin, *api.PodSandbox, *api.Container)
stopContainer func(*mockPlugin, *api.PodSandbox, *api.Container) ([]*api.ContainerUpdate, error)
}
func (m *mockPlugin) Start() error {
@@ -733,6 +777,8 @@ func (m *mockPlugin) Start() error {
if m.mask == 0 {
m.mask.Set(
api.Event_RUN_POD_SANDBOX,
api.Event_UPDATE_POD_SANDBOX,
api.Event_POST_UPDATE_POD_SANDBOX,
api.Event_STOP_POD_SANDBOX,
api.Event_REMOVE_POD_SANDBOX,
api.Event_CREATE_CONTAINER,
@@ -768,6 +814,12 @@ func (m *mockPlugin) Start() error {
if m.runPodSandbox == nil {
m.runPodSandbox = nopRunPodSandbox
}
if m.updatePodSandbox == nil {
m.updatePodSandbox = nopUpdatePodSandbox
}
if m.postUpdatePodSandbox == nil {
m.postUpdatePodSandbox = nopPostUpdatePodSandbox
}
if m.stopPodSandbox == nil {
m.stopPodSandbox = nopStopPodSandbox
}
@@ -860,6 +912,28 @@ func (m *mockPlugin) RunPodSandbox(ctx context.Context, pod *api.PodSandbox) err
return m.runPodSandbox(m, pod)
}
func (m *mockPlugin) UpdatePodSandbox(ctx context.Context, pod *api.PodSandbox, overhead *api.LinuxResources, res *api.LinuxResources) error {
if !m.inNamespace(pod.Namespace) {
return nil
}
m.Log("UpdatePodSandbox %s/%s", pod.Namespace, pod.Name)
m.pods[pod.Id] = pod
m.q.Add(PodSandboxEvent(pod, UpdatePodSandbox))
return m.updatePodSandbox(m, pod, overhead, res)
}
func (m *mockPlugin) PostUpdatePodSandbox(ctx context.Context, pod *api.PodSandbox) error {
if !m.inNamespace(pod.Namespace) {
return nil
}
m.Log("PostUpdatePodSandbox %s/%s", pod.Namespace, pod.Name)
m.pods[pod.Id] = pod
m.q.Add(PodSandboxEvent(pod, PostUpdatePodSandbox))
return m.postUpdatePodSandbox(m, pod)
}
func (m *mockPlugin) StopPodSandbox(ctx context.Context, pod *api.PodSandbox) error {
if !m.inNamespace(pod.Namespace) {
return nil
@@ -994,6 +1068,14 @@ func nopRunPodSandbox(*mockPlugin, *api.PodSandbox) error {
return nil
}
func nopUpdatePodSandbox(*mockPlugin, *api.PodSandbox, *api.LinuxResources, *api.LinuxResources) error {
return nil
}
func nopPostUpdatePodSandbox(*mockPlugin, *api.PodSandbox) error {
return nil
}
func nopStopPodSandbox(*mockPlugin, *api.PodSandbox) error {
return nil
}
@@ -1031,17 +1113,19 @@ const (
Disconnected = "closed"
Stopped = "stopped"
RunPodSandbox = "RunPodSandbox"
StopPodSandbox = "StopPodSandbox"
RemovePodSandbox = "RemovePodSandbox"
CreateContainer = "CreateContainer"
StartContainer = "StartContainer"
UpdateContainer = "UpdateContainer"
StopContainer = "StopContainer"
RemoveContainer = "RemoveContainer"
PostCreateContainer = "PostCreateContainer"
PostStartContainer = "PostStartContainer"
PostUpdateContainer = "PostUpdateContainer"
RunPodSandbox = "RunPodSandbox"
UpdatePodSandbox = "UpdatePodSandbox"
PostUpdatePodSandbox = "PostUpdatePodSandbox"
StopPodSandbox = "StopPodSandbox"
RemovePodSandbox = "RemovePodSandbox"
CreateContainer = "CreateContainer"
StartContainer = "StartContainer"
UpdateContainer = "UpdateContainer"
StopContainer = "StopContainer"
RemoveContainer = "RemoveContainer"
PostCreateContainer = "PostCreateContainer"
PostStartContainer = "PostStartContainer"
PostUpdateContainer = "PostUpdateContainer"
Error = "Error"
Timeout = ""
+22
View File
@@ -144,6 +144,28 @@ func (r *RuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig, runt
return resp.PodSandboxId, nil
}
// UpdatePodSandboxResources updates the cgroup resources for the sandbox.
func (r *RuntimeService) UpdatePodSandboxResources(podSandBoxID string, overhead *runtimeapi.LinuxContainerResources, resources *runtimeapi.LinuxContainerResources, opts ...grpc.CallOption) error {
log.L.Infof("[RuntimeService] UpdatePodSandboxResources (podSandboxID=%v, timeout=%v)", podSandBoxID, r.timeout)
ctx, cancel := getContextWithTimeout(r.timeout)
defer cancel()
_, err := r.runtimeClient.UpdatePodSandboxResources(ctx, &runtimeapi.UpdatePodSandboxResourcesRequest{
PodSandboxId: podSandBoxID,
Overhead: overhead,
Resources: resources,
}, opts...)
if err != nil {
log.L.WithError(err).Errorf("UpdatePodSandboxResources %q from runtime service failed", podSandBoxID)
return err
}
log.L.Infof("[RuntimeService] UpdatePodSandboxResources Response (podSandboxID=%v)", podSandBoxID)
return nil
}
// StopPodSandbox stops the sandbox. If there are any running containers in the
// sandbox, they should be forced to termination.
func (r *RuntimeService) StopPodSandbox(podSandBoxID string, opts ...grpc.CallOption) error {
@@ -18,7 +18,6 @@ package instrument
import (
"context"
"errors"
"github.com/containerd/errdefs"
"github.com/containerd/errdefs/pkg/errgrpc"
@@ -656,5 +655,17 @@ func (in *instrumentedService) RuntimeConfig(ctx context.Context, r *runtime.Run
}
func (in *instrumentedService) UpdatePodSandboxResources(ctx context.Context, r *runtime.UpdatePodSandboxResourcesRequest) (res *runtime.UpdatePodSandboxResourcesResponse, err error) {
return nil, errors.New("not implemented yet")
if err := in.checkInitialized(); err != nil {
return nil, err
}
log.G(ctx).Infof("UpdatePodSandboxResources for %q with Overhead: %+v / Resources: %+v", r.GetPodSandboxId(), r.GetOverhead(), r.GetResources())
defer func() {
if err != nil {
log.G(ctx).WithError(err).Errorf("UpdatePodSandboxResources for %q failed", r.GetPodSandboxId())
} else {
log.G(ctx).Infof("UpdatePodSandboxResources for %q returns successfully", r.GetPodSandboxId())
}
}()
res, err = in.c.UpdatePodSandboxResources(ctrdutil.WithNamespace(ctx), r)
return res, errgrpc.ToGRPC(err)
}
+22
View File
@@ -94,6 +94,28 @@ func (a *API) RunPodSandbox(ctx context.Context, criPod *sstore.Sandbox) error {
return err
}
func (a *API) UpdatePodSandboxResources(ctx context.Context, criPod *sstore.Sandbox, overhead *cri.LinuxContainerResources, req *cri.LinuxContainerResources) error {
if a.IsDisabled() {
return nil
}
pod := a.nriPodSandbox(criPod)
err := a.nri.UpdatePodSandbox(ctx, pod, fromCRILinuxResources(overhead), fromCRILinuxResources(req))
return err
}
func (a *API) PostUpdatePodSandboxResources(ctx context.Context, criPod *sstore.Sandbox) error {
if a.IsDisabled() {
return nil
}
pod := a.nriPodSandbox(criPod)
err := a.nri.PostUpdatePodSandbox(ctx, pod)
return err
}
func (a *API) StopPodSandbox(ctx context.Context, criPod *sstore.Sandbox) error {
if a.IsDisabled() {
return nil
+8
View File
@@ -57,6 +57,14 @@ func (*API) RunPodSandbox(context.Context, *sstore.Sandbox) error {
return nil
}
func (a *API) UpdatePodSandboxResources(context.Context, *sstore.Sandbox, *cri.LinuxContainerResources, *cri.LinuxContainerResources) error {
return nil
}
func (a *API) PostUpdatePodSandboxResources(context.Context, *sstore.Sandbox) error {
return nil
}
func (*API) StopPodSandbox(context.Context, *sstore.Sandbox) error {
return nil
}
@@ -18,11 +18,50 @@ package server
import (
"context"
"errors"
"fmt"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
sstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox"
"github.com/containerd/containerd/v2/pkg/tracing"
"github.com/containerd/log"
)
func (c *criService) UpdatePodSandboxResources(ctx context.Context, r *runtime.UpdatePodSandboxResourcesRequest) (*runtime.UpdatePodSandboxResourcesResponse, error) {
return nil, errors.New("not implemented yet")
span := tracing.SpanFromContext(ctx)
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
if err != nil {
return nil, fmt.Errorf("failed to find sandbox: %w", err)
}
span.SetAttributes(tracing.Attribute("sandbox.id", sandbox.ID))
overhead := r.GetOverhead()
resources := r.GetResources()
err = c.nri.UpdatePodSandboxResources(ctx, &sandbox, overhead, resources)
if err != nil {
return nil, fmt.Errorf("NRI sandbox update failed: %w", err)
}
err = sandbox.Status.Update(
func(status sstore.Status) (sstore.Status, error) {
status.Overhead = &runtime.ContainerResources{
Linux: overhead,
}
status.Resources = &runtime.ContainerResources{
Linux: resources,
}
return status, nil
},
)
if err != nil {
return nil, fmt.Errorf("failed to update sandbox store: %w", err)
}
err = c.nri.PostUpdatePodSandboxResources(ctx, &sandbox)
if err != nil {
log.G(ctx).WithError(err).Errorf("NRI post-update notification failed")
}
return &runtime.UpdatePodSandboxResourcesResponse{}, nil
}
+4
View File
@@ -105,6 +105,10 @@ type Status struct {
ExitStatus uint32
// State is the state of the sandbox.
State State
// Overhead has sandbox resource overhead.
Overhead *runtime.ContainerResources
// Resources has sandbox resource constraints.
Resources *runtime.ContainerResources
}
// UpdateFunc is function used to update the sandbox status. If there
+39
View File
@@ -48,6 +48,12 @@ type API interface {
// RunPodSandbox relays pod creation events to NRI.
RunPodSandbox(context.Context, PodSandbox) error
// UpdatePodSandbox relays pod update requests to NRI.
UpdatePodSandbox(context.Context, PodSandbox, *nri.LinuxResources, *nri.LinuxResources) error
// PostUpdatePodSandbox relays successful pod update events to NRI.
PostUpdatePodSandbox(context.Context, PodSandbox) error
// StopPodSandbox relays pod shutdown events to NRI.
StopPodSandbox(context.Context, PodSandbox) error
@@ -184,6 +190,39 @@ func (l *local) RunPodSandbox(ctx context.Context, pod PodSandbox) error {
return err
}
func (l *local) UpdatePodSandbox(ctx context.Context, pod PodSandbox, overhead *nri.LinuxResources, req *nri.LinuxResources) error {
if !l.IsEnabled() {
return nil
}
l.Lock()
defer l.Unlock()
request := &nri.UpdatePodSandboxRequest{
Pod: podSandboxToNRI(pod),
OverheadLinuxResources: overhead,
LinuxResources: req,
}
_, err := l.nri.UpdatePodSandbox(ctx, request)
return err
}
func (l *local) PostUpdatePodSandbox(ctx context.Context, pod PodSandbox) error {
if !l.IsEnabled() {
return nil
}
l.Lock()
defer l.Unlock()
request := &nri.PostUpdatePodSandboxRequest{
Pod: podSandboxToNRI(pod),
}
return l.nri.PostUpdatePodSandbox(ctx, request)
}
func (l *local) StopPodSandbox(ctx context.Context, pod PodSandbox) error {
if !l.IsEnabled() {
return nil