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 = &current.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 = &current.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 = &current.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 = &current.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 = &current.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  }