open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/propagator/replication_test.go (about) 1 package propagator 2 3 import ( 4 "context" 5 "reflect" 6 "strings" 7 "testing" 8 9 k8sdepwatches "github.com/stolostron/kubernetes-dependency-watches/client" 10 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 k8sruntime "k8s.io/apimachinery/pkg/runtime" 12 "sigs.k8s.io/controller-runtime/pkg/client" 13 fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake" 14 15 policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1" 16 policiesv1beta1 "open-cluster-management.io/governance-policy-propagator/api/v1beta1" 17 ) 18 19 func fakeBasicPolicy(name, namespace string) *policiesv1.Policy { 20 return &policiesv1.Policy{ 21 TypeMeta: v1.TypeMeta{ 22 Kind: policiesv1.Kind, 23 APIVersion: policiesv1.GroupVersion.String(), 24 }, 25 ObjectMeta: v1.ObjectMeta{ 26 Name: name, 27 Namespace: namespace, 28 }, 29 Spec: policiesv1.PolicySpec{ 30 Dependencies: []policiesv1.PolicyDependency{}, 31 PolicyTemplates: []*policiesv1.PolicyTemplate{{ 32 ExtraDependencies: []policiesv1.PolicyDependency{}, 33 }}, 34 }, 35 } 36 } 37 38 func fakePolicyWithDeps(name, namespace string, deps, extraDeps []policiesv1.PolicyDependency) *policiesv1.Policy { 39 pol := fakeBasicPolicy(name, namespace) 40 pol.Spec.Dependencies = deps 41 pol.Spec.PolicyTemplates[0].ExtraDependencies = extraDeps 42 43 return pol 44 } 45 46 func fakePolicySet(name, namespace string, policies ...string) *policiesv1beta1.PolicySet { 47 pols := []policiesv1beta1.NonEmptyString{} 48 for _, p := range policies { 49 pols = append(pols, policiesv1beta1.NonEmptyString(p)) 50 } 51 52 return &policiesv1beta1.PolicySet{ 53 TypeMeta: v1.TypeMeta{ 54 Kind: policiesv1.PolicySetKind, 55 APIVersion: policiesv1beta1.GroupVersion.String(), 56 }, 57 ObjectMeta: v1.ObjectMeta{ 58 Name: name, 59 Namespace: namespace, 60 }, 61 Spec: policiesv1beta1.PolicySetSpec{ 62 Policies: pols, 63 }, 64 } 65 } 66 67 func fakeDependencyFromObj(obj client.Object, compliance string) policiesv1.PolicyDependency { 68 return policiesv1.PolicyDependency{ 69 TypeMeta: v1.TypeMeta{ 70 Kind: obj.GetObjectKind().GroupVersionKind().Kind, 71 APIVersion: obj.GetObjectKind().GroupVersionKind().GroupVersion().String(), 72 }, 73 Name: obj.GetName(), 74 Namespace: obj.GetNamespace(), 75 Compliance: policiesv1.ComplianceState(compliance), 76 } 77 } 78 79 func TestEquivalentReplicatedPolicies(t *testing.T) { 80 basePolicy := fakeBasicPolicy("base-policy", "default") 81 82 withAnnotation := basePolicy.DeepCopy() 83 withAnnotation.SetAnnotations(map[string]string{ 84 "test-annotation.io/foo": "bar", 85 }) 86 87 withLabel := basePolicy.DeepCopy() 88 withLabel.SetLabels(map[string]string{ 89 "test-label.io/fizz": "buzz", 90 }) 91 92 thwimpDep := fakeDependencyFromObj(fakeBasicPolicy("thwimp", "thwump"), "NonCompliant") 93 withDependency := fakePolicyWithDeps("base-policy", "default", []policiesv1.PolicyDependency{thwimpDep}, nil) 94 95 withStatus := basePolicy.DeepCopy() 96 withStatus.Status = policiesv1.PolicyStatus{ 97 ComplianceState: policiesv1.Pending, 98 } 99 100 tests := map[string]struct { 101 comparePlc *policiesv1.Policy 102 expected bool 103 }{ 104 "reflexive": {basePolicy, true}, 105 "different annotations": {withAnnotation, false}, 106 "different labels": {withLabel, false}, 107 "different specs": {withDependency, false}, 108 "different status": {withStatus, true}, 109 } 110 111 for name, test := range tests { 112 t.Run(name, func(t *testing.T) { 113 if equivalentReplicatedPolicies(basePolicy, test.comparePlc) != test.expected { 114 if test.expected { 115 t.Fatalf("Expected policies to be equivalent: %+v, %+v", basePolicy, test.comparePlc) 116 } else { 117 t.Fatalf("Expected policies not to be equivalent: %+v, %+v", basePolicy, test.comparePlc) 118 } 119 } 120 }) 121 } 122 } 123 124 func TestCanonicalizeDependencies(t *testing.T) { 125 depPol := func(name, namespace, compliance string) policiesv1.PolicyDependency { 126 return fakeDependencyFromObj(fakeBasicPolicy(name, namespace), compliance) 127 } 128 129 unusualDependency := policiesv1.PolicyDependency{ 130 TypeMeta: v1.TypeMeta{ 131 Kind: "WeirdPolicy", 132 APIVersion: "something.com/v4beta7", 133 }, 134 Name: "foo", 135 Namespace: "bar", 136 Compliance: "Auditing", 137 } 138 139 fooSet := fakePolicySet("foo", "default", "one", "two") 140 barSet := fakePolicySet("bar", "other-ns", "three", "four", "five") 141 fakeScheme := k8sruntime.NewScheme() 142 143 if err := policiesv1beta1.AddToScheme(fakeScheme); err != nil { 144 t.Fatalf("Unexpected error building scheme: %v", err) 145 } 146 147 fakeClient := fakeClient.NewClientBuilder(). 148 WithScheme(fakeScheme). 149 WithObjects(fooSet, barSet). 150 Build() 151 152 fakeReconciler := Propagator{Client: fakeClient} 153 154 tests := map[string]struct { 155 input []policiesv1.PolicyDependency 156 want []policiesv1.PolicyDependency 157 }{ 158 "empty": { 159 input: []policiesv1.PolicyDependency{}, 160 want: []policiesv1.PolicyDependency{}, 161 }, 162 "single compliant set": { 163 input: []policiesv1.PolicyDependency{ 164 fakeDependencyFromObj(fooSet, "Compliant"), 165 }, 166 want: []policiesv1.PolicyDependency{ 167 depPol("default.one", "", "Compliant"), 168 depPol("default.two", "", "Compliant"), 169 }, 170 }, 171 "two sets, mixed compliance": { 172 input: []policiesv1.PolicyDependency{ 173 fakeDependencyFromObj(fooSet, "Compliant"), 174 fakeDependencyFromObj(barSet, "NonCompliant"), 175 }, 176 want: []policiesv1.PolicyDependency{ 177 depPol("default.one", "", "Compliant"), 178 depPol("default.two", "", "Compliant"), 179 depPol("other-ns.three", "", "NonCompliant"), 180 depPol("other-ns.four", "", "NonCompliant"), 181 depPol("other-ns.five", "", "NonCompliant"), 182 }, 183 }, 184 "policies with namespaces": { 185 input: []policiesv1.PolicyDependency{ 186 depPol("red", "colors", "Compliant"), 187 depPol("blue", "colors", "NonCompliant"), 188 }, 189 want: []policiesv1.PolicyDependency{ 190 depPol("colors.red", "", "Compliant"), 191 depPol("colors.blue", "", "NonCompliant"), 192 }, 193 }, 194 "policies without namespaces": { 195 input: []policiesv1.PolicyDependency{ 196 depPol("magikarp", "", "Compliant"), 197 depPol("gyarados", "", "Compliant"), 198 }, 199 want: []policiesv1.PolicyDependency{ 200 depPol("sentinel.magikarp", "", "Compliant"), 201 depPol("sentinel.gyarados", "", "Compliant"), 202 }, 203 }, 204 "policies already canonicalized": { 205 input: []policiesv1.PolicyDependency{ 206 depPol("legendary.lugia", "", "Terminating"), 207 depPol("legendary.ho-oh", "", "Terminating"), 208 }, 209 want: []policiesv1.PolicyDependency{ 210 depPol("legendary.lugia", "", "Terminating"), 211 depPol("legendary.ho-oh", "", "Terminating"), 212 }, 213 }, 214 "unusual dependency should be unchanged": { 215 input: []policiesv1.PolicyDependency{unusualDependency}, 216 want: []policiesv1.PolicyDependency{unusualDependency}, 217 }, 218 } 219 220 for name, test := range tests { 221 t.Run(name, func(t *testing.T) { 222 got, err := fakeReconciler.canonicalizeDependencies(context.TODO(), test.input, "sentinel") 223 if err != nil { 224 t.Fatal("Got unexpected error") 225 } 226 227 if !reflect.DeepEqual(test.want, got) { 228 t.Fatalf("expected: %v, got: %v", test.want, got) 229 } 230 }) 231 } 232 } 233 234 func TestGetPolicySetDependencies(t *testing.T) { 235 makeDepPlcSet := func(name, namespace, compliance string) policiesv1.PolicyDependency { 236 return fakeDependencyFromObj(fakePolicySet(name, namespace), compliance) 237 } 238 239 makeDepPolicy := func(name, namespace, compliance string) policiesv1.PolicyDependency { 240 return fakeDependencyFromObj(fakeBasicPolicy(name, namespace), compliance) 241 } 242 243 idsFromDeps := func(deps ...policiesv1.PolicyDependency) map[k8sdepwatches.ObjectIdentifier]bool { 244 ids := make(map[k8sdepwatches.ObjectIdentifier]bool) 245 246 for _, d := range deps { 247 ids[k8sdepwatches.ObjectIdentifier{ 248 Group: strings.Split(d.APIVersion, "/")[0], 249 Version: strings.Split(d.APIVersion, "/")[1], 250 Kind: d.Kind, 251 Namespace: d.Namespace, 252 Name: d.Name, 253 }] = true 254 } 255 256 return ids 257 } 258 259 fooSet := makeDepPlcSet("foo", "default", "Compliant") 260 barSet := makeDepPlcSet("bar", "default", "NonCompliant") 261 myPolicy := makeDepPolicy("mypolicy", "mynamespace", "Compliant") 262 fooSetEmptyNamespace := makeDepPlcSet("foo", "", "Compliant") 263 fooSetInferredNamespace := makeDepPlcSet("foo", "testns", "Compliant") 264 265 tests := map[string]struct { 266 inputDeps []policiesv1.PolicyDependency 267 inputExtraDeps []policiesv1.PolicyDependency 268 want map[k8sdepwatches.ObjectIdentifier]bool 269 }{ 270 "no dependencies": { 271 want: idsFromDeps(), 272 }, 273 "one top set dependency": { 274 inputDeps: []policiesv1.PolicyDependency{fooSet}, 275 want: idsFromDeps(fooSet), 276 }, 277 "one extra set dependency": { 278 inputExtraDeps: []policiesv1.PolicyDependency{fooSet}, 279 want: idsFromDeps(fooSet), 280 }, 281 "set dependencies at both levels": { 282 inputDeps: []policiesv1.PolicyDependency{fooSet}, 283 inputExtraDeps: []policiesv1.PolicyDependency{barSet}, 284 want: idsFromDeps(fooSet, barSet), 285 }, 286 "one policy should be ignored": { 287 inputDeps: []policiesv1.PolicyDependency{myPolicy}, 288 want: idsFromDeps(), 289 }, 290 "policies should be ignored when mixed with sets": { 291 inputDeps: []policiesv1.PolicyDependency{fooSet, myPolicy}, 292 want: idsFromDeps(fooSet), 293 }, 294 "namespace should be inferred from policy when not provided": { 295 inputDeps: []policiesv1.PolicyDependency{fooSetEmptyNamespace}, 296 want: idsFromDeps(fooSetInferredNamespace), 297 }, 298 "extraDependency namespace should be inferred from policy when not provided": { 299 inputExtraDeps: []policiesv1.PolicyDependency{fooSetEmptyNamespace}, 300 want: idsFromDeps(fooSetInferredNamespace), 301 }, 302 } 303 304 for name, test := range tests { 305 t.Run(name, func(t *testing.T) { 306 testpol := fakePolicyWithDeps("testpolicy", "testns", test.inputDeps, test.inputExtraDeps) 307 308 got := getPolicySetDependencies(testpol) 309 if !reflect.DeepEqual(got, test.want) { 310 t.Fatalf("expected: %v, got: %v", test.want, got) 311 } 312 }) 313 } 314 }