Skip to content

Commit 7548f7d

Browse files
committed
Fix a bug in olm install
When installing OLM, retry creating resources, if the CRD is not ready yet. Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
1 parent 861f524 commit 7548f7d

File tree

3 files changed

+200
-9
lines changed

3 files changed

+200
-9
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
kind: "bugfix"
8+
9+
# Is this a breaking change?
10+
breaking: false

internal/olm/client/client.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,52 @@ func NewClientForConfig(cfg *rest.Config) (*Client, error) {
112112
func (c Client) DoCreate(ctx context.Context, objs ...client.Object) error {
113113
for _, obj := range objs {
114114
kind := obj.GetObjectKind().GroupVersionKind().Kind
115-
log.Infof(" Creating %s %q", kind, getName(obj.GetNamespace(), obj.GetName()))
116-
err := c.KubeClient.Create(ctx, obj)
115+
resourceName := getName(obj.GetNamespace(), obj.GetName())
116+
117+
log.Infof(" Creating %s %q", kind, resourceName)
118+
119+
if err := c.safeCreateOneResource(ctx, obj, kind, resourceName); err != nil {
120+
return err
121+
}
122+
}
123+
return nil
124+
}
125+
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+
for i := 0; i < 10; i++ { // try to create 10 times before giving up
129+
retry, err := c.tryCreatingOneResource(ctx, obj, kind)
117130
if err != nil {
118-
if !apierrors.IsAlreadyExists(err) {
119-
return err
120-
}
131+
return err
132+
}
133+
134+
if !retry {
135+
log.Infof(" %s %q created", kind, resourceName)
136+
return nil
137+
}
138+
139+
log.Infof(" the CRD for %s %q is not ready yet. Retrying in one second", kind, resourceName)
140+
time.Sleep(time.Second)
141+
}
142+
143+
return fmt.Errorf("failed to create the %s %q resource", kind, resourceName)
144+
}
145+
146+
// tryCreatingOneResource makes one try to create a resource in the cluster. It returns true if retry is needed, and an
147+
// error if something went wrong
148+
func (c Client) tryCreatingOneResource(ctx context.Context, obj client.Object, kind string) (bool, error) {
149+
err := c.KubeClient.Create(ctx, obj)
150+
if err != nil {
151+
if apierrors.IsAlreadyExists(err) {
121152
log.Infof(" %s %q already exists", kind, getName(obj.GetNamespace(), obj.GetName()))
153+
return false, nil
154+
} else if meta.IsNoMatchError(err) {
155+
return true, nil
122156
}
157+
return false, err
123158
}
124-
return nil
159+
160+
return false, nil
125161
}
126162

127163
func (c Client) DoDelete(ctx context.Context, objs ...client.Object) error {

internal/olm/client/client_test.go

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

1717
import (
1818
"context"
19+
"errors"
1920

2021
. "github.com/onsi/ginkgo/v2"
2122
. "github.com/onsi/gomega"
2223
olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
2324
appsv1 "k8s.io/api/apps/v1"
2425
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/api/meta"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26-
28+
"k8s.io/apimachinery/pkg/runtime"
2729
"k8s.io/apimachinery/pkg/types"
28-
2930
"sigs.k8s.io/controller-runtime/pkg/client"
30-
fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
31+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3132
)
3233

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

0 commit comments

Comments
 (0)