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