Skip to content

Commit d430876

Browse files
committed
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 d430876

File tree

4 files changed

+122
-19
lines changed

4 files changed

+122
-19
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 & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package e2e
55

66
import (
77
"context"
8+
"strconv"
9+
"time"
810

911
. "github.com/onsi/ginkgo/v2"
1012
. "github.com/onsi/gomega"
@@ -16,12 +18,12 @@ import (
1618
"open-cluster-management.io/config-policy-controller/test/utils"
1719
)
1820

19-
const (
20-
case14LimitRangeFile string = "../resources/case14_namespaces/case14_limitrange.yaml"
21-
case14LimitRangeName string = "container-mem-limit-range"
22-
)
23-
2421
var _ = Describe("Test policy compliance with namespace selection", Ordered, func() {
22+
const (
23+
case14LimitRangeFile string = "../resources/case14_namespaces/case14_limitrange.yaml"
24+
case14LimitRangeName string = "container-mem-limit-range"
25+
)
26+
2527
checkRelated := func(policy *unstructured.Unstructured) []interface{} {
2628
related, _, err := unstructured.NestedSlice(policy.Object, "status", "relatedObjects")
2729
if err != nil {
@@ -50,18 +52,18 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
5052
},
5153
}
5254

53-
BeforeAll(func() {
55+
BeforeAll(func(ctx context.Context) {
5456
By("Create Namespaces if needed")
5557
namespaces := clientManaged.CoreV1().Namespaces()
5658
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{
59+
if _, err := namespaces.Get(ctx, ns, metav1.GetOptions{}); err != nil && errors.IsNotFound(err) {
60+
Expect(namespaces.Create(ctx, &corev1.Namespace{
5961
ObjectMeta: metav1.ObjectMeta{
6062
Name: ns,
6163
},
6264
}, metav1.CreateOptions{})).NotTo(BeNil())
6365
}
64-
Expect(namespaces.Get(context.TODO(), ns, metav1.GetOptions{})).NotTo(BeNil())
66+
Expect(namespaces.Get(ctx, ns, metav1.GetOptions{})).NotTo(BeNil())
6567
}
6668
})
6769

@@ -135,15 +137,15 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
135137
}
136138
})
137139

138-
It("should be noncompliant after adding new matching namespace", func() {
140+
It("should be noncompliant after adding new matching namespace", func(ctx context.Context) {
139141
By("Creating namespace " + newNs)
140142
namespaces := clientManaged.CoreV1().Namespaces()
141-
Expect(namespaces.Create(context.TODO(), &corev1.Namespace{
143+
Expect(namespaces.Create(ctx, &corev1.Namespace{
142144
ObjectMeta: metav1.ObjectMeta{
143145
Name: newNs,
144146
},
145147
}, metav1.CreateOptions{})).NotTo(BeNil())
146-
Expect(namespaces.Get(context.TODO(), newNs, metav1.GetOptions{})).NotTo(BeNil())
148+
Expect(namespaces.Get(ctx, newNs, metav1.GetOptions{})).NotTo(BeNil())
147149
for _, policy := range policyTests {
148150
By("Checking that " + policy.name + " is NonCompliant")
149151
Eventually(func(g Gomega) {
@@ -238,12 +240,12 @@ var _ = Describe("Test policy compliance with namespace selection", Ordered, fun
238240
}
239241
})
240242

241-
It("should update relatedObjects after deleting a namespace", func() {
243+
It("should update relatedObjects after deleting a namespace", func(ctx context.Context) {
242244
By("Deleting namespace " + newNs)
243245
namespaces := clientManaged.CoreV1().Namespaces()
244-
Expect(namespaces.Delete(context.TODO(), newNs, metav1.DeleteOptions{})).To(Succeed())
246+
Expect(namespaces.Delete(ctx, newNs, metav1.DeleteOptions{})).To(Succeed())
245247
Eventually(func() bool {
246-
_, err := namespaces.Get(context.TODO(), newNs, metav1.GetOptions{})
248+
_, err := namespaces.Get(ctx, newNs, metav1.GetOptions{})
247249

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

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)