Skip to content

Commit c6e2628

Browse files
Ensure new namespaces can trigger a reconcile
Previously, if the controller restarted, then policies with an evaluationInterval would not always start watching the namespaces specified in their namespaceSelectors. The intention was that new or modified namespaces could trigger evaluations even outside of the specified intervals, but this was not working. Refs: - https://issues.redhat.com/browse/ACM-24595 Signed-off-by: Justin Kulikauskas <[email protected]>
1 parent 6c80d0e commit c6e2628

File tree

4 files changed

+122
-20
lines changed

4 files changed

+122
-20
lines changed

pkg/common/namespace_selection.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,17 @@ func (r *NamespaceSelectorReconciler) Get(objNS string, objName string, t policy
251251
}
252252

253253
// HasUpdate indicates when the cached selection for this policy has been changed since the last
254-
// time that Get was called for that policy.
254+
// time that Get was called for that policy. Returns true if the selection isn't cached.
255255
func (r *NamespaceSelectorReconciler) HasUpdate(namespace string, name string) bool {
256256
r.lock.RLock()
257257
defer r.lock.RUnlock()
258258

259-
return r.selections[getKey(namespace, name)].hasUpdate
259+
sel, ok := r.selections[getKey(namespace, name)]
260+
if !ok {
261+
return true
262+
}
263+
264+
return sel.hasUpdate
260265
}
261266

262267
// Stop tells the SelectorReconciler to stop updating the cached selection for the name.

test/e2e/case14_selection_test.go

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
package e2e
55

66
import (
7-
"context"
7+
"strconv"
8+
"time"
89

910
. "github.com/onsi/ginkgo/v2"
1011
. "github.com/onsi/gomega"
@@ -16,12 +17,12 @@ import (
1617
"open-cluster-management.io/config-policy-controller/test/utils"
1718
)
1819

19-
const (
20-
case14LimitRangeFile string = "../resources/case14_namespaces/case14_limitrange.yaml"
21-
case14LimitRangeName string = "container-mem-limit-range"
22-
)
23-
2420
var _ = Describe("Test policy compliance with namespace selection", Ordered, func() {
21+
const (
22+
case14LimitRangeFile string = "../resources/case14_namespaces/case14_limitrange.yaml"
23+
case14LimitRangeName string = "container-mem-limit-range"
24+
)
25+
2526
checkRelated := func(policy *unstructured.Unstructured) []interface{} {
2627
related, _, err := unstructured.NestedSlice(policy.Object, "status", "relatedObjects")
2728
if err != nil {
@@ -50,18 +51,18 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
5051
},
5152
}
5253

53-
BeforeAll(func() {
54+
BeforeAll(func(ctx SpecContext) {
5455
By("Create Namespaces if needed")
5556
namespaces := clientManaged.CoreV1().Namespaces()
5657
for _, ns := range testNamespaces {
57-
if _, err := namespaces.Get(context.TODO(), ns, metav1.GetOptions{}); err != nil && errors.IsNotFound(err) {
58-
Expect(namespaces.Create(context.TODO(), &corev1.Namespace{
58+
if _, err := namespaces.Get(ctx, ns, metav1.GetOptions{}); err != nil && errors.IsNotFound(err) {
59+
Expect(namespaces.Create(ctx, &corev1.Namespace{
5960
ObjectMeta: metav1.ObjectMeta{
6061
Name: ns,
6162
},
6263
}, metav1.CreateOptions{})).NotTo(BeNil())
6364
}
64-
Expect(namespaces.Get(context.TODO(), ns, metav1.GetOptions{})).NotTo(BeNil())
65+
Expect(namespaces.Get(ctx, ns, metav1.GetOptions{})).NotTo(BeNil())
6566
}
6667
})
6768

@@ -135,15 +136,15 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
135136
}
136137
})
137138

138-
It("should be noncompliant after adding new matching namespace", func() {
139+
It("should be noncompliant after adding new matching namespace", func(ctx SpecContext) {
139140
By("Creating namespace " + newNs)
140141
namespaces := clientManaged.CoreV1().Namespaces()
141-
Expect(namespaces.Create(context.TODO(), &corev1.Namespace{
142+
Expect(namespaces.Create(ctx, &corev1.Namespace{
142143
ObjectMeta: metav1.ObjectMeta{
143144
Name: newNs,
144145
},
145146
}, metav1.CreateOptions{})).NotTo(BeNil())
146-
Expect(namespaces.Get(context.TODO(), newNs, metav1.GetOptions{})).NotTo(BeNil())
147+
Expect(namespaces.Get(ctx, newNs, metav1.GetOptions{})).NotTo(BeNil())
147148
for _, policy := range policyTests {
148149
By("Checking that " + policy.name + " is NonCompliant")
149150
Eventually(func(g Gomega) {
@@ -238,12 +239,12 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
238239
}
239240
})
240241

241-
It("should update relatedObjects after deleting a namespace", func() {
242+
It("should update relatedObjects after deleting a namespace", func(ctx SpecContext) {
242243
By("Deleting namespace " + newNs)
243244
namespaces := clientManaged.CoreV1().Namespaces()
244-
Expect(namespaces.Delete(context.TODO(), newNs, metav1.DeleteOptions{})).To(Succeed())
245+
Expect(namespaces.Delete(ctx, newNs, metav1.DeleteOptions{})).To(Succeed())
245246
Eventually(func() bool {
246-
_, err := namespaces.Get(context.TODO(), newNs, metav1.GetOptions{})
247+
_, err := namespaces.Get(ctx, newNs, metav1.GetOptions{})
247248

248249
return errors.IsNotFound(err)
249250
}, defaultTimeoutSeconds, 1).Should(BeTrue())
@@ -268,3 +269,77 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
268269
}
269270
})
270271
})
272+
273+
var _ = Describe("Test NS selection events after controller restart", Label("running-in-cluster"), Ordered, func() {
274+
const (
275+
configmapName = "case14-cm"
276+
policyYAML = "../resources/case14_namespaces/case14_configmaps_policy.yaml"
277+
policyName = "case14-configmaps"
278+
)
279+
280+
BeforeAll(func(ctx SpecContext) {
281+
By("Create initial Namespaces")
282+
namespaces := clientManaged.CoreV1().Namespaces()
283+
for i := range 2 {
284+
ns := "case14-" + strconv.Itoa(i)
285+
if _, err := namespaces.Get(ctx, ns, metav1.GetOptions{}); err != nil && errors.IsNotFound(err) {
286+
Expect(namespaces.Create(ctx, &corev1.Namespace{
287+
ObjectMeta: metav1.ObjectMeta{
288+
Name: ns,
289+
},
290+
}, metav1.CreateOptions{})).NotTo(BeNil())
291+
}
292+
Expect(namespaces.Get(ctx, ns, metav1.GetOptions{})).NotTo(BeNil())
293+
}
294+
295+
By("Creating " + policyName + " on managed")
296+
utils.Kubectl("apply", "-f", policyYAML, "-n", testNamespace)
297+
})
298+
299+
AfterAll(func() {
300+
By("Deleting namespaces")
301+
for i := range 5 {
302+
ns := "case14-" + strconv.Itoa(i)
303+
utils.KubectlDelete("namespace", ns)
304+
}
305+
306+
By("Deleting policy " + policyName)
307+
utils.KubectlDelete("-f", policyYAML, "-n", testNamespace)
308+
})
309+
310+
It("should initially be compliant with 2 related objects", func() {
311+
Eventually(func(g Gomega) {
312+
managedPlc := utils.GetWithTimeout(clientManagedDynamic, gvrConfigPolicy,
313+
policyName, testNamespace, true, defaultTimeoutSeconds)
314+
315+
utils.CheckComplianceStatus(g, managedPlc, "Compliant")
316+
317+
related, found, err := unstructured.NestedSlice(managedPlc.Object, "status", "relatedObjects")
318+
g.Expect(err).NotTo(HaveOccurred())
319+
g.Expect(found).To(BeTrue())
320+
g.Expect(related).To(HaveLen(2))
321+
}, defaultTimeoutSeconds, 1).Should(Succeed())
322+
})
323+
324+
It("should evaluate when a new namespace is created after restarting the controller", func(ctx SpecContext) {
325+
By("Deleting the controller pod")
326+
utils.KubectlDelete("pods", "-l=name=config-policy-controller",
327+
"-n=open-cluster-management-agent-addon", "--wait=true")
328+
time.Sleep(time.Second)
329+
330+
By("Waiting for the deployment to be ready again")
331+
utils.Kubectl("wait", "--for=condition=Available", "deployment", "config-policy-controller",
332+
"-n=open-cluster-management-agent-addon")
333+
334+
By("Creating a new namespace")
335+
Expect(clientManaged.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{
336+
ObjectMeta: metav1.ObjectMeta{
337+
Name: "case14-3",
338+
},
339+
}, metav1.CreateOptions{})).NotTo(BeNil())
340+
341+
By("Checking for the policy to create the new configmap")
342+
utils.GetWithTimeout(clientManagedDynamic, gvrConfigMap, "case14-cm",
343+
"case14-3", true, defaultTimeoutSeconds)
344+
})
345+
})

test/e2e/case29_trigger_uninstall_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
)
1818

1919
// This test only works when the controller is running in the cluster.
20-
var _ = Describe("Clean up during uninstalls", Label("running-in-cluster"), Ordered, func() {
20+
var _ = Describe("Clean up during uninstalls", Label("running-in-cluster"), Serial, func() {
2121
const (
2222
configMapName string = "case29-trigger-uninstall"
2323
deploymentName string = "config-policy-controller"
@@ -110,7 +110,7 @@ var _ = Describe("Clean up during uninstalls", Label("running-in-cluster"), Orde
110110
Expect(policy2.GetFinalizers()).To(BeEmpty())
111111
})
112112

113-
AfterAll(func() {
113+
AfterEach(func() {
114114
deleteConfigPolicies([]string{policyName, policy2Name})
115115

116116
err := clientManaged.CoreV1().ConfigMaps("default").Delete(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: policy.open-cluster-management.io/v1
2+
kind: ConfigurationPolicy
3+
metadata:
4+
name: case14-configmaps
5+
spec:
6+
evaluationInterval:
7+
compliant: 30m
8+
noncompliant: 30m
9+
namespaceSelector:
10+
include:
11+
- 'case14-*'
12+
object-templates:
13+
- complianceType: musthave
14+
objectDefinition:
15+
apiVersion: v1
16+
kind: ConfigMap
17+
metadata:
18+
name: case14-cm
19+
data:
20+
foo: bar
21+
remediationAction: enforce
22+
severity: high

0 commit comments

Comments
 (0)