Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/containerd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ func (pc *proxyClients) getClient(address string) (*grpc.ClientConn, error) {
Backoff: backoffConfig,
}
gopts := []grpc.DialOption{
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(connParams),
grpc.WithContextDialer(dialer.ContextDialer),
Expand Down
8 changes: 8 additions & 0 deletions internal/cri/server/sandbox_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ func (c *criSandboxService) ShutdownSandbox(ctx context.Context, sandboxer strin
return ctrl.Shutdown(ctx, sandboxID)
}

func (c *criSandboxService) UpdateSandbox(ctx context.Context, sandboxer string, sandboxID string, sandbox sandbox.Sandbox, fields ...string) error {
ctrl, err := c.SandboxController(sandboxer)
if err != nil {
return err
}
return ctrl.Update(ctx, sandboxID, sandbox, fields...)
}

func (c *criSandboxService) StopSandbox(ctx context.Context, sandboxer, sandboxID string, opts ...sandbox.StopOpt) error {
ctrl, err := c.SandboxController(sandboxer)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions internal/cri/server/sandbox_update_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/containerd/containerd/v2/internal/cri/server/podsandbox"
sstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox"
"github.com/containerd/containerd/v2/pkg/tracing"
"github.com/containerd/errdefs"
"github.com/containerd/log"
)

Expand Down Expand Up @@ -75,6 +76,14 @@ func (c *criService) UpdatePodSandboxResources(ctx context.Context, r *runtime.U
return nil, fmt.Errorf("failed to update sandbox %s in core store: %w", sandbox.ID, err)
}

if err := c.sandboxService.UpdateSandbox(ctx, sandboxInfo.Sandboxer, sandboxInfo.ID, sandboxInfo, "extensions"); err != nil {
// Tolerate these errors for older sandbox controllers that may not support this yet.
if !errdefs.IsNotImplemented(err) {
return nil, fmt.Errorf("failed to update sandbox controller: %w", err)
}
log.G(ctx).Tracef("sandbox controller %q does not implement Update endpoint", sandboxInfo.Sandboxer)
}

err = c.nri.PostUpdatePodSandboxResources(ctx, &sandbox)
if err != nil {
log.G(ctx).WithError(err).Errorf("NRI post-update notification failed")
Expand Down
223 changes: 223 additions & 0 deletions internal/cri/server/sandbox_update_resources_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
Copyright 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 (
"context"
"testing"

containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/core/sandbox"
"github.com/containerd/containerd/v2/internal/cri/server/podsandbox"
sstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox"
"github.com/containerd/errdefs"
"github.com/containerd/typeurl/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
)

type fakeSandboxStore struct {
sandbox.Store
updatedSandbox *sandbox.Sandbox
getErr error
updateErr error
}

func (f *fakeSandboxStore) Get(_ context.Context, id string) (sandbox.Sandbox, error) {
if f.getErr != nil {
return sandbox.Sandbox{}, f.getErr
}
return sandbox.Sandbox{ID: id, Extensions: map[string]typeurl.Any{}}, nil
}

func (f *fakeSandboxStore) Update(_ context.Context, sb sandbox.Sandbox, _ ...string) (sandbox.Sandbox, error) {
if f.updateErr != nil {
return sandbox.Sandbox{}, f.updateErr
}
f.updatedSandbox = &sb
return sb, nil
}

type recordSandboxService struct {
fakeSandboxService
calledID string
returnErr error
}

func (s *recordSandboxService) UpdateSandbox(_ context.Context, _ string, sandboxID string, _ sandbox.Sandbox, _ ...string) error {
s.calledID = sandboxID
return s.returnErr
}

func TestUpdatePodSandboxResources(t *testing.T) {
mySandbox := sstore.Metadata{
ID: "test-sandbox-id",
Config: &runtime.PodSandboxConfig{
Metadata: &runtime.PodSandboxMetadata{Name: "test-name"},
},
}

t.Run("fails when sandbox not found in local store", func(t *testing.T) {
c := newTestCRIService(func(*criService) {})
req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "missing-sandbox-id",
}
_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to find sandbox")
})

t.Run("fails when core store get fails", func(t *testing.T) {
fakeStore := &fakeSandboxStore{getErr: errdefs.ErrNotFound}
c := newTestCRIService(func(service *criService) {
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(fakeStore)))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
}
_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to get sandbox test-sandbox-id from sandbox store")
})

t.Run("fails when core store update fails", func(t *testing.T) {
fakeStore := &fakeSandboxStore{updateErr: errdefs.ErrUnavailable}
c := newTestCRIService(func(service *criService) {
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(fakeStore)))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
}
_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to update sandbox test-sandbox-id in core store")
})

t.Run("successfully extracts and delegates resources to stores", func(t *testing.T) {
fakeStore := &fakeSandboxStore{}
c := newTestCRIService(func(service *criService) {
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(fakeStore)))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
Overhead: &runtime.LinuxContainerResources{
MemoryLimitInBytes: 100,
},
Resources: &runtime.LinuxContainerResources{
CpuQuota: 200,
},
}

_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.NoError(t, err)

// Assert local mem store mutated correctly.
localSb, err := c.sandboxStore.Get("test-sandbox-id")
require.NoError(t, err)
status := localSb.Status.Get()
require.NotNil(t, status.Overhead)
require.NotNil(t, status.Resources)
assert.Equal(t, int64(100), status.Overhead.Linux.MemoryLimitInBytes)
assert.Equal(t, int64(200), status.Resources.Linux.CpuQuota)

// Assert core store was updated with correct extensions.
require.NotNil(t, fakeStore.updatedSandbox)
ext, ok := fakeStore.updatedSandbox.Extensions[podsandbox.UpdatedResourcesKey]
require.True(t, ok, "expected UpdatedResourcesKey in extensions")

var updatedRes podsandbox.UpdatedResources
err = typeurl.UnmarshalTo(ext, &updatedRes)
require.NoError(t, err)

require.NotNil(t, updatedRes.Overhead)
require.NotNil(t, updatedRes.Resources)
assert.Equal(t, int64(100), updatedRes.Overhead.MemoryLimitInBytes)
assert.Equal(t, int64(200), updatedRes.Resources.CpuQuota)
})

t.Run("success fallback when sandbox controller does not implement update", func(t *testing.T) {
recordService := &recordSandboxService{returnErr: errdefs.ErrNotImplemented}
c := newTestCRIService(func(service *criService) {
service.sandboxService = recordService
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(&fakeSandboxStore{})))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
}

_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.NoError(t, err)

assert.Equal(t, "test-sandbox-id", recordService.calledID)
})

t.Run("success when sandbox controller implements update", func(t *testing.T) {
recordService := &recordSandboxService{returnErr: nil}
c := newTestCRIService(func(service *criService) {
service.sandboxService = recordService
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(&fakeSandboxStore{})))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
}

_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.NoError(t, err)

assert.Equal(t, "test-sandbox-id", recordService.calledID)
})

t.Run("fails when sandbox controller update fails", func(t *testing.T) {
recordService := &recordSandboxService{returnErr: errdefs.ErrInvalidArgument}
c := newTestCRIService(func(service *criService) {
service.sandboxService = recordService
client, _ := containerd.New("", containerd.WithServices(containerd.WithSandboxStore(&fakeSandboxStore{})))
service.client = client
})
s := sstore.NewSandbox(mySandbox, sstore.Status{})
c.sandboxStore.Add(s)

req := &runtime.UpdatePodSandboxResourcesRequest{
PodSandboxId: "test-sandbox-id",
}

_, err := c.UpdatePodSandboxResources(context.Background(), req)
require.Error(t, err)
assert.Contains(t, err.Error(), errdefs.ErrInvalidArgument.Error())
})
}
1 change: 1 addition & 0 deletions internal/cri/server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type sandboxService interface {
CreateSandbox(ctx context.Context, info sandbox.Sandbox, opts ...sandbox.CreateOpt) error
StartSandbox(ctx context.Context, sandboxer string, sandboxID string) (sandbox.ControllerInstance, error)
WaitSandbox(ctx context.Context, sandboxer string, sandboxID string) (<-chan containerd.ExitStatus, error)
UpdateSandbox(ctx context.Context, sandboxer string, sandboxID string, sandbox sandbox.Sandbox, fields ...string) error
StopSandbox(ctx context.Context, sandboxer, sandboxID string, opts ...sandbox.StopOpt) error
ShutdownSandbox(ctx context.Context, sandboxer string, sandboxID string) error
SandboxStatus(ctx context.Context, sandboxer string, sandboxID string, verbose bool) (sandbox.ControllerStatus, error)
Expand Down
4 changes: 4 additions & 0 deletions internal/cri/server/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (f *fakeSandboxService) StartSandbox(ctx context.Context, sandboxer string,
return sandbox.ControllerInstance{}, errdefs.ErrNotImplemented
}

func (f *fakeSandboxService) UpdateSandbox(ctx context.Context, sandboxer string, sandboxID string, sandbox sandbox.Sandbox, fields ...string) error {
return errdefs.ErrNotImplemented
}

func (f *fakeSandboxService) StopSandbox(ctx context.Context, sandboxer, sandboxID string, opts ...sandbox.StopOpt) error {
return errdefs.ErrNotImplemented
}
Expand Down
Loading