@@ -82,6 +82,41 @@ var _ = SIGDescribe(feature.CriProxy, framework.WithSerial(), func() {
8282 isExpectedErrMsg := strings .Contains (eventMsg , expectedErr .Error ())
8383 gomega .Expect (isExpectedErrMsg ).To (gomega .BeTrueBecause ("we injected an exception into the PullImage interface of the cri proxy" ))
8484 })
85+ ginkgo .It ("Image pull retry backs off on error." , func (ctx context.Context ) {
86+ expectedErr := fmt .Errorf ("PullImage failed" )
87+ err := addCRIProxyInjector (func (apiName string ) error {
88+ if apiName == criproxy .PullImage {
89+ return expectedErr
90+ }
91+ return nil
92+ })
93+ framework .ExpectNoError (err )
94+
95+ pod := e2epod .NewPodClient (f ).Create (ctx , newPullImageAlwaysPod ())
96+ podErr := e2epod .WaitForPodCondition (ctx , f .ClientSet , f .Namespace .Name , pod .Name , "ImagePullBackOff" , 1 * time .Minute , func (pod * v1.Pod ) (bool , error ) {
97+ if len (pod .Status .ContainerStatuses ) > 0 && pod .Status .Reason == images .ErrImagePullBackOff .Error () {
98+ return true , nil
99+ }
100+ return false , nil
101+ })
102+ gomega .Expect (podErr ).To (gomega .HaveOccurred ())
103+
104+ eventMsg , err := getFailedToPullImageMsg (ctx , f , pod .Name )
105+ framework .ExpectNoError (err )
106+ isExpectedErrMsg := strings .Contains (eventMsg , expectedErr .Error ())
107+ gomega .Expect (isExpectedErrMsg ).To (gomega .BeTrueBecause ("we injected an exception into the PullImage interface of the cri proxy" ))
108+
109+ // remove error so after backoff we will succeed
110+ resetCRIProxyInjector ()
111+
112+ podErr = e2epod .WaitForPodRunningInNamespace (ctx , f .ClientSet , pod )
113+ framework .ExpectNoError (podErr )
114+
115+ durations , err := getImageBackOffDurations (ctx , f , pod .Name )
116+ framework .ExpectNoError (err )
117+ gomega .Expect (durations [0 ]).Should (gomega .BeNumerically ("~" , time .Duration (10 * time .Second ), time .Duration (2 * time .Second )))
118+
119+ })
85120 })
86121
87122 ginkgo .Context ("Inject a pull image timeout exception into the CriProxy" , func () {
@@ -133,6 +168,48 @@ func getFailedToPullImageMsg(ctx context.Context, f *framework.Framework, podNam
133168 return "" , fmt .Errorf ("failed to find FailedToPullImage event for pod: %s" , podName )
134169}
135170
171+ func getImageBackOffDurations (ctx context.Context , f * framework.Framework , podName string ) ([]time.Duration , error ) {
172+ events , err := f .ClientSet .CoreV1 ().Events (f .Namespace .Name ).List (ctx , metav1.ListOptions {})
173+ if err != nil {
174+ return nil , err
175+ }
176+
177+ var backoffs []time.Duration
178+
179+ type BackOffRecord struct {
180+ podName string
181+ initialEventTime time.Time
182+ backoffEventTimes []time.Time
183+ duration time.Duration
184+ }
185+
186+ records := make (map [int ]* BackOffRecord )
187+ var backoffCount int
188+ var pullTime time.Time
189+ var r * BackOffRecord
190+ for _ , event := range events .Items {
191+ if event .InvolvedObject .Name == podName {
192+ switch event .Reason {
193+ case kubeletevents .PullingImage :
194+ if ! pullTime .IsZero () {
195+ if event .FirstTimestamp .Time .After (pullTime ) {
196+ r = records [backoffCount ]
197+ r .duration = r .initialEventTime .Sub (r .backoffEventTimes [len (r .backoffEventTimes )- 1 ])
198+ backoffs = append (backoffs , r .duration )
199+ backoffCount ++
200+ }
201+ }
202+ pullTime = event .FirstTimestamp .Time
203+ records [backoffCount ].initialEventTime = pullTime
204+ case kubeletevents .BackOffPullImage :
205+ current := records [backoffCount ].backoffEventTimes
206+ current = append (current , event .FirstTimestamp .Time )
207+ }
208+ }
209+ }
210+ return backoffs , nil
211+ }
212+
136213func getPodImagePullDuration (ctx context.Context , f * framework.Framework , podName string ) (time.Duration , error ) {
137214 events , err := f .ClientSet .CoreV1 ().Events (f .Namespace .Name ).List (ctx , metav1.ListOptions {})
138215 if err != nil {
0 commit comments