Skip to content

Commit 670d610

Browse files
committed
unpack retry e2e tests
Signed-off-by: Ankita Thomas <[email protected]>
1 parent 4652ff1 commit 670d610

File tree

5 files changed

+314
-4
lines changed

5 files changed

+314
-4
lines changed

Diff for: pkg/controller/bundle/bundle_unpacker.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"k8s.io/apimachinery/pkg/api/resource"
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020
k8slabels "k8s.io/apimachinery/pkg/labels"
21+
"k8s.io/apiserver/pkg/storage/names"
2122
"k8s.io/client-go/kubernetes"
2223
listersbatchv1 "k8s.io/client-go/listers/batch/v1"
2324
listerscorev1 "k8s.io/client-go/listers/core/v1"
@@ -695,10 +696,10 @@ func (c *ConfigMapUnpacker) ensureJob(cmRef *corev1.ObjectReference, bundlePath
695696

696697
if failed {
697698
if time.Now().After(lastFailureTime.Add(unpackRetryInterval)) {
698-
fresh.SetName(fmt.Sprintf("%s-%d", fresh.GetName(), len(jobs)))
699+
fresh.SetName(names.SimpleNameGenerator.GenerateName(fresh.GetName()))
699700
job, err = c.client.BatchV1().Jobs(fresh.GetNamespace()).Create(context.TODO(), fresh, metav1.CreateOptions{})
700-
return
701701
}
702+
return
702703
}
703704
}
704705
}
@@ -890,7 +891,7 @@ func OperatorGroupBundleUnpackRetryInterval(ogLister v1listers.OperatorGroupName
890891

891892
d, err := time.ParseDuration(timeoutStr)
892893
if err != nil {
893-
return 0, fmt.Errorf("failed to parse unpack timeout annotation(%s: %s): %w", BundleUnpackRetryMinimumIntervalAnnotationKey, timeoutStr, err)
894+
return 0, fmt.Errorf("failed to parse unpack retry annotation(%s: %s): %w", BundleUnpackRetryMinimumIntervalAnnotationKey, timeoutStr, err)
894895
}
895896

896897
return d, nil

Diff for: pkg/controller/bundle/bundle_unpacker_test.go

+112
Original file line numberDiff line numberDiff line change
@@ -1839,3 +1839,115 @@ func TestOperatorGroupBundleUnpackTimeout(t *testing.T) {
18391839
})
18401840
}
18411841
}
1842+
1843+
func TestOperatorGroupBundleUnpackRetryInterval(t *testing.T) {
1844+
nsName := "fake-ns"
1845+
1846+
for _, tc := range []struct {
1847+
name string
1848+
operatorGroups []*operatorsv1.OperatorGroup
1849+
expectedTimeout time.Duration
1850+
expectedError error
1851+
}{
1852+
{
1853+
name: "No operator groups exist",
1854+
expectedTimeout: 0,
1855+
expectedError: errors.New("found 0 operatorGroups, expected 1"),
1856+
},
1857+
{
1858+
name: "Multiple operator groups exist",
1859+
operatorGroups: []*operatorsv1.OperatorGroup{
1860+
{
1861+
TypeMeta: metav1.TypeMeta{
1862+
Kind: operatorsv1.OperatorGroupKind,
1863+
APIVersion: operatorsv1.GroupVersion.String(),
1864+
},
1865+
ObjectMeta: metav1.ObjectMeta{
1866+
Name: "og1",
1867+
Namespace: nsName,
1868+
},
1869+
},
1870+
{
1871+
TypeMeta: metav1.TypeMeta{
1872+
Kind: operatorsv1.OperatorGroupKind,
1873+
APIVersion: operatorsv1.GroupVersion.String(),
1874+
},
1875+
ObjectMeta: metav1.ObjectMeta{
1876+
Name: "og2",
1877+
Namespace: nsName,
1878+
},
1879+
},
1880+
},
1881+
expectedTimeout: 0,
1882+
expectedError: errors.New("found 2 operatorGroups, expected 1"),
1883+
},
1884+
{
1885+
name: "One operator group exists with valid unpack retry annotation",
1886+
operatorGroups: []*operatorsv1.OperatorGroup{
1887+
{
1888+
TypeMeta: metav1.TypeMeta{
1889+
Kind: operatorsv1.OperatorGroupKind,
1890+
APIVersion: operatorsv1.GroupVersion.String(),
1891+
},
1892+
ObjectMeta: metav1.ObjectMeta{
1893+
Name: "og",
1894+
Namespace: nsName,
1895+
Annotations: map[string]string{BundleUnpackRetryMinimumIntervalAnnotationKey: "1m"},
1896+
},
1897+
},
1898+
},
1899+
expectedTimeout: 1 * time.Minute,
1900+
expectedError: nil,
1901+
},
1902+
{
1903+
name: "One operator group exists with no unpack retry annotation",
1904+
operatorGroups: []*operatorsv1.OperatorGroup{
1905+
{
1906+
TypeMeta: metav1.TypeMeta{
1907+
Kind: operatorsv1.OperatorGroupKind,
1908+
APIVersion: operatorsv1.GroupVersion.String(),
1909+
},
1910+
ObjectMeta: metav1.ObjectMeta{
1911+
Name: "og",
1912+
Namespace: nsName,
1913+
},
1914+
},
1915+
},
1916+
expectedTimeout: 0,
1917+
expectedError: nil,
1918+
},
1919+
{
1920+
name: "One operator group exists with invalid unpack retry annotation",
1921+
operatorGroups: []*operatorsv1.OperatorGroup{
1922+
{
1923+
TypeMeta: metav1.TypeMeta{
1924+
Kind: operatorsv1.OperatorGroupKind,
1925+
APIVersion: operatorsv1.GroupVersion.String(),
1926+
},
1927+
ObjectMeta: metav1.ObjectMeta{
1928+
Name: "og",
1929+
Namespace: nsName,
1930+
Annotations: map[string]string{BundleUnpackRetryMinimumIntervalAnnotationKey: "invalid"},
1931+
},
1932+
},
1933+
},
1934+
expectedTimeout: 0,
1935+
expectedError: fmt.Errorf("failed to parse unpack retry annotation(operatorframework.io/bundle-unpack-min-retry-interval: invalid): %w", errors.New("time: invalid duration \"invalid\"")),
1936+
},
1937+
} {
1938+
t.Run(tc.name, func(t *testing.T) {
1939+
ogIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
1940+
ogLister := v1listers.NewOperatorGroupLister(ogIndexer).OperatorGroups(nsName)
1941+
1942+
for _, og := range tc.operatorGroups {
1943+
err := ogIndexer.Add(og)
1944+
assert.NoError(t, err)
1945+
}
1946+
1947+
timeout, err := OperatorGroupBundleUnpackRetryInterval(ogLister)
1948+
1949+
assert.Equal(t, tc.expectedTimeout, timeout)
1950+
assert.Equal(t, tc.expectedError, err)
1951+
})
1952+
}
1953+
}

Diff for: test/e2e/fbc_provider.go

+6
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ func NewFileBasedFiledBasedCatalogProvider(path string) (FileBasedCatalogProvide
3131
func (f *fileBasedFileBasedCatalogProvider) GetCatalog() string {
3232
return f.fbc
3333
}
34+
35+
func NewRawFileBasedCatalogProvider(data string) (FileBasedCatalogProvider, error) {
36+
return &fileBasedFileBasedCatalogProvider{
37+
fbc: string(data),
38+
}, nil
39+
}

Diff for: test/e2e/subscription_e2e_test.go

+179
Original file line numberDiff line numberDiff line change
@@ -2524,6 +2524,185 @@ var _ = Describe("Subscription", func() {
25242524
})
25252525
})
25262526
})
2527+
When("bundle unpack retries are enabled", func() {
2528+
FIt("should retry failing unpack jobs", func() {
2529+
By("Ensuring a registry to host bundle images")
2530+
local, err := Local(c)
2531+
Expect(err).NotTo(HaveOccurred(), "cannot determine if test running locally or on CI: %s", err)
2532+
2533+
var registryURL string
2534+
var copyImage func(dst, dstTag, src, srcTag string) error
2535+
if local {
2536+
registryURL, err = createDockerRegistry(c, generatedNamespace.GetName())
2537+
Expect(err).NotTo(HaveOccurred(), "error creating container registry: %s", err)
2538+
defer deleteDockerRegistry(c, generatedNamespace.GetName())
2539+
2540+
// ensure registry pod is ready before attempting port-forwarding
2541+
_ = awaitPod(GinkgoT(), c, generatedNamespace.GetName(), registryName, podReady)
2542+
2543+
err = registryPortForward(generatedNamespace.GetName())
2544+
Expect(err).NotTo(HaveOccurred(), "port-forwarding local registry: %s", err)
2545+
copyImage = func(dst, dstTag, src, srcTag string) error {
2546+
if !strings.HasPrefix(src, "docker://") {
2547+
src = fmt.Sprintf("docker://%s", src)
2548+
}
2549+
if !strings.HasPrefix(dst, "docker://") {
2550+
dst = fmt.Sprintf("docker://%s", dst)
2551+
}
2552+
_, err := skopeoLocalCopy(dst, dstTag, src, srcTag)
2553+
return err
2554+
}
2555+
} else {
2556+
registryURL = fmt.Sprintf("%s/%s", openshiftregistryFQDN, generatedNamespace.GetName())
2557+
registryAuth, err := openshiftRegistryAuth(c, generatedNamespace.GetName())
2558+
Expect(err).NotTo(HaveOccurred(), "error getting openshift registry authentication: %s", err)
2559+
copyImage = func(dst, dstTag, src, srcTag string) error {
2560+
if !strings.HasPrefix(src, "docker://") {
2561+
src = fmt.Sprintf("docker://%s", src)
2562+
}
2563+
if !strings.HasPrefix(dst, "docker://") {
2564+
dst = fmt.Sprintf("docker://%s", dst)
2565+
}
2566+
skopeoArgs := skopeoCopyCmd(dst, dstTag, src, srcTag, registryAuth)
2567+
err = createSkopeoPod(c, skopeoArgs, generatedNamespace.GetName())
2568+
if err != nil {
2569+
return fmt.Errorf("error creating skopeo pod: %v", err)
2570+
}
2571+
2572+
// wait for skopeo pod to exit successfully
2573+
awaitPod(GinkgoT(), c, generatedNamespace.GetName(), skopeo, func(pod *corev1.Pod) bool {
2574+
return pod.Status.Phase == corev1.PodSucceeded
2575+
})
2576+
2577+
if err := deleteSkopeoPod(c, generatedNamespace.GetName()); err != nil {
2578+
return fmt.Errorf("error deleting skopeo pod: %s", err)
2579+
}
2580+
return nil
2581+
}
2582+
}
2583+
2584+
// testImage is the name of the image used throughout the test - the image overwritten by skopeo
2585+
// the tag is generated randomly and appended to the end of the testImage
2586+
srcImage := "quay.io/olmtest/example-operator-bundle:"
2587+
srcTag := "0.1.0"
2588+
bundleImage := fmt.Sprint(registryURL, "/unpack-retry-bundle", ":")
2589+
bundleTag := genName("x")
2590+
//// hash hashes data with sha256 and returns the hex string.
2591+
//func hash(data string) string {
2592+
// // A SHA256 hash is 64 characters, which is within the 253 character limit for kube resource names
2593+
// h := fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
2594+
//
2595+
// // Make the hash 63 characters instead to comply with the 63 character limit for labels
2596+
// return fmt.Sprintf(h[:len(h)-1])
2597+
//}
2598+
unpackRetryCatalog := fmt.Sprintf(`
2599+
schema: olm.package
2600+
name: unpack-retry-package
2601+
defaultChannel: stable
2602+
---
2603+
schema: olm.channel
2604+
package: unpack-retry-package
2605+
name: stable
2606+
entries:
2607+
- name: unpack-retry-operator.v2.0.0
2608+
skipRange: ">=0.1.0 <2.0.0"
2609+
---
2610+
schema: olm.bundle
2611+
name: unpack-retry-operator.v1.0.0
2612+
package: unpack-retry-package
2613+
image: %s%s
2614+
properties:
2615+
- type: olm.package
2616+
value:
2617+
packageName: unpack-retry-package
2618+
version: 1.0.0
2619+
`, bundleImage, bundleTag)
2620+
2621+
By("creating a catalog referencing a non-existent bundle image")
2622+
unpackRetryProvider, err := NewRawFileBasedCatalogProvider(unpackRetryCatalog)
2623+
Expect(err).ToNot(HaveOccurred())
2624+
catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName())
2625+
magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, unpackRetryProvider)
2626+
Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil())
2627+
2628+
By("patching the OperatorGroup to reduce the bundle unpacking timeout")
2629+
ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()}
2630+
addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s")
2631+
2632+
By("creating a subscription for the missing bundle")
2633+
unpackRetrySubName := fmt.Sprintf("%s-unpack-retry-package-sub", generatedNamespace.GetName())
2634+
createSubscriptionForCatalog(crc, generatedNamespace.GetName(), unpackRetrySubName, catalogSourceName, "unpack-retry-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
2635+
2636+
By("waiting for bundle unpack to fail")
2637+
Eventually(
2638+
func() error {
2639+
fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{})
2640+
if err != nil {
2641+
return err
2642+
}
2643+
if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" {
2644+
return fmt.Errorf("%s condition not found", v1alpha1.SubscriptionBundleUnpackFailed)
2645+
}
2646+
return nil
2647+
},
2648+
5*time.Minute,
2649+
interval,
2650+
).Should(Succeed())
2651+
2652+
By("pushing missing bundle image")
2653+
Expect(copyImage(bundleImage, bundleTag, srcImage, srcTag)).To(Succeed())
2654+
2655+
By("patching the OperatorGroup to increase the bundle unpacking timeout")
2656+
addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "") // revert to default unpack timeout
2657+
2658+
By("patching operator group to enable unpack retries")
2659+
setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s")
2660+
2661+
By("waiting for checking for installPlan to indicate unpack succeeds")
2662+
Expect(fetchSubscription(crc, magicCatalog.GetNamespace(), unpackRetrySubName, subscriptionHasInstallPlanChecker)).To(Succeed())
2663+
time.Sleep(20 * time.Minute)
2664+
})
2665+
2666+
It("should not retry successful unpack jobs", func() {
2667+
By("deploying the testing catalog")
2668+
provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, failForwardTestDataBaseDir, "example-operator.v0.1.0.yaml"))
2669+
Expect(err).To(BeNil())
2670+
catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName())
2671+
magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider)
2672+
Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil())
2673+
2674+
By("creating the testing subscription")
2675+
subName := fmt.Sprintf("%s-packagea-sub", generatedNamespace.GetName())
2676+
createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "packageA", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
2677+
2678+
By("waiting until the subscription has an IP reference")
2679+
subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker)
2680+
Expect(err).Should(BeNil())
2681+
2682+
By("waiting for the v0.1.0 CSV to report a succeeded phase")
2683+
_, err = fetchCSV(crc, subscription.Status.CurrentCSV, generatedNamespace.GetName(), buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded))
2684+
Expect(err).ShouldNot(HaveOccurred())
2685+
2686+
By("patching operator group to enable unpack retries")
2687+
ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()}
2688+
setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s")
2689+
2690+
By("Ensuring successful bundle unpack jobs are not retried")
2691+
Consistently(func() error {
2692+
fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{})
2693+
if err != nil {
2694+
return err
2695+
}
2696+
if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpacking); cond.Status == corev1.ConditionTrue {
2697+
return fmt.Errorf("unexpected condition status for %s on subscription %s", v1alpha1.SubscriptionBundleUnpacking, subName)
2698+
}
2699+
if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpackFailed); cond.Status == corev1.ConditionTrue {
2700+
return fmt.Errorf("unexpected condition status for %s on subscription %s", v1alpha1.SubscriptionBundleUnpackFailed, subName)
2701+
}
2702+
return nil
2703+
}).Should(Succeed())
2704+
})
2705+
})
25272706
})
25282707

25292708
const (

Diff for: test/e2e/util.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,25 @@ func objectRefToNamespacedName(ip *corev1.ObjectReference) types.NamespacedName
9494
// adding the "operatorframework.io/bundle-unpack-timeout" annotation to an OperatorGroup
9595
// resource.
9696
func addBundleUnpackTimeoutOGAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, timeout string) {
97+
setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackTimeoutAnnotationKey, timeout)
98+
}
99+
100+
func setBundleUnpackRetryMinimumIntervalAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, interval string) {
101+
setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackRetryMinimumIntervalAnnotationKey, interval)
102+
}
103+
104+
func setOGAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, key, value string) {
97105
Eventually(func() error {
98106
og := &operatorsv1.OperatorGroup{}
99107
if err := c.Get(ctx, ogNN, og); err != nil {
100108
return err
101109
}
102110
annotations := og.GetAnnotations()
103-
annotations[bundle.BundleUnpackTimeoutAnnotationKey] = timeout
111+
if len(value) == 0 {
112+
delete(annotations, key)
113+
} else {
114+
annotations[key] = value
115+
}
104116
og.SetAnnotations(annotations)
105117

106118
return c.Update(ctx, og)

0 commit comments

Comments
 (0)