github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibpca/ibpca_controller_test.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package ibpca 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "sync" 26 27 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 28 v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1" 29 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 30 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 31 32 . "github.com/onsi/ginkgo/v2" 33 . "github.com/onsi/gomega" 34 35 camocks "github.com/IBM-Blockchain/fabric-operator/controllers/ibpca/mocks" 36 "github.com/IBM-Blockchain/fabric-operator/controllers/mocks" 37 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 38 corev1 "k8s.io/api/core/v1" 39 k8serror "k8s.io/apimachinery/pkg/api/errors" 40 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 41 "k8s.io/apimachinery/pkg/runtime" 42 "k8s.io/apimachinery/pkg/types" 43 "sigs.k8s.io/controller-runtime/pkg/client" 44 "sigs.k8s.io/controller-runtime/pkg/event" 45 "sigs.k8s.io/controller-runtime/pkg/reconcile" 46 ) 47 48 var _ = Describe("ReconcileIBPCA", func() { 49 var ( 50 reconciler *ReconcileIBPCA 51 request reconcile.Request 52 mockKubeClient *mocks.Client 53 mockCAReconcile *camocks.CAReconcile 54 instance *current.IBPCA 55 ) 56 57 BeforeEach(func() { 58 mockKubeClient = &mocks.Client{} 59 mockCAReconcile = &camocks.CAReconcile{} 60 instance = ¤t.IBPCA{ 61 Spec: current.IBPCASpec{}, 62 } 63 instance.Name = "test-ca" 64 65 mockKubeClient.GetStub = func(ctx context.Context, types types.NamespacedName, obj client.Object) error { 66 switch obj.(type) { 67 case *current.IBPCA: 68 o := obj.(*current.IBPCA) 69 o.Kind = "IBPCA" 70 o.Name = instance.Name 71 72 instance.Status = o.Status 73 } 74 return nil 75 } 76 77 mockKubeClient.UpdateStatusStub = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { 78 switch obj.(type) { 79 case *current.IBPCA: 80 o := obj.(*current.IBPCA) 81 instance.Status = o.Status 82 } 83 return nil 84 } 85 86 mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error { 87 switch obj.(type) { 88 case *corev1.NodeList: 89 nodeList := obj.(*corev1.NodeList) 90 node := corev1.Node{} 91 node.Labels = map[string]string{} 92 node.Labels["topology.kubernetes.io/zone"] = "dal" 93 node.Labels["topology.kubernetes.io/region"] = "us-south" 94 nodeList.Items = append(nodeList.Items, node) 95 case *current.IBPCAList: 96 caList := obj.(*current.IBPCAList) 97 ca1 := current.IBPCA{} 98 ca1.Name = "test-ca1" 99 ca2 := current.IBPCA{} 100 ca2.Name = "test-ca2" 101 ca3 := current.IBPCA{} 102 ca3.Name = "test-ca2" 103 caList.Items = []current.IBPCA{ca1, ca2, ca3} 104 case *current.IBPPeerList: 105 caList := obj.(*current.IBPPeerList) 106 p1 := current.IBPPeer{} 107 p1.Name = "test-peer" 108 caList.Items = []current.IBPPeer{p1} 109 } 110 return nil 111 } 112 113 reconciler = &ReconcileIBPCA{ 114 Offering: mockCAReconcile, 115 client: mockKubeClient, 116 scheme: &runtime.Scheme{}, 117 update: map[string][]Update{}, 118 mutex: &sync.Mutex{}, 119 } 120 request = reconcile.Request{ 121 NamespacedName: types.NamespacedName{ 122 Namespace: "test-namespace", 123 Name: "test", 124 }, 125 } 126 }) 127 128 Context("Reconciles", func() { 129 It("does not return an error if the custom resource is 'not found'", func() { 130 notFoundErr := &k8serror.StatusError{ 131 ErrStatus: metav1.Status{ 132 Reason: metav1.StatusReasonNotFound, 133 }, 134 } 135 mockKubeClient.GetReturns(notFoundErr) 136 _, err := reconciler.Reconcile(context.TODO(), request) 137 Expect(err).NotTo(HaveOccurred()) 138 }) 139 140 It("returns an error if the request to get custom resource return any other error besides 'not found'", func() { 141 alreadyExistsErr := &k8serror.StatusError{ 142 ErrStatus: metav1.Status{ 143 Message: "already exists", 144 Reason: metav1.StatusReasonAlreadyExists, 145 }, 146 } 147 mockKubeClient.GetReturns(alreadyExistsErr) 148 _, err := reconciler.Reconcile(context.TODO(), request) 149 Expect(err).To(HaveOccurred()) 150 Expect(err.Error()).To(Equal("already exists")) 151 }) 152 153 It("returns an error if it encountered a non-breaking error", func() { 154 errMsg := "failed to reconcile deployment encountered breaking error" 155 mockCAReconcile.ReconcileReturns(common.Result{}, errors.New(errMsg)) 156 _, err := reconciler.Reconcile(context.TODO(), request) 157 Expect(err).To(HaveOccurred()) 158 Expect(err.Error()).To(Equal(fmt.Sprintf("CA instance '%s' encountered error: %s", instance.Name, errMsg))) 159 }) 160 161 It("does not return an error if it encountered a breaking error", func() { 162 mockCAReconcile.ReconcileReturns(common.Result{}, operatorerrors.New(operatorerrors.InvalidDeploymentCreateRequest, "failed to reconcile deployment encountered breaking error")) 163 _, err := reconciler.Reconcile(context.TODO(), request) 164 Expect(err).NotTo(HaveOccurred()) 165 }) 166 }) 167 168 Context("update reconcile", func() { 169 var ( 170 oldCA *current.IBPCA 171 newCA *current.IBPCA 172 e event.UpdateEvent 173 ) 174 175 BeforeEach(func() { 176 caConfig := &v1.ServerConfig{ 177 CAConfig: v1.CAConfig{ 178 CA: v1.CAInfo{ 179 Name: "old-ca-name", 180 }, 181 }, 182 } 183 caJson, err := util.ConvertToJsonMessage(caConfig) 184 Expect(err).NotTo(HaveOccurred()) 185 186 oldCA = ¤t.IBPCA{ 187 ObjectMeta: metav1.ObjectMeta{ 188 Name: instance.Name, 189 }, 190 Spec: current.IBPCASpec{ 191 ConfigOverride: ¤t.ConfigOverride{ 192 CA: &runtime.RawExtension{Raw: *caJson}, 193 }, 194 }, 195 } 196 197 newcaConfig := &v1.ServerConfig{ 198 CAConfig: v1.CAConfig{ 199 CA: v1.CAInfo{ 200 Name: "new-ca-name", 201 }, 202 }, 203 } 204 newcaJson, err := util.ConvertToJsonMessage(newcaConfig) 205 Expect(err).NotTo(HaveOccurred()) 206 207 newCA = ¤t.IBPCA{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Name: instance.Name, 210 }, 211 Spec: current.IBPCASpec{ 212 ConfigOverride: ¤t.ConfigOverride{ 213 CA: &runtime.RawExtension{Raw: *newcaJson}, 214 }, 215 }, 216 } 217 218 e = event.UpdateEvent{ 219 ObjectOld: oldCA, 220 ObjectNew: newCA, 221 } 222 223 Expect(reconciler.UpdateFunc(e)).To(Equal(true)) 224 225 oldCA = ¤t.IBPCA{ 226 ObjectMeta: metav1.ObjectMeta{ 227 Name: instance.Name, 228 }, 229 Spec: current.IBPCASpec{ 230 ImagePullSecrets: []string{"old-secret"}, 231 }, 232 } 233 234 newCA = ¤t.IBPCA{ 235 ObjectMeta: metav1.ObjectMeta{ 236 Name: instance.Name, 237 }, 238 Spec: current.IBPCASpec{ 239 ImagePullSecrets: []string{"new-secret"}, 240 }, 241 } 242 243 e = event.UpdateEvent{ 244 ObjectOld: oldCA, 245 ObjectNew: newCA, 246 } 247 248 Expect(reconciler.UpdateFunc(e)).To(Equal(true)) 249 }) 250 251 It("properly pops update flags from stack", func() { 252 Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(true)) 253 254 _, err := reconciler.Reconcile(context.TODO(), request) 255 Expect(err).NotTo(HaveOccurred()) 256 257 Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(false)) 258 Expect(reconciler.GetUpdateStatus(instance).SpecUpdated()).To(Equal(true)) 259 260 _, err = reconciler.Reconcile(context.TODO(), request) 261 Expect(err).NotTo(HaveOccurred()) 262 263 Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(false)) 264 Expect(reconciler.GetUpdateStatus(instance).TLSCAOverridesUpdated()).To(Equal(false)) 265 Expect(reconciler.GetUpdateStatus(instance).SpecUpdated()).To(Equal(false)) 266 }) 267 }) 268 269 Context("set status", func() { 270 It("sets the status to error if error occured during IBPCA reconciliation", func() { 271 reconciler.SetStatus(instance, nil, errors.New("ibpca error")) 272 Expect(instance.Status.Type).To(Equal(current.Error)) 273 Expect(instance.Status.Message).To(Equal("ibpca error")) 274 }) 275 276 It("sets the status to deploying if pod is not yet running", func() { 277 mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error { 278 podList := obj.(*corev1.PodList) 279 pod := corev1.Pod{} 280 podList.Items = append(podList.Items, pod) 281 return nil 282 } 283 reconciler.SetStatus(instance, nil, nil) 284 Expect(instance.Status.Type).To(Equal(current.Deploying)) 285 }) 286 287 It("sets the status to deployed if pod is running", func() { 288 mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error { 289 podList := obj.(*corev1.PodList) 290 pod := corev1.Pod{ 291 Status: corev1.PodStatus{ 292 Phase: corev1.PodRunning, 293 }, 294 } 295 podList.Items = append(podList.Items, pod) 296 return nil 297 } 298 299 reconciler.SetStatus(instance, nil, nil) 300 Expect(instance.Status.Type).To(Equal(current.Deployed)) 301 }) 302 }) 303 304 Context("add owner reference to secret", func() { 305 var ( 306 secret *corev1.Secret 307 ) 308 309 BeforeEach(func() { 310 secret = &corev1.Secret{} 311 secret.Name = "test-ca1-ca-crypto" 312 }) 313 314 It("returns error if fails to get list of CAs", func() { 315 mockKubeClient.ListReturns(errors.New("list error")) 316 _, err := reconciler.AddOwnerReferenceToSecret(secret) 317 Expect(err).To(HaveOccurred()) 318 Expect(err.Error()).To(ContainSubstring("list error")) 319 }) 320 321 It("returns false if secret doesn't belong to any CAs in list", func() { 322 secret.Name = "invalidca-ca-crypto" 323 added, err := reconciler.AddOwnerReferenceToSecret(secret) 324 Expect(err).NotTo(HaveOccurred()) 325 Expect(added).To(Equal(false)) 326 }) 327 328 It("returns true if owner references added to ca crypto secret", func() { 329 added, err := reconciler.AddOwnerReferenceToSecret(secret) 330 Expect(err).NotTo(HaveOccurred()) 331 Expect(added).To(Equal(true)) 332 }) 333 334 It("returns true if owner references added to tlsca crypto secret", func() { 335 secret.Name = "test-ca2-tlsca-crypto" 336 added, err := reconciler.AddOwnerReferenceToSecret(secret) 337 Expect(err).NotTo(HaveOccurred()) 338 Expect(added).To(Equal(true)) 339 }) 340 341 It("returns true if owner references added to ca secret", func() { 342 secret.Name = "test-ca2-ca" 343 added, err := reconciler.AddOwnerReferenceToSecret(secret) 344 Expect(err).NotTo(HaveOccurred()) 345 Expect(added).To(Equal(true)) 346 }) 347 348 It("returns true if owner references added to tlsca secret", func() { 349 secret.Name = "test-ca2-tlsca" 350 added, err := reconciler.AddOwnerReferenceToSecret(secret) 351 Expect(err).NotTo(HaveOccurred()) 352 Expect(added).To(Equal(true)) 353 }) 354 }) 355 })