github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/multiclusterapplicationconfiguration_webhook_test.go (about) 1 // Copyright (c) 2021, 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package webhooks 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 11 admissionv1 "k8s.io/api/admission/v1" 12 13 "github.com/prometheus/client_golang/prometheus/testutil" 14 "github.com/stretchr/testify/assert" 15 v1alpha12 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 16 "github.com/verrazzano/verrazzano/application-operator/constants" 17 "github.com/verrazzano/verrazzano/application-operator/metricsexporter" 18 corev1 "k8s.io/api/core/v1" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "sigs.k8s.io/controller-runtime/pkg/client/fake" 21 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 22 ) 23 24 // newMultiClusterApplicationConfigurationValidator creates a new MultiClusterApplicationConfigurationValidator 25 func newMultiClusterApplicationConfigurationValidator() MultiClusterApplicationConfigurationValidator { 26 scheme := newScheme() 27 decoder, _ := admission.NewDecoder(scheme) 28 cli := fake.NewClientBuilder().WithScheme(scheme).Build() 29 v := MultiClusterApplicationConfigurationValidator{client: cli, decoder: decoder} 30 return v 31 } 32 33 // TestValidationFailureForMultiClusterApplicationConfigurationCreationWithoutTargetClusters tests preventing the creation 34 // of a MultiClusterApplicationConfiguration resources that is missing Placement information. 35 // GIVEN a call to validate a MultiClusterApplicationConfiguration resource 36 // WHEN the MultiClusterApplicationConfiguration resource is missing Placement information 37 // THEN the validation should fail. 38 func TestValidationFailureForMultiClusterApplicationConfigurationCreationWithoutTargetClusters(t *testing.T) { 39 asrt := assert.New(t) 40 v := newMultiClusterApplicationConfigurationValidator() 41 p := v1alpha12.MultiClusterApplicationConfiguration{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "test-mcapplicationconfiguration-name", 44 Namespace: constants.VerrazzanoMultiClusterNamespace, 45 }, 46 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{}, 47 } 48 49 req := newAdmissionRequest(admissionv1.Create, p) 50 res := v.Handle(context.TODO(), req) 51 asrt.False(res.Allowed, "Expected multi-cluster application configuration validation to fail due to missing placement information.") 52 asrt.Contains(res.Result.Reason, "target cluster") 53 54 req = newAdmissionRequest(admissionv1.Update, p) 55 res = v.Handle(context.TODO(), req) 56 asrt.False(res.Allowed, "Expected multi-cluster application configuration validation to fail due to missing placement information.") 57 asrt.Contains(res.Result.Reason, "target cluster") 58 } 59 60 // TestValidationFailureForMultiClusterApplicationConfigurationCreationTargetingMissingManagedCluster tests preventing the creation 61 // of a MultiClusterApplicationConfiguration resources that references a non-existent managed cluster. 62 // GIVEN a call to validate a MultiClusterApplicationConfiguration resource 63 // WHEN the MultiClusterApplicationConfiguration resource references a VerrazzanoManagedCluster that does not exist 64 // THEN the validation should fail. 65 func TestValidationFailureForMultiClusterApplicationConfigurationCreationTargetingMissingManagedCluster(t *testing.T) { 66 asrt := assert.New(t) 67 v := newMultiClusterApplicationConfigurationValidator() 68 p := v1alpha12.MultiClusterApplicationConfiguration{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Name: "test-mcapplicationconfiguration-name", 71 Namespace: constants.VerrazzanoMultiClusterNamespace, 72 }, 73 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 74 Placement: v1alpha12.Placement{ 75 Clusters: []v1alpha12.Cluster{{Name: "invalid-cluster-name"}}, 76 }, 77 }, 78 } 79 80 req := newAdmissionRequest(admissionv1.Create, p) 81 res := v.Handle(context.TODO(), req) 82 asrt.False(res.Allowed, "Expected multi-cluster application configuration validation to fail due to missing placement information.") 83 asrt.Contains(res.Result.Reason, "invalid-cluster-name") 84 85 req = newAdmissionRequest(admissionv1.Update, p) 86 res = v.Handle(context.TODO(), req) 87 asrt.False(res.Allowed, "Expected multi-cluster application configuration validation to fail due to missing placement information.") 88 asrt.Contains(res.Result.Reason, "invalid-cluster-name") 89 } 90 91 // TestValidationSuccessForMultiClusterApplicationConfigurationCreationTargetingExistingManagedCluster tests allowing the creation 92 // of a MultiClusterApplicationConfiguration resources that references an existent managed cluster. 93 // GIVEN a call to validate a MultiClusterApplicationConfiguration resource 94 // WHEN the MultiClusterApplicationConfiguration resource references a VerrazzanoManagedCluster that does exist 95 // THEN the validation should pass. 96 func TestValidationSuccessForMultiClusterApplicationConfigurationCreationTargetingExistingManagedCluster(t *testing.T) { 97 asrt := assert.New(t) 98 v := newMultiClusterApplicationConfigurationValidator() 99 mc := v1alpha1.VerrazzanoManagedCluster{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Name: "valid-cluster-name", 102 Namespace: constants.VerrazzanoMultiClusterNamespace, 103 }, 104 Spec: v1alpha1.VerrazzanoManagedClusterSpec{ 105 CASecret: "test-secret", 106 ManagedClusterManifestSecret: "test-cluster-manifest-secret", 107 ServiceAccount: "test-service-account", 108 }, 109 } 110 mcac := v1alpha12.MultiClusterApplicationConfiguration{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: "test-mcapplicationconfiguration-name", 113 Namespace: "application-ns", 114 }, 115 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 116 Placement: v1alpha12.Placement{ 117 Clusters: []v1alpha12.Cluster{{Name: "valid-cluster-name"}}, 118 }, 119 }, 120 } 121 vp := v1alpha12.VerrazzanoProject{ 122 ObjectMeta: metav1.ObjectMeta{ 123 Name: "test-verrazzanoproject-name", 124 Namespace: constants.VerrazzanoMultiClusterNamespace, 125 }, 126 Spec: v1alpha12.VerrazzanoProjectSpec{ 127 Template: v1alpha12.ProjectTemplate{ 128 Namespaces: []v1alpha12.NamespaceTemplate{ 129 { 130 Metadata: metav1.ObjectMeta{ 131 Name: "application-ns", 132 }, 133 }, 134 }, 135 }, 136 }, 137 } 138 139 asrt.NoError(v.client.Create(context.TODO(), &mc)) 140 asrt.NoError(v.client.Create(context.TODO(), &vp)) 141 142 req := newAdmissionRequest(admissionv1.Create, mcac) 143 res := v.Handle(context.TODO(), req) 144 asrt.True(res.Allowed, "Expected multi-cluster application configuration create validation to succeed.") 145 146 req = newAdmissionRequest(admissionv1.Update, mcac) 147 res = v.Handle(context.TODO(), req) 148 asrt.True(res.Allowed, "Expected multi-cluster application configuration update validation to succeed.") 149 } 150 151 // TestValidationSuccessForMultiClusterApplicationConfigurationCreationWithoutTargetClustersOnManagedCluster tests allowing the creation 152 // of a MultiClusterApplicationConfiguration resources that is missing target cluster information when on managed cluster. 153 // GIVEN a call to validate a MultiClusterApplicationConfiguration resource 154 // WHEN the MultiClusterApplicationConfiguration resource is missing Placement information 155 // AND the validation is being done on a managed cluster 156 // THEN the validation should succeed. 157 func TestValidationSuccessForMultiClusterApplicationConfigurationCreationWithoutTargetClustersOnManagedCluster(t *testing.T) { 158 asrt := assert.New(t) 159 v := newMultiClusterApplicationConfigurationValidator() 160 s := corev1.Secret{ 161 ObjectMeta: metav1.ObjectMeta{ 162 Name: constants.MCRegistrationSecret, 163 Namespace: constants.VerrazzanoSystemNamespace, 164 }, 165 } 166 mcac := v1alpha12.MultiClusterApplicationConfiguration{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: "test-mcapplicationconfiguration-name", 169 Namespace: "application-ns", 170 }, 171 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 172 Placement: v1alpha12.Placement{ 173 Clusters: []v1alpha12.Cluster{{Name: "invalid-cluster-name"}}, 174 }, 175 }, 176 } 177 vp := v1alpha12.VerrazzanoProject{ 178 ObjectMeta: metav1.ObjectMeta{ 179 Name: "test-verrazzanoproject-name", 180 Namespace: constants.VerrazzanoMultiClusterNamespace, 181 }, 182 Spec: v1alpha12.VerrazzanoProjectSpec{ 183 Template: v1alpha12.ProjectTemplate{ 184 Namespaces: []v1alpha12.NamespaceTemplate{ 185 { 186 Metadata: metav1.ObjectMeta{ 187 Name: "application-ns", 188 }, 189 }, 190 }, 191 }, 192 }, 193 } 194 195 asrt.NoError(v.client.Create(context.TODO(), &s)) 196 asrt.NoError(v.client.Create(context.TODO(), &vp)) 197 198 req := newAdmissionRequest(admissionv1.Create, mcac) 199 res := v.Handle(context.TODO(), req) 200 asrt.True(res.Allowed, "Expected multi-cluster application configuration validation to succeed with missing placement information on managed cluster.") 201 202 req = newAdmissionRequest(admissionv1.Update, mcac) 203 res = v.Handle(context.TODO(), req) 204 asrt.True(res.Allowed, "Expected multi-cluster application configuration validation to succeed with missing placement information on managed cluster.") 205 } 206 207 // TestValidateSecrets tests the function validateSecrets 208 // GIVEN a call to validateSecrets 209 // WHEN called with various MultiClusterApplicationConfiguration resources 210 // THEN the validation should succeed or fail based on what secrets are specified in the 211 // MultiClusterApplicationConfiguration resource 212 func TestValidateSecrets(t *testing.T) { 213 asrt := assert.New(t) 214 v := newMultiClusterApplicationConfigurationValidator() 215 216 mcac := &v1alpha12.MultiClusterApplicationConfiguration{ 217 ObjectMeta: metav1.ObjectMeta{ 218 Name: "test-mcapplicationconfiguration-name", 219 Namespace: constants.VerrazzanoMultiClusterNamespace, 220 }, 221 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{}, 222 } 223 224 // No secrets specified, so success is expected 225 asrt.NoError(v.validateSecrets(mcac)) 226 227 mcac = &v1alpha12.MultiClusterApplicationConfiguration{ 228 ObjectMeta: metav1.ObjectMeta{ 229 Name: "test-mcapplicationconfiguration-name", 230 Namespace: constants.VerrazzanoMultiClusterNamespace, 231 }, 232 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 233 Secrets: []string{ 234 "secret1", 235 }, 236 }, 237 } 238 239 // Secret not found, so failure expected 240 err := v.validateSecrets(mcac) 241 asrt.EqualError(err, "secret(s) secret1 specified in MultiClusterApplicationConfiguration not found in namespace verrazzano-mc") 242 243 mcac = &v1alpha12.MultiClusterApplicationConfiguration{ 244 ObjectMeta: metav1.ObjectMeta{ 245 Name: "test-mcapplicationconfiguration-name", 246 Namespace: constants.VerrazzanoMultiClusterNamespace, 247 }, 248 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 249 Secrets: []string{ 250 "secret1", 251 "secret2", 252 }, 253 }, 254 } 255 256 // Secrets not found, so failure expected 257 err = v.validateSecrets(mcac) 258 asrt.EqualError(err, "secret(s) secret1,secret2 specified in MultiClusterApplicationConfiguration not found in namespace verrazzano-mc") 259 260 mcac = &v1alpha12.MultiClusterApplicationConfiguration{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: "test-mcapplicationconfiguration-name", 263 Namespace: constants.VerrazzanoMultiClusterNamespace, 264 }, 265 Spec: v1alpha12.MultiClusterApplicationConfigurationSpec{ 266 Secrets: []string{ 267 "secret1", 268 }, 269 }, 270 } 271 secret1 := corev1.Secret{ 272 ObjectMeta: metav1.ObjectMeta{ 273 Name: "secret1", 274 Namespace: constants.VerrazzanoMultiClusterNamespace, 275 }, 276 } 277 asrt.NoError(v.client.Create(context.TODO(), &secret1)) 278 279 // Secret should be found, so success is expected 280 asrt.NoError(v.validateSecrets(mcac)) 281 282 } 283 284 // TestMultiClusterAppConfigHandleFailed tests to make sure the failure metric is being exposed 285 func TestMultiClusterAppConfigHandleFailed(t *testing.T) { 286 assert := assert.New(t) 287 // Create a request and decode(Handle) 288 decoder := decoder() 289 defaulter := &IstioWebhook{} 290 _ = defaulter.InjectDecoder(decoder) 291 req := admission.Request{} 292 defaulter.Handle(context.TODO(), req) 293 reconcileerrorCounterObject, err := metricsexporter.GetSimpleCounterMetric(metricsexporter.MultiClusterAppconfigPodHandleError) 294 assert.NoError(err) 295 // Expect a call to fetch the error 296 reconcileFailedCounterBefore := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 297 reconcileerrorCounterObject.Get().Inc() 298 reconcileFailedCounterAfter := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 299 assert.Equal(reconcileFailedCounterBefore, reconcileFailedCounterAfter-1) 300 }