Skip to content

Commit 8b2a106

Browse files
Remove pods with uninstall
1 parent 4535735 commit 8b2a106

File tree

4 files changed

+231
-0
lines changed

4 files changed

+231
-0
lines changed

internal/containerd/install.go

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

1010
"github.com/pkg/errors"
11+
"go.uber.org/zap"
12+
v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
1113

1214
"github.com/aws/eks-hybrid/internal/artifact"
1315
"github.com/aws/eks-hybrid/internal/daemon"
1416
"github.com/aws/eks-hybrid/internal/system"
1517
"github.com/aws/eks-hybrid/internal/tracker"
18+
"github.com/aws/eks-hybrid/internal/util"
1619
"github.com/aws/eks-hybrid/internal/util/cmd"
20+
internalapi "github.com/containerd/containerd/integration/cri-api/pkg/apis"
21+
"github.com/containerd/containerd/integration/remote"
1722
)
1823

1924
type SourceName string
@@ -127,3 +132,75 @@ func isContainerdNotInstalled() bool {
127132
_, runcNotFoundErr := exec.LookPath(runcPackageName)
128133
return containerdNotFoundErr != nil || runcNotFoundErr != nil
129134
}
135+
136+
// Client is a containerd runtime client wrapper
137+
// Holds the internalapi.RuntimeService for pod/container operations
138+
type Client struct {
139+
Runtime internalapi.RuntimeService
140+
}
141+
142+
// NewClient creates a new Client with a real containerd runtime service
143+
func NewClient() (*Client, error) {
144+
runtime, err := remote.NewRuntimeService(ContainerRuntimeEndpoint, 5*time.Second)
145+
if err != nil {
146+
return nil, err
147+
}
148+
return &Client{Runtime: runtime}, nil
149+
}
150+
151+
func (c *Client) RemovePods() error {
152+
podSandboxes, err := c.Runtime.ListPodSandbox(&v1.PodSandboxFilter{
153+
State: &v1.PodSandboxStateValue{
154+
State: v1.PodSandboxState_SANDBOX_READY,
155+
},
156+
})
157+
if err != nil {
158+
return err
159+
}
160+
161+
for _, sandbox := range podSandboxes {
162+
zap.L().Info("Stopping pod..", zap.String("pod", sandbox.Metadata.Name))
163+
err := util.RetryExponentialBackoff(3, 2*time.Second, func() error {
164+
if err := c.Runtime.StopPodSandbox(sandbox.Id); err != nil {
165+
return err
166+
}
167+
if err := c.Runtime.RemovePodSandbox(sandbox.Id); err != nil {
168+
return err
169+
}
170+
return nil
171+
})
172+
if err != nil {
173+
zap.L().Info("ignored error stopping pod", zap.Error(err))
174+
}
175+
}
176+
177+
// If pod sandbox deletes dont work, we can try to stop and remove containers individually
178+
containers, err := c.Runtime.ListContainers(nil)
179+
if err != nil {
180+
return errors.Wrap(err, "failed to list containers")
181+
}
182+
183+
for _, container := range containers {
184+
status, err := c.Runtime.ContainerStatus(container.Id)
185+
if err != nil {
186+
return errors.Wrapf(err, "failed to get container status for %s", container.Id)
187+
}
188+
zap.L().Info("Stopping container..", zap.String("container", container.Metadata.Name))
189+
err = util.RetryExponentialBackoff(3, 2*time.Second, func() error {
190+
if status.State == v1.ContainerState_CONTAINER_RUNNING {
191+
if err := c.Runtime.StopContainer(container.Id, 0); err != nil {
192+
return errors.Wrapf(err, "failed to stop container %s", container.Id)
193+
}
194+
}
195+
196+
if err := c.Runtime.RemoveContainer(container.Id); err != nil {
197+
return errors.Wrapf(err, "failed to remove container %s", container.Id)
198+
}
199+
return nil
200+
})
201+
if err != nil {
202+
zap.L().Info("ignored error removing container", zap.Error(err))
203+
}
204+
}
205+
return nil
206+
}
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
@@ -102,6 +102,14 @@ func (u *Uninstaller) uninstallDaemons(ctx context.Context) error {
102102
}
103103
if u.Artifacts.Containerd != string(containerd.ContainerdSourceNone) {
104104
u.Logger.Info("Uninstalling containerd...")
105+
client, err := containerd.NewClient()
106+
if err != nil {
107+
u.Logger.Info("ignored error creating containerd client", zap.Error(err))
108+
} else {
109+
if err := client.RemovePods(); err != nil {
110+
u.Logger.Info("ignored error stopping pods", zap.Error(err))
111+
}
112+
}
105113
if err := u.DaemonManager.StopDaemon(containerd.ContainerdDaemonName); err != nil {
106114
return err
107115
}

0 commit comments

Comments
 (0)