github.com/IBM-Blockchain/fabric-operator@v1.0.4/integration/restartmgr/restartmgr_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 restartmgr_test 20 21 import ( 22 "context" 23 "encoding/base64" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "path/filepath" 28 "time" 29 30 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 31 "github.com/IBM-Blockchain/fabric-operator/integration/helper" 32 v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1" 33 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 34 "github.com/IBM-Blockchain/fabric-operator/pkg/restart" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts" 36 . "github.com/onsi/ginkgo/v2" 37 . "github.com/onsi/gomega" 38 39 corev1 "k8s.io/api/core/v1" 40 k8serrors "k8s.io/apimachinery/pkg/api/errors" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/runtime" 43 "k8s.io/apimachinery/pkg/types" 44 ) 45 46 var _ = Describe("restart manager", func() { 47 AfterEach(func() { 48 // Set flag if a test falls 49 if CurrentGinkgoTestDescription().Failed { 50 testFailed = true 51 } 52 }) 53 54 Context("peer", func() { 55 Context("admin certs", func() { 56 var ( 57 podName string 58 peer *current.IBPPeer 59 tlsbackup *common.Backup 60 ecertbackup *common.Backup 61 ) 62 63 BeforeEach(func() { 64 Eventually(func() int { return len(org1peer.GetRunningPods()) }).Should(Equal(1)) 65 66 podName = org1peer.GetRunningPods()[0].Name 67 68 // Get peer's custom resource (CR) 69 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPPEERS).Name(org1peer.Name).Do(context.TODO()) 70 Expect(result.Error()).NotTo(HaveOccurred()) 71 72 peer = ¤t.IBPPeer{} 73 result.Into(peer) 74 75 tlsbackup = GetBackup("tls", org1peer.Name) 76 ecertbackup = GetBackup("ecert", org1peer.Name) 77 }) 78 79 It("restarts the peer after admin cert update", func() { 80 // Update the admin cert in the peer's CR spec 81 adminCertBytes, err := ioutil.ReadFile(filepath.Join(wd, "org1peer", peerAdminUsername+"2", "msp", "signcerts", "cert.pem")) 82 Expect(err).NotTo(HaveOccurred()) 83 adminCertB64 := base64.StdEncoding.EncodeToString(adminCertBytes) 84 peer.Spec.Secret.Enrollment.Component.AdminCerts = []string{peer.Spec.Secret.Enrollment.Component.AdminCerts[0], adminCertB64} 85 86 bytes, err := json.Marshal(peer) 87 Expect(err).NotTo(HaveOccurred()) 88 89 // Update the peer's CR spec 90 result := ibpCRClient.Put().Namespace(namespace).Resource(IBPPEERS).Name(org1peer.Name).Body(bytes).Do(context.TODO()) 91 Expect(result.Error()).NotTo(HaveOccurred()) 92 93 Eventually(org1peer.PodIsRunning).Should((Equal(true))) 94 95 By("restarting peer pods", func() { 96 Eventually(func() bool { 97 pods := org1peer.GetRunningPods() 98 if len(pods) != 1 { 99 return false 100 } 101 102 newPodName := pods[0].Name 103 if newPodName == podName { 104 return false 105 } 106 107 return true 108 }).Should(Equal(true)) 109 }) 110 111 By("not performing backup of crypto beforehand", func() { 112 newTLSBackup := GetBackup("tls", org1peer.Name) 113 newEcertBackup := GetBackup("ecert", org1peer.Name) 114 Expect(newTLSBackup).To(Equal(tlsbackup)) 115 Expect(newEcertBackup).To(Equal(ecertbackup)) 116 }) 117 118 By("removing instance from restart queue", func() { 119 Eventually(func() bool { 120 restartConfig := GetRestartConfigFor("peer") 121 if len(restartConfig.Queues[org1peer.CR.GetMSPID()]) != 0 { 122 return false 123 } 124 if restartConfig.Log[org1peer.Name] == nil { 125 return false 126 } 127 if len(restartConfig.Log[org1peer.Name]) != 1 { 128 return false 129 } 130 if restartConfig.Log[org1peer.Name][0].CRName != org1peer.Name { 131 return false 132 } 133 134 return true 135 }).Should(Equal(true)) 136 }) 137 }) 138 139 It("does not restart the peer if spec is updated with empty list of admin certs", func() { 140 // Update the admin cert in the peer's CR spec to be empty 141 peer.Spec.Secret.Enrollment.Component.AdminCerts = []string{} 142 bytes, err := json.Marshal(peer) 143 Expect(err).NotTo(HaveOccurred()) 144 145 // Update the peer's CR spec 146 result := ibpCRClient.Put().Namespace(namespace).Resource(IBPPEERS).Name(org1peer.Name).Body(bytes).Do(context.TODO()) 147 Expect(result.Error()).NotTo(HaveOccurred()) 148 149 Eventually(org1peer.PodIsRunning).Should((Equal(true))) 150 151 Eventually(func() bool { 152 pods := org1peer.GetRunningPods() 153 if len(pods) != 1 { 154 return false 155 } 156 157 newPodName := pods[0].Name 158 if newPodName == podName { 159 return true 160 } 161 162 return false 163 }).Should(Equal(true)) 164 165 }) 166 }) 167 168 Context("request deployment restart", func() { 169 var ( 170 podName string 171 peer *current.IBPPeer 172 restartTime string 173 ) 174 175 BeforeEach(func() { 176 Eventually(func() int { return len(org1peer.GetPods()) }).Should(Equal(1)) 177 178 podName = org1peer.GetRunningPods()[0].Name 179 180 // Get peer's custom resource (CR) 181 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPPEERS).Name(org1peer.Name).Do(context.TODO()) 182 Expect(result.Error()).NotTo(HaveOccurred()) 183 184 peer = ¤t.IBPPeer{} 185 result.Into(peer) 186 187 }) 188 189 When("peer was restarted less than 10 min ago for admin cert updates", func() { 190 BeforeEach(func() { 191 // Create operator-config map to indicate that peer was restarted recently for admin cert update 192 restartTime = time.Now().UTC().Format(time.RFC3339) 193 CreateOrUpdateOperatorConfig(peer.Name, restart.ADMINCERT, restartTime) 194 195 Eventually(func() bool { 196 _, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 197 if err != nil { 198 return false 199 } 200 return true 201 }).Should(Equal(true)) 202 }) 203 204 It("does not restart the peer when admin certs are updated", func() { 205 By("updating peer's admin certs", func() { 206 adminCertBytes, err := ioutil.ReadFile(filepath.Join(wd, "org1peer", peerAdminUsername+"2", "msp", "signcerts", "cert.pem")) 207 Expect(err).NotTo(HaveOccurred()) 208 adminCertB64 := base64.StdEncoding.EncodeToString(adminCertBytes) 209 peer.Spec.Secret.Enrollment.Component.AdminCerts = []string{adminCertB64} 210 211 bytes, err := json.Marshal(peer) 212 Expect(err).NotTo(HaveOccurred()) 213 214 // Update the peer's CR spec 215 result := ibpCRClient.Put().Namespace(namespace).Resource(IBPPEERS).Name(org1peer.Name).Body(bytes).Do(context.TODO()) 216 Expect(result.Error()).NotTo(HaveOccurred()) 217 218 Eventually(org1peer.PodIsRunning).Should((Equal(true))) 219 }) 220 221 By("not restarting peer pods again", func() { 222 Consistently(func() bool { 223 pods := org1peer.GetRunningPods() 224 if len(pods) != 1 { 225 return false 226 } 227 228 newPodName := pods[0].Name 229 if newPodName == podName { 230 return true 231 } 232 233 return false 234 }, 5*time.Second).Should(Equal(true)) 235 }) 236 237 // TODO: This test is failing, there seems to be a couple seconds difference between actual and expected time values. Needs investigation. 238 By("adding a pending restart request to config map", func() { 239 Skip("Skipping test, needs revision as it currently fails") 240 cm, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 241 Expect(err).NotTo(HaveOccurred()) 242 243 cfg := &restart.Config{} 244 err = json.Unmarshal(cm.BinaryData["restart-config.yaml"], cfg) 245 Expect(err).NotTo(HaveOccurred()) 246 247 Expect(cfg.Instances[peer.Name].Requests[restart.ADMINCERT].LastActionTimestamp).To(Equal(restartTime)) 248 Expect(cfg.Instances[peer.Name].Requests[restart.ADMINCERT].Status).To(Equal(restart.Pending)) 249 }) 250 }) 251 }) 252 }) 253 }) 254 255 Context("orderer - request deployment restart", func() { 256 var ( 257 node1 helper.Orderer 258 259 podName string 260 ibporderer *current.IBPOrderer 261 restartTime string 262 ) 263 264 BeforeEach(func() { 265 ClearOperatorConfig() 266 267 node1 = orderer.Nodes[0] 268 Eventually(node1.PodIsRunning, time.Second*60, time.Second*2).Should((Equal(true))) 269 Eventually(func() int { return len(node1.GetPods()) }).Should(Equal(1)) 270 271 podName = node1.GetPods()[0].Name 272 result := ibpCRClient.Get().Namespace(namespace). 273 Resource(IBPORDERERS). 274 Name(node1.Name). 275 Do(context.TODO()) 276 Expect(result.Error()).NotTo(HaveOccurred()) 277 278 ibporderer = ¤t.IBPOrderer{} 279 result.Into(ibporderer) 280 }) 281 282 When("reenroll is triggered", func() { 283 It("restarts", func() { 284 ibporderer.Spec.Action.Reenroll.Ecert = true 285 ordererbytes, err := json.Marshal(ibporderer) 286 Expect(err).NotTo(HaveOccurred()) 287 288 result := ibpCRClient.Patch(types.MergePatchType).Namespace(namespace).Resource(IBPORDERERS).Name(node1.Name).Body(ordererbytes).Do(context.TODO()) 289 Expect(result.Error()).NotTo(HaveOccurred()) 290 291 Eventually(func() bool { 292 restartConfig := GetRestartConfigFor("orderer") 293 if restartConfig == nil { 294 return false 295 } 296 if len(restartConfig.Queues["orderermsp"]) != 0 { 297 return false 298 } 299 if restartConfig.Log["ibporderer1node1"] == nil || len(restartConfig.Log["ibporderer1node1"]) != 1 { 300 return false 301 } 302 return true 303 }).Should(Equal(true)) 304 305 }) 306 }) 307 308 When("orderer was restarted less than 10 min ago for ecert reenroll", func() { 309 BeforeEach(func() { 310 // Create operator-config map to indicate that peer was restarted recently for ecert reenroll 311 restartTime = time.Now().UTC().Format(time.RFC3339) 312 CreateOrUpdateOperatorConfig(ibporderer.Name, restart.ECERTUPDATE, restartTime) 313 314 Eventually(func() bool { 315 _, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 316 if err != nil { 317 return false 318 } 319 return true 320 }).Should(Equal(true)) 321 }) 322 323 It("does not restart orderer when ecerts reenroll occurs", func() { 324 By("triggering ecert reenroll", func() { 325 ibporderer.Spec.Action.Reenroll.Ecert = true 326 ordererbytes, err := json.Marshal(ibporderer) 327 Expect(err).NotTo(HaveOccurred()) 328 329 result := ibpCRClient.Put().Namespace(namespace).Resource(IBPORDERERS).Name(node1.Name).Body(ordererbytes).Do(context.TODO()) 330 Expect(result.Error()).NotTo(HaveOccurred()) 331 332 Eventually(node1.PodIsRunning).Should(Equal(true)) 333 }) 334 335 By("not restarting orderer pods again", func() { 336 Eventually(func() bool { 337 pods := node1.GetRunningPods() 338 if len(pods) != 1 { 339 return false 340 } 341 342 newPodName := pods[0].Name 343 if newPodName == podName { 344 return true 345 } 346 347 return false 348 }).Should(Equal(true)) 349 }) 350 351 By("adding a pending restart request to config map", func() { 352 Skip("Skipping test, needs revision as it currently fails") 353 354 Eventually(func() bool { 355 cm, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 356 Expect(err).NotTo(HaveOccurred()) 357 358 cfg := &restart.Config{} 359 err = json.Unmarshal(cm.BinaryData["restart-config.yaml"], cfg) 360 Expect(err).NotTo(HaveOccurred()) 361 362 status := cfg.Instances[ibporderer.Name].Requests[restart.ECERTUPDATE].Status 363 lastTimestamp := cfg.Instances[ibporderer.Name].Requests[restart.ECERTUPDATE].LastActionTimestamp 364 if status == restart.Pending && lastTimestamp == restartTime { 365 return true 366 } 367 368 return false 369 }).Should(Equal(true)) 370 }) 371 }) 372 }) 373 }) 374 375 Context("CA - request deployment restart", func() { 376 var ( 377 podName string 378 ca *current.IBPCA 379 restartTime string 380 ) 381 382 BeforeEach(func() { 383 Eventually(func() int { 384 return len(org1ca.GetPods()) 385 }).Should(Equal(1)) 386 387 podName = org1ca.GetPods()[0].Name 388 389 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Do(context.TODO()) 390 Expect(result.Error()).NotTo(HaveOccurred()) 391 392 ca = ¤t.IBPCA{} 393 result.Into(ca) 394 }) 395 396 Context("staggering ca restarts", func() { 397 var ( 398 bytes []byte 399 err error 400 ) 401 402 BeforeEach(func() { 403 ca.Spec.Action.Renew.TLSCert = true 404 405 bytes, err = json.Marshal(ca) 406 Expect(err).NotTo(HaveOccurred()) 407 }) 408 409 It("restarts nodes one at a time in same org", func() { 410 result := ibpCRClient.Patch(types.MergePatchType).Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Body(bytes).Do(context.TODO()) 411 Expect(result.Error()).NotTo(HaveOccurred()) 412 413 Eventually(func() bool { 414 restartConfig := GetRestartConfigFor("ca") 415 if restartConfig == nil { 416 return false 417 } 418 if len(restartConfig.Queues[""]) != 0 { 419 return false 420 } 421 if restartConfig.Log["org1ca"] == nil || len(restartConfig.Log["org1ca"]) != 1 { 422 return false 423 } 424 425 return true 426 }).Should(Equal(true)) 427 }) 428 }) 429 430 When("ca was restarted less than 10 min ago for config override", func() { 431 BeforeEach(func() { 432 // Create operator-config map to indicate that peer was restarted recently for ecert reenroll 433 restartTime = time.Now().UTC().Format(time.RFC3339) 434 CreateOrUpdateOperatorConfig(ca.Name, restart.CONFIGOVERRIDE, restartTime) 435 436 Eventually(func() bool { 437 _, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 438 if err != nil { 439 return false 440 } 441 return true 442 }).Should(Equal(true)) 443 }) 444 445 It("does not restart ca when config override occurs", func() { 446 override := &v1.ServerConfig{ 447 CAConfig: v1.CAConfig{ 448 Version: "1.4.8", 449 }, 450 } 451 overrideBytes, err := json.Marshal(override) 452 Expect(err).NotTo(HaveOccurred()) 453 ca.Spec.ConfigOverride = ¤t.ConfigOverride{ 454 CA: &runtime.RawExtension{Raw: overrideBytes}, 455 } 456 457 bytes, err := json.Marshal(ca) 458 Expect(err).NotTo(HaveOccurred()) 459 460 result := ibpCRClient.Patch(types.MergePatchType).Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Body(bytes).Do(context.TODO()) 461 Expect(result.Error()).NotTo(HaveOccurred()) 462 463 Eventually(org1ca.PodIsRunning).Should((Equal(true))) 464 465 By("not restarting ca pod", func() { 466 Eventually(func() bool { 467 pods := org1ca.GetPods() 468 if len(pods) != 1 { 469 return false 470 } 471 472 newPodName := pods[0].Name 473 if newPodName == podName { 474 return true 475 } 476 477 return false 478 }).Should(Equal(true)) 479 }) 480 481 By("adding a pending restart request to config map", func() { 482 Eventually(func() bool { 483 cm, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 484 Expect(err).NotTo(HaveOccurred()) 485 486 cfg := &restart.Config{} 487 err = json.Unmarshal(cm.BinaryData["restart-config.yaml"], cfg) 488 Expect(err).NotTo(HaveOccurred()) 489 490 status := cfg.Instances[ca.Name].Requests[restart.CONFIGOVERRIDE].Status 491 lastTimestamp := cfg.Instances[ca.Name].Requests[restart.CONFIGOVERRIDE].LastActionTimestamp 492 if status == restart.Pending && lastTimestamp == restartTime { 493 return true 494 } 495 496 return false 497 }).Should(Equal(true)) 498 }) 499 }) 500 }) 501 }) 502 }) 503 504 func CreateOrUpdateOperatorConfig(instance string, reason restart.Reason, lastRestart string) { 505 oldCM := GetOperatorConfigMap(instance, reason, lastRestart) 506 507 cm, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), "operator-config", metav1.GetOptions{}) 508 if k8serrors.IsNotFound(err) { 509 _, err = kclient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), oldCM, metav1.CreateOptions{}) 510 Expect(err).NotTo(HaveOccurred()) 511 } else { 512 513 cm.BinaryData = oldCM.BinaryData 514 _, err = kclient.CoreV1().ConfigMaps(namespace).Update(context.TODO(), cm, metav1.UpdateOptions{}) 515 Expect(err).NotTo(HaveOccurred()) 516 } 517 } 518 519 func GetOperatorConfigMap(instance string, reason restart.Reason, lastRestart string) *corev1.ConfigMap { 520 cfg := &restart.Config{ 521 Instances: map[string]*restart.Restart{ 522 instance: { 523 Requests: map[restart.Reason]*restart.Request{ 524 reason: { 525 LastActionTimestamp: lastRestart, 526 }, 527 }, 528 }, 529 }, 530 } 531 bytes, err := json.Marshal(cfg) 532 Expect(err).NotTo(HaveOccurred()) 533 534 return &corev1.ConfigMap{ 535 ObjectMeta: metav1.ObjectMeta{ 536 Name: "operator-config", 537 Namespace: namespace, 538 }, 539 BinaryData: map[string][]byte{ 540 "restart-config.yaml": bytes, 541 }, 542 } 543 } 544 545 func ClearOperatorConfig() { 546 err := kclient.CoreV1().ConfigMaps(namespace).Delete(context.TODO(), "operator-config", *metav1.NewDeleteOptions(0)) 547 if !k8serrors.IsNotFound(err) { 548 Expect(err).NotTo(HaveOccurred()) 549 } 550 } 551 552 func GetBackup(certType, name string) *common.Backup { 553 backupSecret, err := kclient.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf("%s-crypto-backup", name), metav1.GetOptions{}) 554 if err != nil { 555 Expect(k8serrors.IsNotFound(err)).To(Equal(true)) 556 return &common.Backup{} 557 } 558 559 backup := &common.Backup{} 560 key := fmt.Sprintf("%s-backup.json", certType) 561 err = json.Unmarshal(backupSecret.Data[key], backup) 562 Expect(err).NotTo(HaveOccurred()) 563 564 return backup 565 } 566 567 func GetRestartConfigFor(componentType string) *staggerrestarts.RestartConfig { 568 cmName := componentType + "-restart-config" 569 cm, err := kclient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), cmName, metav1.GetOptions{}) 570 if err != nil { 571 return nil 572 } 573 574 restartConfig := &staggerrestarts.RestartConfig{} 575 err = json.Unmarshal(cm.BinaryData["restart-config.yaml"], restartConfig) 576 Expect(err).NotTo(HaveOccurred()) 577 578 return restartConfig 579 }