Skip to content

Commit b36cb7f

Browse files
Remove pods with uninstall
1 parent 35af999 commit b36cb7f

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

internal/containerd/install.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import (
77
"os/exec"
88
"time"
99

10+
internalapi "github.com/containerd/containerd/integration/cri-api/pkg/apis"
11+
"github.com/containerd/containerd/integration/remote"
1012
"github.com/pkg/errors"
13+
"go.uber.org/zap"
14+
v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
1115

1216
"github.com/aws/eks-hybrid/internal/artifact"
1317
"github.com/aws/eks-hybrid/internal/daemon"
1418
"github.com/aws/eks-hybrid/internal/system"
1519
"github.com/aws/eks-hybrid/internal/tracker"
20+
"github.com/aws/eks-hybrid/internal/util"
1621
"github.com/aws/eks-hybrid/internal/util/cmd"
1722
)
1823

@@ -117,3 +122,78 @@ func areContainerdAndRuncInstalled() bool {
117122
_, runcNotFoundErr := exec.LookPath(runcPackageName)
118123
return containerdNotFoundErr == nil && runcNotFoundErr == nil
119124
}
125+
126+
// Client is a containerd runtime client wrapper
127+
// Holds the internalapi.RuntimeService for pod/container operations
128+
type Client struct {
129+
Runtime internalapi.RuntimeService
130+
}
131+
132+
// NewClient creates a new Client with a real containerd runtime service
133+
func NewClient() (*Client, error) {
134+
runtime, err := remote.NewRuntimeService(ContainerRuntimeEndpoint, 5*time.Second)
135+
if err != nil {
136+
return nil, err
137+
}
138+
return &Client{Runtime: runtime}, nil
139+
}
140+
141+
// RemovePods stops and removes all pod sandboxes and containers in the k8s.io namespace on the node
142+
func (c *Client) RemovePods() error {
143+
podSandboxes, err := c.Runtime.ListPodSandbox(&v1.PodSandboxFilter{
144+
State: &v1.PodSandboxStateValue{
145+
State: v1.PodSandboxState_SANDBOX_READY,
146+
},
147+
})
148+
if err != nil {
149+
return errors.Wrap(err, "listing pod sandboxes")
150+
}
151+
152+
for _, sandbox := range podSandboxes {
153+
zap.L().Info("Stopping pod..", zap.String("pod", sandbox.Metadata.Name))
154+
err := util.RetryExponentialBackoff(3, 2*time.Second, func() error {
155+
if err := c.Runtime.StopPodSandbox(sandbox.Id); err != nil {
156+
return errors.Wrapf(err, "stopping pod %s", sandbox.Id)
157+
}
158+
if err := c.Runtime.RemovePodSandbox(sandbox.Id); err != nil {
159+
return errors.Wrapf(err, "removing pod %s", sandbox.Id)
160+
}
161+
return nil
162+
})
163+
if err != nil {
164+
zap.L().Info("ignored error stopping pod", zap.Error(err))
165+
}
166+
}
167+
168+
// If pod sandbox deletion fails, we can try to stop and remove containers individually
169+
// We do not pass in a container state filter here as we want to remove all containers
170+
// including stopped ones as they arent GCed by containerd post daemon stop.
171+
containers, err := c.Runtime.ListContainers(nil)
172+
if err != nil {
173+
return errors.Wrap(err, "listing containers")
174+
}
175+
176+
for _, container := range containers {
177+
status, err := c.Runtime.ContainerStatus(container.Id)
178+
if err != nil {
179+
return errors.Wrapf(err, "getting container status for %s", container.Id)
180+
}
181+
zap.L().Info("Stopping container..", zap.String("container", container.Metadata.Name))
182+
err = util.RetryExponentialBackoff(3, 2*time.Second, func() error {
183+
if status.State == v1.ContainerState_CONTAINER_RUNNING {
184+
if err := c.Runtime.StopContainer(container.Id, 0); err != nil {
185+
return errors.Wrapf(err, "stopping container %s", container.Id)
186+
}
187+
}
188+
189+
if err := c.Runtime.RemoveContainer(container.Id); err != nil {
190+
return errors.Wrapf(err, "removing container %s", container.Id)
191+
}
192+
return nil
193+
})
194+
if err != nil {
195+
zap.L().Info("ignored error removing container", zap.Error(err))
196+
}
197+
}
198+
return nil
199+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package containerd_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/mock"
9+
v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
10+
11+
"github.com/aws/eks-hybrid/internal/containerd"
12+
"github.com/aws/eks-hybrid/internal/containerd/mocks"
13+
)
14+
15+
func TestClient_RemovePods(t *testing.T) {
16+
t.Run("no pods or containers", func(t *testing.T) {
17+
m := new(mocks.MockRuntimeService)
18+
m.On("ListPodSandbox", mock.Anything).Return([]*v1.PodSandbox{}, nil)
19+
m.On("ListContainers", (*v1.ContainerFilter)(nil)).Return([]*v1.Container{}, nil)
20+
c := &containerd.Client{Runtime: m}
21+
err := c.RemovePods()
22+
assert.NoError(t, err)
23+
m.AssertExpectations(t)
24+
})
25+
26+
t.Run("error listing pods", func(t *testing.T) {
27+
m := new(mocks.MockRuntimeService)
28+
m.On("ListPodSandbox", mock.Anything).Return(nil, errors.New("fail list pods"))
29+
c := &containerd.Client{Runtime: m}
30+
err := c.RemovePods()
31+
assert.ErrorContains(t, err, "fail list pods")
32+
m.AssertExpectations(t)
33+
})
34+
35+
t.Run("error stopping/removing pod", func(t *testing.T) {
36+
m := new(mocks.MockRuntimeService)
37+
pod := &v1.PodSandbox{Metadata: &v1.PodSandboxMetadata{Name: "pod1"}, Id: "pod1id"}
38+
m.On("ListPodSandbox", mock.Anything).Return([]*v1.PodSandbox{pod}, nil)
39+
m.On("StopPodSandbox", "pod1id").Return(errors.New("fail stop pod")).Times(3)
40+
m.On("ListContainers", (*v1.ContainerFilter)(nil)).Return([]*v1.Container{}, nil)
41+
c := &containerd.Client{Runtime: m}
42+
err := c.RemovePods()
43+
assert.NoError(t, err, "RemovePods should ignore pod stop errors")
44+
m.AssertExpectations(t)
45+
})
46+
47+
t.Run("error listing containers", func(t *testing.T) {
48+
m := new(mocks.MockRuntimeService)
49+
m.On("ListPodSandbox", mock.Anything).Return([]*v1.PodSandbox{}, nil)
50+
m.On("ListContainers", (*v1.ContainerFilter)(nil)).Return(nil, errors.New("fail list containers"))
51+
c := &containerd.Client{Runtime: m}
52+
err := c.RemovePods()
53+
assert.ErrorContains(t, err, "fail list containers")
54+
m.AssertExpectations(t)
55+
})
56+
57+
t.Run("error stopping/removing container", func(t *testing.T) {
58+
m := new(mocks.MockRuntimeService)
59+
container := &v1.Container{Metadata: &v1.ContainerMetadata{Name: "c1"}, Id: "cid1"}
60+
status := &v1.ContainerStatus{State: v1.ContainerState_CONTAINER_RUNNING}
61+
m.On("ListPodSandbox", mock.Anything).Return([]*v1.PodSandbox{}, nil)
62+
m.On("ListContainers", (*v1.ContainerFilter)(nil)).Return([]*v1.Container{container}, nil)
63+
m.On("ContainerStatus", "cid1").Return(status, nil)
64+
m.On("StopContainer", "cid1", int64(0)).Return(errors.New("fail stop container")).Times(3)
65+
c := &containerd.Client{Runtime: m}
66+
err := c.RemovePods()
67+
assert.NoError(t, err, "RemovePods should ignore container stop errors")
68+
m.AssertExpectations(t)
69+
})
70+
71+
t.Run("all success", func(t *testing.T) {
72+
m := new(mocks.MockRuntimeService)
73+
pod := &v1.PodSandbox{Metadata: &v1.PodSandboxMetadata{Name: "pod1"}, Id: "pod1id"}
74+
container := &v1.Container{Metadata: &v1.ContainerMetadata{Name: "c1"}, Id: "cid1"}
75+
status := &v1.ContainerStatus{State: v1.ContainerState_CONTAINER_EXITED}
76+
m.On("ListPodSandbox", mock.Anything).Return([]*v1.PodSandbox{pod}, nil)
77+
m.On("StopPodSandbox", "pod1id").Return(nil)
78+
m.On("RemovePodSandbox", "pod1id").Return(nil)
79+
m.On("ListContainers", (*v1.ContainerFilter)(nil)).Return([]*v1.Container{container}, nil)
80+
m.On("ContainerStatus", "cid1").Return(status, nil)
81+
m.On("RemoveContainer", "cid1").Return(nil)
82+
c := &containerd.Client{Runtime: m}
83+
err := c.RemovePods()
84+
assert.NoError(t, err)
85+
m.AssertExpectations(t)
86+
})
87+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package mocks
2+
3+
import (
4+
internalapi "github.com/containerd/containerd/integration/cri-api/pkg/apis"
5+
"github.com/stretchr/testify/mock"
6+
"google.golang.org/grpc"
7+
v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
8+
)
9+
10+
// MockRuntimeService is a testify mock for internalapi.RuntimeService
11+
// Only the methods used by RemovePods are implemented
12+
type MockRuntimeService struct {
13+
mock.Mock
14+
internalapi.RuntimeService
15+
}
16+
17+
func (m *MockRuntimeService) ListPodSandbox(filter *v1.PodSandboxFilter, opts ...grpc.CallOption) ([]*v1.PodSandbox, error) {
18+
args := m.Called(filter)
19+
if args.Get(0) == nil {
20+
return nil, args.Error(1)
21+
}
22+
return args.Get(0).([]*v1.PodSandbox), args.Error(1)
23+
}
24+
25+
func (m *MockRuntimeService) StopPodSandbox(id string, opts ...grpc.CallOption) error {
26+
args := m.Called(id)
27+
return args.Error(0)
28+
}
29+
30+
func (m *MockRuntimeService) RemovePodSandbox(id string, opts ...grpc.CallOption) error {
31+
args := m.Called(id)
32+
return args.Error(0)
33+
}
34+
35+
func (m *MockRuntimeService) ListContainers(filter *v1.ContainerFilter, opts ...grpc.CallOption) ([]*v1.Container, error) {
36+
args := m.Called(filter)
37+
if args.Get(0) == nil {
38+
return nil, args.Error(1)
39+
}
40+
return args.Get(0).([]*v1.Container), args.Error(1)
41+
}
42+
43+
func (m *MockRuntimeService) ContainerStatus(id string, opts ...grpc.CallOption) (*v1.ContainerStatus, error) {
44+
args := m.Called(id)
45+
if args.Get(0) == nil {
46+
return nil, args.Error(1)
47+
}
48+
return args.Get(0).(*v1.ContainerStatus), args.Error(1)
49+
}
50+
51+
func (m *MockRuntimeService) StopContainer(id string, timeout int64, opts ...grpc.CallOption) error {
52+
args := m.Called(id, timeout)
53+
return args.Error(0)
54+
}
55+
56+
func (m *MockRuntimeService) RemoveContainer(id string, opts ...grpc.CallOption) error {
57+
args := m.Called(id)
58+
return args.Error(0)
59+
}

internal/flows/uninstall.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ func (u *Uninstaller) uninstallDaemons(ctx context.Context) error {
105105
}
106106
if u.Artifacts.Containerd != tracker.ContainerdSourceNone {
107107
u.Logger.Info("Uninstalling containerd...")
108+
client, err := containerd.NewClient()
109+
if err != nil {
110+
u.Logger.Info("ignored error creating containerd client", zap.Error(err))
111+
} else {
112+
if err := client.RemovePods(); err != nil {
113+
u.Logger.Info("ignored error stopping pods", zap.Error(err))
114+
}
115+
}
108116
if err := u.DaemonManager.StopDaemon(containerd.ContainerdDaemonName); err != nil {
109117
return err
110118
}

0 commit comments

Comments
 (0)