Skip to content

Commit d5d6714

Browse files
kurlovporridge
andauthored
ROX-30138: Add tests for WithSelector (#496)
* Add tests for WithSelector * Fix lint * Small fix * Apply suggestions from code review Co-authored-by: Marcin Owsiany <[email protected]> * Fix indentation * Use hooks and add another test with two managers * Fix linting * Improve assertion * Add managers cleanup and change Eventually asserting for mutex objects * return protobuf Size() method for label setup check * Add a comment that unlabeled CR might be reonciled --------- Co-authored-by: Marcin Owsiany <[email protected]>
1 parent f20ae0f commit d5d6714

File tree

1 file changed

+211
-39
lines changed

1 file changed

+211
-39
lines changed

pkg/reconciler/reconciler_test.go

Lines changed: 211 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"context"
2222
"errors"
2323
"fmt"
24+
"slices"
2425
"strconv"
26+
"sync"
2527
"time"
2628

2729
. "github.com/onsi/ginkgo/v2"
@@ -1492,45 +1494,6 @@ var _ = Describe("Reconciler", func() {
14921494
})
14931495
})
14941496
})
1495-
When("label selector set", func() {
1496-
It("reconcile only matching CR", func() {
1497-
By("adding selector to the reconciler", func() {
1498-
selectorFoo := metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}
1499-
Expect(WithSelector(selectorFoo)(r)).To(Succeed())
1500-
})
1501-
1502-
By("adding not matching label to the CR", func() {
1503-
Expect(mgr.GetClient().Get(ctx, objKey, obj)).To(Succeed())
1504-
obj.SetLabels(map[string]string{"app": "bar"})
1505-
Expect(mgr.GetClient().Update(ctx, obj)).To(Succeed())
1506-
})
1507-
1508-
By("reconciling skipped and no actions for the release", func() {
1509-
res, err := r.Reconcile(ctx, req)
1510-
Expect(res).To(Equal(reconcile.Result{}))
1511-
Expect(err).ToNot(HaveOccurred())
1512-
})
1513-
1514-
By("verifying the release has not changed", func() {
1515-
rel, err := ac.Get(obj.GetName())
1516-
Expect(err).ToNot(HaveOccurred())
1517-
Expect(rel).NotTo(BeNil())
1518-
Expect(*rel).To(Equal(*currentRelease))
1519-
})
1520-
1521-
By("adding matching label to the CR", func() {
1522-
Expect(mgr.GetClient().Get(ctx, objKey, obj)).To(Succeed())
1523-
obj.SetLabels(map[string]string{"app": "foo"})
1524-
Expect(mgr.GetClient().Update(ctx, obj)).To(Succeed())
1525-
})
1526-
1527-
By("successfully reconciling with correct labels", func() {
1528-
res, err := r.Reconcile(ctx, req)
1529-
Expect(res).To(Equal(reconcile.Result{}))
1530-
Expect(err).ToNot(HaveOccurred())
1531-
})
1532-
})
1533-
})
15341497
})
15351498
})
15361499
})
@@ -1545,6 +1508,184 @@ var _ = Describe("Reconciler", func() {
15451508
})
15461509
})
15471510

1511+
_ = Describe("WithSelector", func() {
1512+
var (
1513+
ctx context.Context
1514+
cancel context.CancelFunc
1515+
matchingLabels map[string]string
1516+
anotherMatchingLabels map[string]string
1517+
mu sync.Mutex
1518+
doneChans []chan struct{}
1519+
)
1520+
1521+
BeforeEach(func() {
1522+
matchingLabels = map[string]string{"app": "foo"}
1523+
anotherMatchingLabels = map[string]string{"app": "bar"}
1524+
doneChans = nil
1525+
ctx, cancel = context.WithCancel(context.Background())
1526+
1527+
cleanupMgr := getManagerOrFail()
1528+
cleanupClient := cleanupMgr.GetClient()
1529+
crList := &unstructured.UnstructuredList{}
1530+
crList.SetGroupVersionKind(gvk)
1531+
Expect(cleanupClient.List(ctx, crList)).To(Succeed())
1532+
1533+
for i := range crList.Items {
1534+
cr := &crList.Items[i]
1535+
// Remove all finalizers to allow CR deletion
1536+
cr.SetFinalizers([]string{})
1537+
Expect(cleanupClient.Update(ctx, cr)).To(Succeed())
1538+
Expect(cleanupClient.Delete(ctx, cr)).To(Succeed())
1539+
}
1540+
1541+
// Wait for all CRs to be deleted
1542+
Eventually(func() bool {
1543+
crList := &unstructured.UnstructuredList{}
1544+
crList.SetGroupVersionKind(gvk)
1545+
if err := cleanupClient.List(ctx, crList); err != nil {
1546+
return false
1547+
}
1548+
return len(crList.Items) == 0
1549+
}, "10s", "100ms").Should(BeTrue())
1550+
})
1551+
1552+
AfterEach(func() {
1553+
// Cancel the context to stop all managers
1554+
if cancel != nil {
1555+
cancel()
1556+
}
1557+
// Wait for all managers to shut down completely
1558+
for _, done := range doneChans {
1559+
select {
1560+
case <-done:
1561+
// Manager has shut down
1562+
case <-time.After(5 * time.Second):
1563+
// Timeout waiting for manager shutdown
1564+
}
1565+
}
1566+
})
1567+
1568+
It("should only reconcile CRs matching the label selector", func() {
1569+
labeledObj := testutil.BuildTestCR(gvk)
1570+
labeledObj.SetName("labeled-cr-test1")
1571+
labeledObj.SetLabels(matchingLabels)
1572+
labeledObjKey := types.NamespacedName{Namespace: labeledObj.GetNamespace(), Name: labeledObj.GetName()}
1573+
1574+
anotherObj := testutil.BuildTestCR(gvk)
1575+
anotherObj.SetName("another-cr-test1")
1576+
anotherObjKey := types.NamespacedName{Namespace: anotherObj.GetNamespace(), Name: anotherObj.GetName()}
1577+
1578+
var reconciledCRs []string
1579+
postHook := makePostHook(&mu, &reconciledCRs)
1580+
mgr, done := setupManagerWithSelectorAndPostHook(ctx, postHook, matchingLabels)
1581+
doneChans = append(doneChans, done)
1582+
1583+
By("creating a CR without matching labels", func() {
1584+
Expect(mgr.GetClient().Create(ctx, anotherObj)).To(Succeed())
1585+
})
1586+
1587+
// There is no robust way to check whether controller-runtime will never do something.
1588+
// So there is a low chance that the unlabeled CR could be reconciled.
1589+
// See: https://kubernetes.slack.com/archives/C02MRBMN00Z/p1762251589740719
1590+
By("verifying that the labeled reconciler does not reconcile CR without labels", func() {
1591+
Consistently(func() bool {
1592+
mu.Lock()
1593+
defer mu.Unlock()
1594+
return len(reconciledCRs) == 0
1595+
}, "2s", "100ms").Should(BeTrue())
1596+
})
1597+
1598+
By("creating a CR with matching labels", func() {
1599+
Expect(mgr.GetClient().Create(ctx, labeledObj)).To(Succeed())
1600+
})
1601+
1602+
By("verifying only the labeled CR was reconciled", func() {
1603+
Eventually(func() bool {
1604+
mu.Lock()
1605+
defer mu.Unlock()
1606+
return len(reconciledCRs) == 1 && reconciledCRs[0] == labeledObjKey.Name
1607+
}).Should(BeTrue())
1608+
})
1609+
1610+
By("updating the unlabeled CR to have matching labels", func() {
1611+
Expect(mgr.GetClient().Get(ctx, anotherObjKey, anotherObj)).To(Succeed())
1612+
anotherObj.SetLabels(matchingLabels)
1613+
Expect(mgr.GetClient().Update(ctx, anotherObj)).To(Succeed())
1614+
})
1615+
1616+
By("verifying that both CRs were reconciled after setting label to the unlabeled CR", func() {
1617+
Eventually(func() bool {
1618+
mu.Lock()
1619+
defer mu.Unlock()
1620+
return len(reconciledCRs) == 2 &&
1621+
slices.Contains(reconciledCRs, labeledObjKey.Name) &&
1622+
slices.Contains(reconciledCRs, anotherObjKey.Name)
1623+
}, "10s", "100ms").Should(BeTrue())
1624+
})
1625+
})
1626+
1627+
It("should reconcile CRs independently when using two managers with different label selectors", func() {
1628+
labeledObj := testutil.BuildTestCR(gvk)
1629+
labeledObj.SetName("labeled-cr-test2")
1630+
labeledObj.SetLabels(matchingLabels)
1631+
labeledObjKey := types.NamespacedName{Namespace: labeledObj.GetNamespace(), Name: labeledObj.GetName()}
1632+
1633+
anotherObj := testutil.BuildTestCR(gvk)
1634+
anotherObj.SetName("another-cr-test2")
1635+
anotherObjKey := types.NamespacedName{Namespace: anotherObj.GetNamespace(), Name: anotherObj.GetName()}
1636+
1637+
var reconciledCRs []string
1638+
var anotherReconciledCRs []string
1639+
1640+
postHook := makePostHook(&mu, &reconciledCRs)
1641+
mgr, done := setupManagerWithSelectorAndPostHook(ctx, postHook, matchingLabels)
1642+
doneChans = append(doneChans, done)
1643+
1644+
postHook2 := makePostHook(&mu, &anotherReconciledCRs)
1645+
_, done2 := setupManagerWithSelectorAndPostHook(ctx, postHook2, anotherMatchingLabels)
1646+
doneChans = append(doneChans, done2)
1647+
1648+
By("creating a CR with matching labels for the first manager", func() {
1649+
Expect(mgr.GetClient().Create(ctx, labeledObj)).To(Succeed())
1650+
})
1651+
1652+
By("verifying that only the first manager reconciled the CR", func() {
1653+
Eventually(func() bool {
1654+
mu.Lock()
1655+
defer mu.Unlock()
1656+
return len(reconciledCRs) == 1 && reconciledCRs[0] == labeledObjKey.Name
1657+
}, "10s", "100ms").Should(BeTrue())
1658+
1659+
Consistently(func() bool {
1660+
mu.Lock()
1661+
defer mu.Unlock()
1662+
return len(anotherReconciledCRs) == 0
1663+
}, "2s", "100ms").Should(BeTrue())
1664+
})
1665+
1666+
By("creating a CR with matching labels for the second manager", func() {
1667+
Expect(mgr.GetClient().Create(ctx, anotherObj)).To(Succeed())
1668+
Expect(mgr.GetClient().Get(ctx, anotherObjKey, anotherObj)).To(Succeed())
1669+
anotherObj.SetLabels(anotherMatchingLabels)
1670+
Expect(mgr.GetClient().Update(ctx, anotherObj)).To(Succeed())
1671+
})
1672+
1673+
By("verifying that both managers reconcile only matching labels CRs", func() {
1674+
Eventually(func() bool {
1675+
mu.Lock()
1676+
defer mu.Unlock()
1677+
return len(reconciledCRs) == 1 && reconciledCRs[0] == labeledObjKey.Name
1678+
}, "10s", "100ms").Should(BeTrue())
1679+
1680+
Eventually(func() bool {
1681+
mu.Lock()
1682+
defer mu.Unlock()
1683+
return len(anotherReconciledCRs) == 1 && anotherReconciledCRs[0] == anotherObjKey.Name
1684+
}, "10s", "100ms").Should(BeTrue())
1685+
})
1686+
})
1687+
})
1688+
15481689
_ = Describe("Test custom controller setup", func() {
15491690
var (
15501691
mgr manager.Manager
@@ -1743,3 +1884,34 @@ func verifyEvent(ctx context.Context, cl client.Reader, obj metav1.Object, event
17431884
Reason: %q
17441885
Message: %q`, eventType, reason, message))
17451886
}
1887+
1888+
func makePostHook(mu *sync.Mutex, reconciledCRs *[]string) hook.PostHook {
1889+
return hook.PostHookFunc(func(obj *unstructured.Unstructured, _ release.Release, _ logr.Logger) error {
1890+
mu.Lock()
1891+
defer mu.Unlock()
1892+
if !slices.Contains(*reconciledCRs, obj.GetName()) {
1893+
*reconciledCRs = append(*reconciledCRs, obj.GetName())
1894+
}
1895+
return nil
1896+
})
1897+
}
1898+
1899+
func setupManagerWithSelectorAndPostHook(ctx context.Context, postHook hook.PostHook, matchingLabels map[string]string) (manager.Manager, chan struct{}) {
1900+
mgr := getManagerOrFail()
1901+
r, err := New(
1902+
WithGroupVersionKind(gvk),
1903+
WithChart(chrt),
1904+
WithSelector(metav1.LabelSelector{MatchLabels: matchingLabels}),
1905+
WithPostHook(postHook),
1906+
)
1907+
Expect(err).ToNot(HaveOccurred())
1908+
Expect(r.SetupWithManager(mgr)).To(Succeed())
1909+
1910+
done := make(chan struct{})
1911+
go func() {
1912+
defer close(done)
1913+
Expect(mgr.Start(ctx)).To(Succeed())
1914+
}()
1915+
Expect(mgr.GetCache().WaitForCacheSync(ctx)).To(BeTrue())
1916+
return mgr, done
1917+
}

0 commit comments

Comments
 (0)