Skip to content

Commit 7911713

Browse files
authored
Fix a bug in olm install (#6490)
When installing OLM, retry creating resources, if the CRD is not ready yet. Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
1 parent 03136c0 commit 7911713

File tree

3 files changed

+216
-11
lines changed

3 files changed

+216
-11
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# entries is a list of entries to include in
2+
# release notes and/or the migration guide
3+
entries:
4+
- description: >
5+
Fix a bug where `olm install` command is failed fo "no-match" error.
6+
7+
The output in this case is something like:
8+
9+
```
10+
$ operator-sdk olm install --verbose
11+
...
12+
FATA[0001] Failed to install OLM version "latest": failed to create CRDs and resources: no matches for kind "OLMConfig" in version "operators.coreos.com/v1"
13+
```
14+
15+
Now, in this case, operator-sdk tries to create the resource again, until it succeed (or until the timeout exceeded).
16+
17+
kind: "bugfix"
18+
19+
# Is this a breaking change?
20+
breaking: false

internal/olm/client/client.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
log "github.com/sirupsen/logrus"
3131
appsv1 "k8s.io/api/apps/v1"
3232
corev1 "k8s.io/api/core/v1"
33-
3433
apierrors "k8s.io/apimachinery/pkg/api/errors"
3534
"k8s.io/apimachinery/pkg/api/meta"
3635
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -112,18 +111,50 @@ func NewClientForConfig(cfg *rest.Config) (*Client, error) {
112111
func (c Client) DoCreate(ctx context.Context, objs ...client.Object) error {
113112
for _, obj := range objs {
114113
kind := obj.GetObjectKind().GroupVersionKind().Kind
115-
log.Infof(" Creating %s %q", kind, getName(obj.GetNamespace(), obj.GetName()))
116-
err := c.KubeClient.Create(ctx, obj)
117-
if err != nil {
118-
if !apierrors.IsAlreadyExists(err) {
119-
return err
120-
}
121-
log.Infof(" %s %q already exists", kind, getName(obj.GetNamespace(), obj.GetName()))
114+
resourceName := getName(obj.GetNamespace(), obj.GetName())
115+
116+
log.Infof(" Creating %s %q", kind, resourceName)
117+
118+
if err := c.safeCreateOneResource(ctx, obj, kind, resourceName); err != nil {
119+
log.Infof(" failed to create %s %q; %v", kind, resourceName, err)
120+
return err
122121
}
123122
}
124123
return nil
125124
}
126125

126+
// try to create 10 times before giving up
127+
func (c Client) safeCreateOneResource(ctx context.Context, obj client.Object, kind string, resourceName string) error {
128+
backoff := wait.Backoff{
129+
// retrying every one seconds. We're relaying on the timeout context, so the number of steps is very large, so
130+
// we could use the timeout flag (or its default value), as it used to create the context.
131+
Duration: time.Second,
132+
Steps: 1000,
133+
Factor: 1,
134+
}
135+
136+
err := wait.ExponentialBackoffWithContext(ctx, backoff, func() (bool, error) {
137+
err := c.KubeClient.Create(ctx, obj)
138+
if err == nil || apierrors.IsAlreadyExists(err) {
139+
log.Infof(" %s %q created", kind, resourceName)
140+
return true, nil
141+
}
142+
143+
if meta.IsNoMatchError(err) {
144+
log.Infof(" Failed to create %s %q. CRD is not ready yet. Retrying...", kind, resourceName)
145+
return false, nil
146+
}
147+
148+
return false, err
149+
})
150+
151+
if err != nil && errors.Is(err, context.DeadlineExceeded) {
152+
return fmt.Errorf("timeout")
153+
}
154+
155+
return err
156+
}
157+
127158
func (c Client) DoDelete(ctx context.Context, objs ...client.Object) error {
128159
for _, obj := range objs {
129160
kind := obj.GetObjectKind().GroupVersionKind().Kind

internal/olm/client/client_test.go

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@ package client
1616

1717
import (
1818
"context"
19+
"errors"
20+
"time"
1921

2022
. "github.com/onsi/ginkgo/v2"
2123
. "github.com/onsi/gomega"
2224
olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
2325
appsv1 "k8s.io/api/apps/v1"
2426
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/meta"
2528
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26-
29+
"k8s.io/apimachinery/pkg/runtime"
2730
"k8s.io/apimachinery/pkg/types"
28-
2931
"sigs.k8s.io/controller-runtime/pkg/client"
30-
fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
32+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3133
)
3234

3335
var _ = Describe("Client", func() {
@@ -258,4 +260,156 @@ var _ = Describe("Client", func() {
258260
})
259261
})
260262
})
263+
264+
Describe("test DoCreate", func() {
265+
var fakeClient client.Client
266+
267+
BeforeEach(func() {
268+
fakeClient = &errClient{cli: fake.NewClientBuilder().Build()}
269+
})
270+
271+
AfterEach(func() {
272+
fakeClient.(*errClient).reset()
273+
})
274+
275+
It("should create all the resources successfully", func() {
276+
cli := Client{KubeClient: fakeClient}
277+
278+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
279+
defer cancel()
280+
281+
Expect(cli.DoCreate(ctx,
282+
&corev1.Namespace{
283+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
284+
},
285+
&corev1.Pod{
286+
ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "test-ns"},
287+
},
288+
)).To(Succeed())
289+
290+
ns := &corev1.Namespace{}
291+
Expect(fakeClient.Get(context.Background(), client.ObjectKey{Name: "test-ns"}, ns)).To(Succeed())
292+
293+
pod := &corev1.Pod{}
294+
Expect(fakeClient.Get(context.Background(), client.ObjectKey{Namespace: "test-ns", Name: "test-pod"}, pod)).To(Succeed())
295+
})
296+
297+
It("should eventually create all the resources successfully", func() {
298+
cli := Client{KubeClient: fakeClient}
299+
300+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
301+
defer cancel()
302+
303+
Expect(cli.DoCreate(ctx,
304+
&corev1.Namespace{
305+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
306+
},
307+
&corev1.Pod{
308+
ObjectMeta: metav1.ObjectMeta{Name: "eventually-match", Namespace: "test-ns"},
309+
},
310+
)).To(Succeed())
311+
312+
ns := &corev1.Namespace{}
313+
Expect(fakeClient.Get(context.Background(), client.ObjectKey{Name: "test-ns"}, ns)).To(Succeed())
314+
315+
pod := &corev1.Pod{}
316+
Expect(fakeClient.Get(context.Background(), client.ObjectKey{Namespace: "test-ns", Name: "eventually-match"}, pod)).To(Succeed())
317+
})
318+
319+
It("should fail with no-match error", func() {
320+
cli := Client{KubeClient: fakeClient}
321+
322+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
323+
defer cancel()
324+
Expect(cli.DoCreate(ctx,
325+
&corev1.Namespace{
326+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
327+
},
328+
&corev1.Pod{
329+
ObjectMeta: metav1.ObjectMeta{Name: "no-match", Namespace: "test-ns"},
330+
},
331+
)).ToNot(Succeed())
332+
})
333+
334+
It("should fail with unknown-error error", func() {
335+
cli := Client{KubeClient: fakeClient}
336+
337+
Expect(cli.DoCreate(context.Background(),
338+
&corev1.Namespace{
339+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
340+
},
341+
&corev1.Pod{
342+
ObjectMeta: metav1.ObjectMeta{Name: "unknown-error", Namespace: "test-ns"},
343+
},
344+
)).ToNot(Succeed())
345+
})
346+
})
261347
})
348+
349+
type errClient struct {
350+
cli client.Client
351+
noMatchCounter int
352+
}
353+
354+
func (c *errClient) reset() {
355+
c.noMatchCounter = 0
356+
}
357+
358+
func (c *errClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
359+
return c.cli.Get(ctx, key, obj, opts...)
360+
}
361+
362+
func (c *errClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
363+
return c.cli.List(ctx, list, opts...)
364+
}
365+
func (c *errClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
366+
switch obj.GetName() {
367+
case "no-match":
368+
return &meta.NoResourceMatchError{}
369+
370+
case "eventually-match":
371+
if c.noMatchCounter >= 4 {
372+
return c.cli.Create(ctx, obj, opts...)
373+
}
374+
c.noMatchCounter++
375+
return &meta.NoResourceMatchError{}
376+
377+
case "unknown-error":
378+
return errors.New("fake error")
379+
380+
default:
381+
return c.cli.Create(ctx, obj, opts...)
382+
}
383+
}
384+
385+
func (c *errClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
386+
return c.cli.Delete(ctx, obj, opts...)
387+
}
388+
389+
func (c *errClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
390+
return c.cli.Update(ctx, obj, opts...)
391+
}
392+
393+
func (c *errClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
394+
return c.cli.Patch(ctx, obj, patch, opts...)
395+
}
396+
397+
func (c *errClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
398+
return c.cli.DeleteAllOf(ctx, obj, opts...)
399+
}
400+
401+
func (c *errClient) SubResource(subResource string) client.SubResourceClient {
402+
return c.cli.SubResource(subResource)
403+
}
404+
405+
func (c *errClient) Scheme() *runtime.Scheme {
406+
return c.cli.Scheme()
407+
}
408+
409+
func (c *errClient) RESTMapper() meta.RESTMapper {
410+
return c.cli.RESTMapper()
411+
}
412+
413+
func (c *errClient) Status() client.SubResourceWriter {
414+
return c.cli.Status()
415+
}

0 commit comments

Comments
 (0)