Skip to content

Commit ebe89d0

Browse files
yevgeny-shnaidmank8s-ci-robot
authored andcommitted
Updating image puller package with the new pod flow
This commit contains the following changes: 1. Removing image label - image will be obtained from the Spec directly 2. Adding pod type label - one-time pod that should be considered as Failed once the container fails on image pull, and run-until-success, that is considered as InProgess, in case container fails on image pull and there are build instructions 3. Adding GetPodStatus API - this api returns pod status based on the following: - the pod that fails to pull image stays in the Pending state, and the container status is updated with appropriate reason.So, if the pod is one-time pod, and container failed on image pull - status is Failed. Otherwise status is Running, since we let the pod restart the container with back-offs 4. Pod is created with restartPolicy Never, since if the pod managed to pull image - we consider it a success
1 parent 670222e commit ebe89d0

File tree

3 files changed

+171
-22
lines changed

3 files changed

+171
-22
lines changed

internal/pod/imagepuller.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ import (
1212
"sigs.k8s.io/controller-runtime/pkg/client"
1313
)
1414

15+
type PullPodStatus string
16+
17+
const (
18+
PullImageFailed PullPodStatus = "pullFailed"
19+
PullImageSuccess PullPodStatus = "pullSuccess"
20+
PullImageInProcess PullPodStatus = "pullInProcess"
21+
PullImageUnexpectedErr PullPodStatus = "unexpectedError"
22+
23+
imagePullBackOffReason = "ImagePullBackOff"
24+
errImagePullReason = "ErrImagePull"
25+
26+
moduleImageLabelKey = "kmm.node.kubernetes.io/module-image-config"
27+
pullPodTypeLabelKey = "kmm.node.kubernetes.io/pull-pod-type"
28+
29+
pullerContainerName = "puller"
30+
31+
pullPodTypeOneTime = "one-time-pull"
32+
pullPodUntilSuccess = "until-success"
33+
)
34+
1535
//go:generate mockgen -source=imagepuller.go -package=pod -destination=mock_imagepuller.go
1636

1737
type ImagePuller interface {
@@ -20,14 +40,9 @@ type ImagePuller interface {
2040
ListPullPods(ctx context.Context, micObj *kmmv1beta1.ModuleImagesConfig) ([]v1.Pod, error)
2141
GetPullPodForImage(pods []v1.Pod, image string) *v1.Pod
2242
GetPullPodImage(pod v1.Pod) string
43+
GetPullPodStatus(pod *v1.Pod) PullPodStatus
2344
}
2445

25-
const (
26-
moduleImageLabelKey = "kmm.node.kubernetes.io/module-image-config"
27-
imageLabelKey = "kmm.node.kubernetes.io/module-image"
28-
pullerContainerName = "puller"
29-
)
30-
3146
type imagePullerImpl struct {
3247
client client.Client
3348
scheme *runtime.Scheme
@@ -43,9 +58,9 @@ func NewImagePuller(client client.Client, scheme *runtime.Scheme) ImagePuller {
4358
func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1beta1.ModuleImageSpec,
4459
micObj *kmmv1beta1.ModuleImagesConfig) error {
4560

46-
restartPolicy := v1.RestartPolicyOnFailure
61+
pullPodTypeLabeValue := pullPodUntilSuccess
4762
if imageSpec.Build != nil || imageSpec.Sign != nil {
48-
restartPolicy = v1.RestartPolicyNever
63+
pullPodTypeLabeValue = pullPodTypeOneTime
4964
}
5065

5166
imagePullSecrets := []v1.LocalObjectReference{}
@@ -59,7 +74,7 @@ func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1b
5974
Namespace: micObj.Namespace,
6075
Labels: map[string]string{
6176
moduleImageLabelKey: micObj.Name,
62-
imageLabelKey: imageSpec.Image,
77+
pullPodTypeLabelKey: pullPodTypeLabeValue,
6378
},
6479
},
6580
Spec: v1.PodSpec{
@@ -70,7 +85,7 @@ func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1b
7085
Command: []string{"/bin/sh", "-c", "exit 0"},
7186
},
7287
},
73-
RestartPolicy: restartPolicy,
88+
RestartPolicy: v1.RestartPolicyNever,
7489
ImagePullSecrets: imagePullSecrets,
7590
},
7691
}
@@ -92,7 +107,7 @@ func (ipi *imagePullerImpl) ListPullPods(ctx context.Context, micObj *kmmv1beta1
92107

93108
pl := v1.PodList{}
94109

95-
hl := client.HasLabels{imageLabelKey}
110+
hl := client.HasLabels{pullPodTypeLabelKey}
96111
ml := client.MatchingLabels{moduleImageLabelKey: micObj.Name}
97112

98113
ctrl.LoggerFrom(ctx).WithValues("mic name", micObj.Name).V(1).Info("Listing mic image Pods")
@@ -107,13 +122,46 @@ func (ipi *imagePullerImpl) ListPullPods(ctx context.Context, micObj *kmmv1beta1
107122
func (ipi *imagePullerImpl) GetPullPodForImage(pods []v1.Pod, image string) *v1.Pod {
108123

109124
for i, pod := range pods {
110-
if image == pod.Labels[imageLabelKey] {
125+
if image == pod.Spec.Containers[0].Image {
111126
return &pods[i]
112127
}
113128
}
114129
return nil
115130
}
116131

117132
func (ipi *imagePullerImpl) GetPullPodImage(pod v1.Pod) string {
118-
return pod.Labels[imageLabelKey]
133+
return pod.Spec.Containers[0].Image
134+
}
135+
136+
func (ipi *imagePullerImpl) GetPullPodStatus(pod *v1.Pod) PullPodStatus {
137+
switch pod.Status.Phase {
138+
case v1.PodSucceeded:
139+
return PullImageSuccess
140+
case v1.PodFailed, v1.PodUnknown:
141+
return PullImageUnexpectedErr
142+
case v1.PodRunning:
143+
return PullImageInProcess
144+
case v1.PodPending:
145+
// no container statuses yet, the pod is just starting to pull images
146+
if pod.Status.ContainerStatuses == nil {
147+
return PullImageInProcess
148+
}
149+
150+
// no wating status, the pull process is still in progress
151+
if pod.Status.ContainerStatuses[0].State.Waiting == nil {
152+
return PullImageInProcess
153+
}
154+
155+
pullPodType := pod.GetLabels()[pullPodTypeLabelKey]
156+
// if pod is targeted to wait till the end - return the InProgress
157+
if pullPodType == pullPodUntilSuccess {
158+
return PullImageInProcess
159+
}
160+
161+
if waitingReason := pod.Status.ContainerStatuses[0].State.Waiting.Reason; waitingReason == imagePullBackOffReason || waitingReason == errImagePullReason {
162+
return PullImageFailed
163+
}
164+
}
165+
166+
return PullImageUnexpectedErr
119167
}

internal/pod/imagepuller_test.go

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ var _ = Describe("ListPullPods", func() {
3838
}
3939

4040
It("list succeeded", func() {
41-
hl := ctrlclient.HasLabels{imageLabelKey}
41+
hl := ctrlclient.HasLabels{pullPodTypeLabelKey}
4242
ml := ctrlclient.MatchingLabels{moduleImageLabelKey: testMic.Name}
4343

4444
clnt.EXPECT().List(context.Background(), gomock.Any(), ctrlclient.InNamespace(testMic.Namespace), hl, ml).DoAndReturn(
@@ -54,7 +54,7 @@ var _ = Describe("ListPullPods", func() {
5454
})
5555

5656
It("list failed", func() {
57-
hl := ctrlclient.HasLabels{imageLabelKey}
57+
hl := ctrlclient.HasLabels{pullPodTypeLabelKey}
5858
ml := ctrlclient.MatchingLabels{moduleImageLabelKey: testMic.Name}
5959

6060
clnt.EXPECT().List(context.Background(), gomock.Any(), ctrlclient.InNamespace(testMic.Namespace), hl, ml).Return(fmt.Errorf("some error"))
@@ -116,21 +116,29 @@ var _ = Describe("GetPullPodForImage", func() {
116116

117117
pullPods := []v1.Pod{
118118
{
119-
ObjectMeta: metav1.ObjectMeta{
120-
Labels: map[string]string{imageLabelKey: "image 1"},
119+
Spec: v1.PodSpec{
120+
Containers: []v1.Container{
121+
{
122+
Image: "image 1",
123+
},
124+
},
121125
},
122126
},
123127
{
124-
ObjectMeta: metav1.ObjectMeta{
125-
Labels: map[string]string{imageLabelKey: "image 2"},
128+
Spec: v1.PodSpec{
129+
Containers: []v1.Container{
130+
{
131+
Image: "image 2",
132+
},
133+
},
126134
},
127135
},
128136
}
129137

130138
It("there is a pull pod for that image", func() {
131139
res := ip.GetPullPodForImage(pullPods, "image 2")
132140
Expect(res).ToNot(BeNil())
133-
Expect(res.Labels[imageLabelKey]).To(Equal("image 2"))
141+
Expect(res.Spec.Containers[0].Image).To(Equal("image 2"))
134142
})
135143

136144
It("there is no pull pod for that image", func() {
@@ -170,7 +178,7 @@ var _ = Describe("CreatePullPod", func() {
170178
Namespace: testMic.Namespace,
171179
Labels: map[string]string{
172180
moduleImageLabelKey: "some name",
173-
imageLabelKey: "some image",
181+
pullPodTypeLabelKey: pullPodUntilSuccess,
174182
},
175183
},
176184
Spec: v1.PodSpec{
@@ -181,7 +189,7 @@ var _ = Describe("CreatePullPod", func() {
181189
Command: []string{"/bin/sh", "-c", "exit 0"},
182190
},
183191
},
184-
RestartPolicy: v1.RestartPolicyOnFailure,
192+
RestartPolicy: v1.RestartPolicyNever,
185193
ImagePullSecrets: []v1.LocalObjectReference{},
186194
},
187195
}
@@ -202,3 +210,82 @@ var _ = Describe("CreatePullPod", func() {
202210
Expect(err).To(BeNil())
203211
})
204212
})
213+
214+
var _ = Describe("GetPullPodStatus", func() {
215+
var (
216+
ctrl *gomock.Controller
217+
clnt *client.MockClient
218+
ip ImagePuller
219+
)
220+
221+
BeforeEach(func() {
222+
ctrl = gomock.NewController(GinkgoT())
223+
clnt = client.NewMockClient(ctrl)
224+
ip = NewImagePuller(clnt, scheme)
225+
})
226+
227+
testPod := v1.Pod{
228+
Status: v1.PodStatus{
229+
Phase: v1.PodSucceeded,
230+
},
231+
}
232+
233+
It("check different phases except for PodPending", func() {
234+
By("phase Succeeded")
235+
236+
res := ip.GetPullPodStatus(&testPod)
237+
Expect(res).To(Equal(PullImageSuccess))
238+
239+
By("phase PodFailed")
240+
testPod.Status.Phase = v1.PodFailed
241+
res = ip.GetPullPodStatus(&testPod)
242+
Expect(res).To(Equal(PullImageUnexpectedErr))
243+
244+
By("phase PodUnknown")
245+
testPod.Status.Phase = v1.PodUnknown
246+
res = ip.GetPullPodStatus(&testPod)
247+
Expect(res).To(Equal(PullImageUnexpectedErr))
248+
249+
By("phase PodRunning")
250+
testPod.Status.Phase = v1.PodRunning
251+
res = ip.GetPullPodStatus(&testPod)
252+
Expect(res).To(Equal(PullImageInProcess))
253+
})
254+
255+
It("check container statuses for PodPending", func() {
256+
testPod.Status.Phase = v1.PodPending
257+
By("container statuses missing")
258+
testPod.Status.ContainerStatuses = nil
259+
res := ip.GetPullPodStatus(&testPod)
260+
Expect(res).To(Equal(PullImageInProcess))
261+
262+
By("container statuses waiting is nil")
263+
testPod.Status.ContainerStatuses = []v1.ContainerStatus{v1.ContainerStatus{}}
264+
res = ip.GetPullPodStatus(&testPod)
265+
Expect(res).To(Equal(PullImageInProcess))
266+
267+
By("container statuses waiting is not nil and pod is not one time pull pod")
268+
testPod.SetLabels(map[string]string{pullPodTypeLabelKey: pullPodUntilSuccess})
269+
testPod.Status.ContainerStatuses[0].State = v1.ContainerState{
270+
Waiting: &v1.ContainerStateWaiting{},
271+
}
272+
res = ip.GetPullPodStatus(&testPod)
273+
Expect(res).To(Equal(PullImageInProcess))
274+
275+
By("container statuses waiting is not nil and pod is one time pull pod and reason is not image pull error")
276+
testPod.SetLabels(map[string]string{pullPodTypeLabelKey: pullPodTypeOneTime})
277+
testPod.Status.ContainerStatuses[0].State.Waiting.Reason = "some reason"
278+
res = ip.GetPullPodStatus(&testPod)
279+
Expect(res).To(Equal(PullImageUnexpectedErr))
280+
281+
By("container statuses waiting is not nil and pod is one time pull pod and reason is ImagePullBackOff")
282+
testPod.Status.ContainerStatuses[0].State.Waiting.Reason = imagePullBackOffReason
283+
res = ip.GetPullPodStatus(&testPod)
284+
Expect(res).To(Equal(PullImageFailed))
285+
286+
By("container statuses waiting is not nil and pod is one time pull pod and reason is errImagePullReason")
287+
testPod.Status.ContainerStatuses[0].State.Waiting.Reason = errImagePullReason
288+
res = ip.GetPullPodStatus(&testPod)
289+
Expect(res).To(Equal(PullImageFailed))
290+
})
291+
})

internal/pod/mock_imagepuller.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)