github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/restart/restart_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 restart_test 20 21 import ( 22 "context" 23 "encoding/json" 24 "errors" 25 "time" 26 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 32 controllermocks "github.com/IBM-Blockchain/fabric-operator/controllers/mocks" 33 k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 34 "github.com/IBM-Blockchain/fabric-operator/pkg/restart" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts" 36 37 appsv1 "k8s.io/api/apps/v1" 38 corev1 "k8s.io/api/core/v1" 39 k8serrors "k8s.io/apimachinery/pkg/api/errors" 40 "k8s.io/apimachinery/pkg/runtime/schema" 41 "k8s.io/apimachinery/pkg/types" 42 ) 43 44 var _ = Describe("Restart", func() { 45 SetDefaultEventuallyTimeout(30 * time.Second) 46 SetDefaultEventuallyPollingInterval(time.Second) 47 48 var ( 49 mockClient *controllermocks.Client 50 instance *current.IBPPeer 51 52 restartManager *restart.RestartManager 53 54 cfg *restart.Config 55 updatedCfg *restart.Config 56 testTimestamp string 57 ) 58 59 BeforeEach(func() { 60 mockClient = &controllermocks.Client{} 61 restartManager = restart.New(mockClient, 10*time.Minute, 5*time.Minute) 62 63 instance = ¤t.IBPPeer{} 64 instance.Name = "peer1" 65 instance.Namespace = "default" 66 67 testTimestamp = time.Now().UTC().Format(time.RFC3339) 68 cfg = &restart.Config{ 69 Instances: map[string]*restart.Restart{ 70 "peer1": { 71 Requests: map[restart.Reason]*restart.Request{ 72 restart.ADMINCERT: { 73 RequestTimestamp: testTimestamp, 74 Status: restart.Pending, 75 }, 76 }, 77 }, 78 "peer2": { 79 Requests: map[restart.Reason]*restart.Request{ 80 restart.ADMINCERT: { 81 LastActionTimestamp: time.Now().Add(-5 * time.Minute).UTC().Format(time.RFC3339), 82 }, 83 }, 84 }, 85 "peer3": { 86 Requests: map[restart.Reason]*restart.Request{ 87 restart.ADMINCERT: { 88 LastActionTimestamp: time.Now().Add(-5 * time.Second).UTC().Format(time.RFC3339), 89 RequestTimestamp: testTimestamp, 90 Status: restart.Pending, 91 }, 92 }, 93 }, 94 "peer4": { 95 Requests: map[restart.Reason]*restart.Request{ 96 restart.ADMINCERT: { 97 LastActionTimestamp: time.Now().Add(-15 * time.Minute).UTC().Format(time.RFC3339), 98 RequestTimestamp: testTimestamp, 99 Status: restart.Pending, 100 }, 101 restart.ECERTUPDATE: { 102 LastActionTimestamp: time.Now().Add(-5 * time.Minute).UTC().Format(time.RFC3339), 103 RequestTimestamp: testTimestamp, 104 Status: restart.Pending, 105 }, 106 }, 107 }, 108 }, 109 } 110 111 cfgBytes, err := json.Marshal(cfg) 112 Expect(err).NotTo(HaveOccurred()) 113 114 mockClient.GetStub = func(ctx context.Context, ns types.NamespacedName, obj client.Object) error { 115 switch obj.(type) { 116 case *corev1.ConfigMap: 117 o := obj.(*corev1.ConfigMap) 118 switch ns.Name { 119 case "operator-config": 120 o.Name = "operator-config" 121 o.Namespace = instance.Namespace 122 o.BinaryData = map[string][]byte{ 123 "restart-config.yaml": cfgBytes, 124 } 125 } 126 case *appsv1.Deployment: 127 o := obj.(*appsv1.Deployment) 128 o.Name = ns.Name 129 o.Namespace = instance.Namespace 130 } 131 132 return nil 133 } 134 135 updatedCfg = &restart.Config{} 136 mockClient.CreateOrUpdateStub = func(ctx context.Context, obj client.Object, opts ...k8sclient.CreateOrUpdateOption) error { 137 o := obj.(*corev1.ConfigMap) 138 err := json.Unmarshal(o.BinaryData["restart-config.yaml"], updatedCfg) 139 Expect(err).NotTo(HaveOccurred()) 140 return nil 141 } 142 }) 143 144 Context("get config from config map", func() { 145 It("returns empty config if config map doesn't exist", func() { 146 mockClient.GetReturns(k8serrors.NewNotFound(schema.GroupResource{}, "not found")) 147 config, err := restartManager.GetConfig(instance) 148 Expect(err).NotTo(HaveOccurred()) 149 Expect(config).To(Equal(&restart.Config{})) 150 }) 151 152 It("returns error if failed to get existing config map", func() { 153 mockClient.GetReturns(errors.New("get error")) 154 _, err := restartManager.GetConfig(instance) 155 Expect(err).To(HaveOccurred()) 156 Expect(err.Error()).Should(Equal("failed to get operator-config config map: get error")) 157 }) 158 159 It("returns error if fails to unmarshal config map", func() { 160 mockClient.GetStub = func(ctx context.Context, ns types.NamespacedName, obj client.Object) error { 161 o := obj.(*corev1.ConfigMap) 162 o.Name = "operator-config" 163 o.Namespace = instance.Namespace 164 o.BinaryData = map[string][]byte{ 165 "restart-config.yaml": []byte("invalid"), 166 } 167 return nil 168 } 169 _, err := restartManager.GetConfig(instance) 170 Expect(err).To(HaveOccurred()) 171 Expect(err.Error()).Should(ContainSubstring("failed to unmarshal operator-config config map")) 172 }) 173 174 It("returns restart config from config map", func() { 175 config, err := restartManager.GetConfig(instance) 176 Expect(err).NotTo(HaveOccurred()) 177 Expect(config).To(Equal(cfg)) 178 }) 179 }) 180 181 Context("update config map", func() { 182 It("returns error if fails to update config map", func() { 183 mockClient.CreateOrUpdateReturns(errors.New("update error")) 184 err := restartManager.UpdateConfigMap(cfg, instance) 185 Expect(err).To(HaveOccurred()) 186 Expect(err.Error()).To(Equal("failed to create or update operator-config config map: update error")) 187 }) 188 189 It("updates config map", func() { 190 err := restartManager.UpdateConfigMap(cfg, instance) 191 Expect(err).NotTo(HaveOccurred()) 192 }) 193 }) 194 195 Context("for admin cert update", func() { 196 It("returns error if fails to get config from config map", func() { 197 mockClient.GetReturns(errors.New("get error")) 198 err := restartManager.ForAdminCertUpdate(instance) 199 Expect(err).To(HaveOccurred()) 200 Expect(err.Error()).To(Equal("failed to get operator-config config map: get error")) 201 }) 202 203 It("returns error if fails to update config map", func() { 204 mockClient.CreateOrUpdateReturns(errors.New("update error")) 205 err := restartManager.ForAdminCertUpdate(instance) 206 Expect(err).To(HaveOccurred()) 207 Expect(err.Error()).To(Equal("failed to create or update operator-config config map: update error")) 208 }) 209 210 It("doesn't set RequestTimestamp if already set", func() { 211 instance.Name = "peer1" 212 err := restartManager.ForAdminCertUpdate(instance) 213 Expect(err).NotTo(HaveOccurred()) 214 Expect(updatedCfg.Instances["peer1"].Requests[restart.ADMINCERT].RequestTimestamp).To(Equal(testTimestamp)) 215 }) 216 217 It("sets RequestTimestamp if not set for that instance", func() { 218 instance.Name = "peer2" 219 err := restartManager.ForAdminCertUpdate(instance) 220 Expect(err).NotTo(HaveOccurred()) 221 Expect(updatedCfg.Instances["peer2"].Requests[restart.ADMINCERT].RequestTimestamp).NotTo(Equal("")) 222 }) 223 224 It("sets RequestTimestamp for instance if instance not yet in config", func() { 225 instance.Name = "newpeer" 226 err := restartManager.ForAdminCertUpdate(instance) 227 Expect(err).NotTo(HaveOccurred()) 228 Expect(updatedCfg.Instances["newpeer"].Requests[restart.ADMINCERT].RequestTimestamp).NotTo(Equal("")) 229 }) 230 231 }) 232 233 Context("for ecert reenroll", func() { 234 It("sets RequestTimestamp for instance if not set for that instance", func() { 235 err := restartManager.ForEcertReenroll(instance) 236 Expect(err).NotTo(HaveOccurred()) 237 Expect(updatedCfg.Instances["peer1"].Requests[restart.ECERTUPDATE].RequestTimestamp).NotTo(Equal("")) 238 }) 239 }) 240 241 Context("for tls reenroll", func() { 242 It("sets RequestTimestamp for instance if not set for that instance", func() { 243 err := restartManager.ForTLSReenroll(instance) 244 Expect(err).NotTo(HaveOccurred()) 245 Expect(updatedCfg.Instances["peer1"].Requests[restart.TLSUPDATE].RequestTimestamp).NotTo(Equal("")) 246 }) 247 }) 248 249 Context("for config override", func() { 250 It("sets RequestTimestamp for instance if not set for that instance", func() { 251 err := restartManager.ForConfigOverride(instance) 252 Expect(err).NotTo(HaveOccurred()) 253 Expect(updatedCfg.Instances["peer1"].Requests[restart.CONFIGOVERRIDE].RequestTimestamp).NotTo(Equal("")) 254 }) 255 }) 256 257 Context("for migration", func() { 258 It("sets RequestTimestamp for instance if not set for that instance", func() { 259 err := restartManager.ForMigration(instance) 260 Expect(err).NotTo(HaveOccurred()) 261 Expect(updatedCfg.Instances["peer1"].Requests[restart.MIGRATION].RequestTimestamp).NotTo(Equal("")) 262 }) 263 }) 264 265 Context("trigger if needed", func() { 266 It("returns error if fails to get config map", func() { 267 mockClient.GetReturns(errors.New("get error")) 268 err := restartManager.TriggerIfNeeded(instance) 269 Expect(err).To(HaveOccurred()) 270 }) 271 272 It("returns nil if instance is not in config map", func() { 273 instance.Name = "fake peer" 274 err := restartManager.TriggerIfNeeded(instance) 275 Expect(err).NotTo(HaveOccurred()) 276 }) 277 278 It("triggers restart if there are pending restarts and no previous restart", func() { 279 instance.Name = "peer1" 280 err := restartManager.TriggerIfNeeded(instance) 281 Expect(err).NotTo(HaveOccurred()) 282 283 By("clearing restart", func() { 284 for _, req := range updatedCfg.Instances["peer1"].Requests { 285 Expect(req.Status).To(Equal(restart.Complete)) 286 Expect(req.RequestTimestamp).To(Equal("")) 287 Expect(req.LastActionTimestamp).NotTo(Equal("")) 288 } 289 }) 290 291 By("adding restart request to queue", func() { 292 _, cm, _ := mockClient.CreateOrUpdateArgsForCall(1) 293 cfgBytes := cm.(*corev1.ConfigMap).BinaryData["restart-config.yaml"] 294 restartcfg := &staggerrestarts.RestartConfig{} 295 err = json.Unmarshal(cfgBytes, restartcfg) 296 Expect(err).NotTo(HaveOccurred()) 297 298 Expect(len(restartcfg.Queues[instance.GetMSPID()])).To(Equal(1)) 299 Expect(restartcfg.Queues[instance.GetMSPID()][0].CRName).To(Equal(instance.Name)) 300 Expect(restartcfg.Queues[instance.GetMSPID()][0].Reason).To(Equal("adminCert")) 301 Expect(restartcfg.Queues[instance.GetMSPID()][0].Status).To(Equal(staggerrestarts.Pending)) 302 }) 303 }) 304 305 It("returns nil if there are no pending restarts for instance", func() { 306 instance.Name = "peer2" 307 err := restartManager.TriggerIfNeeded(instance) 308 Expect(err).NotTo(HaveOccurred()) 309 }) 310 311 It("sets timer if there are pending restarts but last restart action timestamp is sooner than 10 min", func() { 312 instance.Name = "peer3" 313 err := restartManager.TriggerIfNeeded(instance) 314 Expect(err).NotTo(HaveOccurred()) 315 316 By("not updating config map", func() { 317 Expect(mockClient.CreateOrUpdateCallCount()).To(Equal(0)) 318 }) 319 320 By("setting timer", func() { 321 // timer.Stop() == true means that it was set 322 Expect(restartManager.Timers["peer3"].Stop()).To(Equal(true)) 323 }) 324 }) 325 326 It("triggers restart if there are pending restarts and at least one request last action timestamp is more than 10 min ago", func() { 327 instance.Name = "peer4" 328 err := restartManager.TriggerIfNeeded(instance) 329 Expect(err).NotTo(HaveOccurred()) 330 331 By("clearing restart", func() { 332 for reason, req := range updatedCfg.Instances["peer4"].Requests { 333 Expect(req.Status).To(Equal(restart.Complete)) 334 Expect(req.RequestTimestamp).To(Equal("")) 335 Expect(req.LastActionTimestamp).NotTo(Equal(cfg.Instances["peer4"].Requests[reason].LastActionTimestamp)) 336 } 337 }) 338 339 By("adding restart request to queue", func() { 340 _, cm, _ := mockClient.CreateOrUpdateArgsForCall(1) 341 cfgBytes := cm.(*corev1.ConfigMap).BinaryData["restart-config.yaml"] 342 restartcfg := &staggerrestarts.RestartConfig{} 343 err = json.Unmarshal(cfgBytes, restartcfg) 344 Expect(err).NotTo(HaveOccurred()) 345 346 Expect(len(restartcfg.Queues[instance.GetMSPID()])).To(Equal(1)) 347 Expect(restartcfg.Queues[instance.GetMSPID()][0].CRName).To(Equal(instance.Name)) 348 Expect(restartcfg.Queues[instance.GetMSPID()][0].Reason).To(ContainSubstring("adminCert")) 349 Expect(restartcfg.Queues[instance.GetMSPID()][0].Reason).To(ContainSubstring("ecertUpdate")) 350 Expect(restartcfg.Queues[instance.GetMSPID()][0].Status).To(Equal(staggerrestarts.Pending)) 351 }) 352 }) 353 }) 354 355 Context("set timer", func() { 356 BeforeEach(func() { 357 restartManager.WaitTime = 10 * time.Second 358 }) 359 360 It("returns error if fails to get config map", func() { 361 mockClient.GetReturns(errors.New("get error")) 362 err := restartManager.SetTimer(instance, "") 363 Expect(err).To(HaveOccurred()) 364 }) 365 366 It("sets timer for instance if there are pending restarts", func() { 367 instance.Name = "peer3" 368 err := restartManager.SetTimer(instance, "") 369 Expect(err).NotTo(HaveOccurred()) 370 371 // Timer should go off in 5 seconds 372 time.Sleep(10 * time.Second) 373 374 By("restarting deployment after timer goes off", func() { 375 Expect(restartManager.Timers["peer3"]).To(BeNil()) 376 }) 377 }) 378 }) 379 380 })