1
- // Copyright (c) 2023 Red Hat, Inc.
2
1
// Copyright Contributors to the Open Cluster Management project
3
2
4
3
package common
5
4
6
5
import (
7
6
"context"
8
- "errors"
9
7
"fmt"
8
+ "reflect"
10
9
"sort"
11
- "strings"
12
10
13
11
k8serrors "k8s.io/apimachinery/pkg/api/errors"
14
12
"k8s.io/apimachinery/pkg/types"
@@ -20,22 +18,22 @@ import (
20
18
policiesv1beta1 "open-cluster-management.io/governance-policy-propagator/api/v1beta1"
21
19
)
22
20
23
- // RootStatusUpdat update Root policy status with bound decisions and placements
24
- func RootStatusUpdate (c client.Client , rootPolicy * policiesv1.Policy ) error {
25
- placements , decisions , err := GetClusterDecisions (c , rootPolicy )
21
+ // RootStatusUpdate updates the root policy status with bound decisions, placements, and cluster status.
22
+ func RootStatusUpdate (ctx context. Context , c client.Client , rootPolicy * policiesv1.Policy ) ( DecisionSet , error ) {
23
+ placements , decisions , err := GetClusterDecisions (ctx , c , rootPolicy )
26
24
if err != nil {
27
25
log .Info ("Failed to get any placement decisions. Giving up on the request." )
28
26
29
- return errors . New ( "could not get the placement decisions" )
27
+ return nil , err
30
28
}
31
29
32
- cpcs , cpcsErr := CalculatePerClusterStatus (c , rootPolicy , decisions )
30
+ cpcs , cpcsErr := CalculatePerClusterStatus (ctx , c , rootPolicy , decisions )
33
31
if cpcsErr != nil {
34
32
// If there is a new replicated policy, then its lookup is expected to fail - it hasn't been created yet.
35
33
log .Error (cpcsErr , "Failed to get at least one replicated policy, but that may be expected. Ignoring." )
36
34
}
37
35
38
- err = c .Get (context . TODO () ,
36
+ err = c .Get (ctx ,
39
37
types.NamespacedName {
40
38
Namespace : rootPolicy .Namespace ,
41
39
Name : rootPolicy .Name ,
@@ -44,26 +42,32 @@ func RootStatusUpdate(c client.Client, rootPolicy *policiesv1.Policy) error {
44
42
log .Error (err , "Failed to refresh the cached policy. Will use existing policy." )
45
43
}
46
44
47
- // make a copy of the original status
48
- originalCPCS := make ([]* policiesv1.CompliancePerClusterStatus , len (rootPolicy .Status .Status ))
49
- copy (originalCPCS , rootPolicy .Status .Status )
45
+ complianceState := CalculateRootCompliance (cpcs )
46
+
47
+ if reflect .DeepEqual (rootPolicy .Status .Status , cpcs ) &&
48
+ rootPolicy .Status .ComplianceState == complianceState &&
49
+ reflect .DeepEqual (rootPolicy .Status .Placement , placements ) {
50
+ return decisions , nil
51
+ }
52
+
53
+ log .Info ("Updating the root policy status" , "RootPolicyName" , rootPolicy .Name , "Namespace" , rootPolicy .Namespace )
50
54
51
55
rootPolicy .Status .Status = cpcs
52
- rootPolicy .Status .ComplianceState = CalculateRootCompliance ( cpcs )
56
+ rootPolicy .Status .ComplianceState = complianceState
53
57
rootPolicy .Status .Placement = placements
54
58
55
- err = c .Status ().Update (context . TODO () , rootPolicy )
59
+ err = c .Status ().Update (ctx , rootPolicy )
56
60
if err != nil {
57
- return err
61
+ return nil , err
58
62
}
59
63
60
- return nil
64
+ return decisions , nil
61
65
}
62
66
63
67
// GetPolicyPlacementDecisions retrieves the placement decisions for a input PlacementBinding when
64
68
// the policy is bound within it. It can return an error if the PlacementBinding is invalid, or if
65
69
// a required lookup fails.
66
- func GetPolicyPlacementDecisions (c client.Client ,
70
+ func GetPolicyPlacementDecisions (ctx context. Context , c client.Client ,
67
71
instance * policiesv1.Policy , pb * policiesv1.PlacementBinding ,
68
72
) (decisions []appsv1.PlacementDecision , placements []* policiesv1.Placement , err error ) {
69
73
if ! HasValidPlacementRef (pb ) {
@@ -91,7 +95,7 @@ func GetPolicyPlacementDecisions(c client.Client,
91
95
if _ , exists := policySetSubjects [subject .Name ]; ! exists {
92
96
policySetSubjects [subject .Name ] = struct {}{}
93
97
94
- if IsPolicyInPolicySet (c , instance .GetName (), subject .Name , pb .GetNamespace ()) {
98
+ if IsPolicyInPolicySet (ctx , c , instance .GetName (), subject .Name , pb .GetNamespace ()) {
95
99
placements = append (placements , & policiesv1.Placement {
96
100
PlacementBinding : pb .GetName (),
97
101
PolicySet : subject .Name ,
@@ -115,7 +119,7 @@ func GetPolicyPlacementDecisions(c client.Client,
115
119
switch pb .PlacementRef .Kind {
116
120
case "PlacementRule" :
117
121
plr := & appsv1.PlacementRule {}
118
- if err := c .Get (context . TODO () , refNN , plr ); err != nil && ! k8serrors .IsNotFound (err ) {
122
+ if err := c .Get (ctx , refNN , plr ); err != nil && ! k8serrors .IsNotFound (err ) {
119
123
return nil , nil , fmt .Errorf ("failed to check for PlacementRule '%v': %w" , pb .PlacementRef .Name , err )
120
124
}
121
125
@@ -124,7 +128,7 @@ func GetPolicyPlacementDecisions(c client.Client,
124
128
}
125
129
case "Placement" :
126
130
pl := & clusterv1beta1.Placement {}
127
- if err := c .Get (context . TODO () , refNN , pl ); err != nil && ! k8serrors .IsNotFound (err ) {
131
+ if err := c .Get (ctx , refNN , pl ); err != nil && ! k8serrors .IsNotFound (err ) {
128
132
return nil , nil , fmt .Errorf ("failed to check for Placement '%v': %w" , pb .PlacementRef .Name , err )
129
133
}
130
134
@@ -143,154 +147,108 @@ func GetPolicyPlacementDecisions(c client.Client,
143
147
return nil , placements , nil
144
148
}
145
149
146
- decisions , err = GetDecisions (c , pb )
150
+ decisions , err = GetDecisions (ctx , c , pb )
147
151
148
152
return decisions , placements , err
149
153
}
150
154
151
- // GetAllClusterDecisions calculates which managed clusters should have a replicated policy, and
152
- // whether there are any BindingOverrides for that cluster. The placements array it returns is
153
- // sorted by PlacementBinding name. It can return an error if the PlacementBinding is invalid, or if
154
- // a required lookup fails.
155
- func GetAllClusterDecisions (
155
+ type DecisionSet map [appsv1.PlacementDecision ]bool
156
+
157
+ // GetClusterDecisions identifies all managed clusters which should have a replicated policy using the root policy
158
+ // This returns unique decisions and placements that are NOT under Restricted subset.
159
+ // Also this function returns placements that are under restricted subset.
160
+ // But these placements include decisions which are under non-restricted subset.
161
+ // In other words, this function returns placements which include at least one decision under non-restricted subset.
162
+ func GetClusterDecisions (
163
+ ctx context.Context ,
156
164
c client.Client ,
157
- instance * policiesv1.Policy , pbList * policiesv1. PlacementBindingList ,
165
+ rootPolicy * policiesv1.Policy ,
158
166
) (
159
- decisions map [appsv1. PlacementDecision ]policiesv1. BindingOverrides , placements [] * policiesv1.Placement , err error ,
167
+ [] * policiesv1.Placement , DecisionSet , error ,
160
168
) {
161
- decisions = make (map [appsv1.PlacementDecision ]policiesv1.BindingOverrides )
169
+ log := log .WithValues ("policyName" , rootPolicy .GetName (), "policyNamespace" , rootPolicy .GetNamespace ())
170
+ decisions := make (map [appsv1.PlacementDecision ]bool )
171
+
172
+ pbList := & policiesv1.PlacementBindingList {}
162
173
163
- // Process all placement bindings without subFilter
174
+ err := c .List (ctx , pbList , & client.ListOptions {Namespace : rootPolicy .GetNamespace ()})
175
+ if err != nil {
176
+ log .Error (err , "Could not list the placement bindings" )
177
+
178
+ return nil , decisions , err
179
+ }
180
+
181
+ placements := []* policiesv1.Placement {}
182
+
183
+ // Gather all placements and decisions when it is NOT policiesv1.Restricted
164
184
for i , pb := range pbList .Items {
165
185
if pb .SubFilter == policiesv1 .Restricted {
166
186
continue
167
187
}
168
188
169
- plcDecisions , plcPlacements , err := GetPolicyPlacementDecisions (c , instance , & pbList .Items [i ])
189
+ plcDecisions , plcPlacements , err := GetPolicyPlacementDecisions (ctx , c , rootPolicy , & pbList .Items [i ])
170
190
if err != nil {
171
191
return nil , nil , err
172
192
}
173
193
174
194
if len (plcDecisions ) == 0 {
175
- log .Info ("No placement decisions to process for this policy from this binding" ,
176
- "policyName" , instance .GetName (), "bindingName" , pb .GetName ())
195
+ log .Info ("No placement decisions to process for this policy from this non-restricted binding" ,
196
+ "policyName" , rootPolicy .GetName (), "bindingName" , pb .GetName ())
177
197
}
178
198
179
- for _ , decision := range plcDecisions {
180
- if overrides , ok := decisions [decision ]; ok {
181
- // Found cluster in the decision map
182
- if strings .EqualFold (pb .BindingOverrides .RemediationAction , string (policiesv1 .Enforce )) {
183
- overrides .RemediationAction = strings .ToLower (string (policiesv1 .Enforce ))
184
- decisions [decision ] = overrides
185
- }
186
- } else {
187
- // No found cluster in the decision map, add it to the map
188
- decisions [decision ] = policiesv1.BindingOverrides {
189
- // empty string if pb.BindingOverrides.RemediationAction is not defined
190
- RemediationAction : strings .ToLower (pb .BindingOverrides .RemediationAction ),
191
- }
192
- }
199
+ // Decisions are all unique
200
+ for _ , plcDecision := range plcDecisions {
201
+ decisions [plcDecision ] = true
193
202
}
194
203
195
204
placements = append (placements , plcPlacements ... )
196
205
}
197
206
198
- if len (decisions ) == 0 {
199
- sort .Slice (placements , func (i , j int ) bool {
200
- return placements [i ].PlacementBinding < placements [j ].PlacementBinding
201
- })
202
-
203
- // No decisions, and subfilters can't add decisions, so we can stop early.
204
- return nil , placements , nil
205
- }
206
-
207
- // Process all placement bindings with subFilter:restricted
207
+ // Gather placements which have at least one decision that is included in NON-Restricted
208
208
for i , pb := range pbList .Items {
209
209
if pb .SubFilter != policiesv1 .Restricted {
210
210
continue
211
211
}
212
212
213
213
foundInDecisions := false
214
214
215
- plcDecisions , plcPlacements , err := GetPolicyPlacementDecisions (c , instance , & pbList .Items [i ])
215
+ plcDecisions , plcPlacements , err := GetPolicyPlacementDecisions (ctx , c , rootPolicy , & pbList .Items [i ])
216
216
if err != nil {
217
217
return nil , nil , err
218
218
}
219
219
220
220
if len (plcDecisions ) == 0 {
221
- log .Info ("No placement decisions to process for this policy from this binding" ,
222
- "policyName" , instance .GetName (), "bindingName" , pb .GetName ())
221
+ log .Info ("No placement decisions to process for this policy from this restricted binding" ,
222
+ "policyName" , rootPolicy .GetName (), "bindingName" , pb .GetName ())
223
223
}
224
224
225
- for _ , decision := range plcDecisions {
226
- if overrides , ok := decisions [ decision ]; ok {
227
- // Found cluster in the decision map
225
+ // Decisions are all unique
226
+ for _ , plcDecision := range plcDecisions {
227
+ if _ , ok := decisions [ plcDecision ]; ok {
228
228
foundInDecisions = true
229
-
230
- if strings .EqualFold (pb .BindingOverrides .RemediationAction , string (policiesv1 .Enforce )) {
231
- overrides .RemediationAction = strings .ToLower (string (policiesv1 .Enforce ))
232
- decisions [decision ] = overrides
233
- }
234
229
}
230
+
231
+ decisions [plcDecision ] = true
235
232
}
236
233
237
234
if foundInDecisions {
238
235
placements = append (placements , plcPlacements ... )
239
236
}
240
237
}
241
238
242
- sort .Slice (placements , func (i , j int ) bool {
243
- return placements [i ].PlacementBinding < placements [j ].PlacementBinding
244
- })
245
-
246
- return decisions , placements , nil
247
- }
248
-
249
- type DecisionSet map [appsv1.PlacementDecision ]bool
250
-
251
- // getClusterDecisions identifies all managed clusters which should have a replicated policy using RootPolicy
252
- func GetClusterDecisions (
253
- c client.Client ,
254
- instance * policiesv1.Policy ,
255
- ) (
256
- []* policiesv1.Placement , DecisionSet , error ,
257
- ) {
258
- log := log .WithValues ("policyName" , instance .GetName (), "policyNamespace" , instance .GetNamespace ())
259
- decisions := make (map [appsv1.PlacementDecision ]bool )
260
-
261
- pbList := & policiesv1.PlacementBindingList {}
262
-
263
- err := c .List (context .TODO (), pbList , & client.ListOptions {Namespace : instance .GetNamespace ()})
264
- if err != nil {
265
- log .Error (err , "Could not list the placement bindings" )
266
-
267
- return nil , decisions , err
268
- }
269
-
270
- allClusterDecisions , placements , err := GetAllClusterDecisions (c , instance , pbList )
271
- if err != nil {
272
- return placements , decisions , err
273
- }
274
-
275
- if allClusterDecisions == nil {
276
- allClusterDecisions = make (map [appsv1.PlacementDecision ]policiesv1.BindingOverrides )
277
- }
278
-
279
- for dec := range allClusterDecisions {
280
- decisions [dec ] = true
281
- }
282
-
283
239
return placements , decisions , nil
284
240
}
285
241
286
242
// CalculatePerClusterStatus lists up all policies replicated from the input policy, and stores
287
243
// their compliance states in the result list. The result is sorted by cluster name. An error
288
244
// will be returned if lookup of a replicated policy fails, but all lookups will still be attempted.
289
245
func CalculatePerClusterStatus (
246
+ ctx context.Context ,
290
247
c client.Client ,
291
- instance * policiesv1.Policy , decisions DecisionSet ,
248
+ rootPolicy * policiesv1.Policy ,
249
+ decisions DecisionSet ,
292
250
) ([]* policiesv1.CompliancePerClusterStatus , error ) {
293
- if instance .Spec .Disabled {
251
+ if rootPolicy .Spec .Disabled {
294
252
return nil , nil
295
253
}
296
254
@@ -301,10 +259,10 @@ func CalculatePerClusterStatus(
301
259
for dec := range decisions {
302
260
replicatedPolicy := & policiesv1.Policy {}
303
261
key := types.NamespacedName {
304
- Namespace : dec .ClusterNamespace , Name : instance .Namespace + "." + instance .Name ,
262
+ Namespace : dec .ClusterNamespace , Name : rootPolicy .Namespace + "." + rootPolicy .Name ,
305
263
}
306
264
307
- err := c .Get (context . TODO () , key , replicatedPolicy )
265
+ err := c .Get (ctx , key , replicatedPolicy )
308
266
if err != nil {
309
267
if k8serrors .IsNotFound (err ) {
310
268
status = append (status , & policiesv1.CompliancePerClusterStatus {
@@ -332,7 +290,7 @@ func CalculatePerClusterStatus(
332
290
return status , lookupErr
333
291
}
334
292
335
- func IsPolicyInPolicySet (c client.Client , policyName , policySetName , namespace string ) bool {
293
+ func IsPolicyInPolicySet (ctx context. Context , c client.Client , policyName , policySetName , namespace string ) bool {
336
294
log := log .WithValues ("policyName" , policyName , "policySetName" , policySetName , "policyNamespace" , namespace )
337
295
338
296
policySet := policiesv1beta1.PolicySet {}
@@ -341,7 +299,7 @@ func IsPolicyInPolicySet(c client.Client, policyName, policySetName, namespace s
341
299
Namespace : namespace ,
342
300
}
343
301
344
- if err := c .Get (context . TODO () , setNN , & policySet ); err != nil {
302
+ if err := c .Get (ctx , setNN , & policySet ); err != nil {
345
303
log .Error (err , "Failed to get the policyset" )
346
304
347
305
return false
0 commit comments