github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibppeer/ibppeer_controller.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 ibppeer
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"reflect"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    31  	commoncontroller "github.com/IBM-Blockchain/fabric-operator/controllers/common"
    32  	config "github.com/IBM-Blockchain/fabric-operator/operatorconfig"
    33  	"github.com/IBM-Blockchain/fabric-operator/pkg/global"
    34  	controllerclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/offering"
    36  	basepeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/peer"
    37  	"github.com/IBM-Blockchain/fabric-operator/pkg/offering/common"
    38  	k8speer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/k8s/peer"
    39  	openshiftpeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/openshift/peer"
    40  	"github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors"
    41  	"github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts"
    42  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    43  	"github.com/IBM-Blockchain/fabric-operator/version"
    44  	"github.com/pkg/errors"
    45  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    46  	yaml "sigs.k8s.io/yaml"
    47  
    48  	appsv1 "k8s.io/api/apps/v1"
    49  	corev1 "k8s.io/api/core/v1"
    50  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    51  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    52  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    53  	"k8s.io/apimachinery/pkg/labels"
    54  	"k8s.io/apimachinery/pkg/runtime"
    55  	"k8s.io/apimachinery/pkg/types"
    56  	ctrl "sigs.k8s.io/controller-runtime"
    57  	"sigs.k8s.io/controller-runtime/pkg/client"
    58  	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
    59  	"sigs.k8s.io/controller-runtime/pkg/controller"
    60  	"sigs.k8s.io/controller-runtime/pkg/event"
    61  	"sigs.k8s.io/controller-runtime/pkg/handler"
    62  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    63  	"sigs.k8s.io/controller-runtime/pkg/manager"
    64  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    65  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    66  	"sigs.k8s.io/controller-runtime/pkg/source"
    67  )
    68  
    69  const (
    70  	KIND = "IBPPeer"
    71  )
    72  
    73  var log = logf.Log.WithName("controller_ibppeer")
    74  
    75  type CoreConfig interface {
    76  	GetMaxNameLength() *int
    77  }
    78  
    79  // Add creates a new IBPPeer Controller and adds it to the Manager. The Manager will set fields on the Controller
    80  // and Start it when the Manager is Started.
    81  func Add(mgr manager.Manager, config *config.Config) error {
    82  	r, err := newReconciler(mgr, config)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	return add(mgr, r)
    87  }
    88  
    89  // newReconciler returns a new reconcile.Reconciler
    90  func newReconciler(mgr manager.Manager, cfg *config.Config) (*ReconcileIBPPeer, error) {
    91  	client := controllerclient.New(mgr.GetClient(), &global.ConfigSetter{Config: cfg.Operator.Globals})
    92  	scheme := mgr.GetScheme()
    93  
    94  	ibppeer := &ReconcileIBPPeer{
    95  		client:         client,
    96  		scheme:         scheme,
    97  		Config:         cfg,
    98  		update:         map[string][]Update{},
    99  		mutex:          &sync.Mutex{},
   100  		RestartService: staggerrestarts.New(client, cfg.Operator.Restart.Timeout.Get()),
   101  	}
   102  
   103  	restClient, err := clientset.NewForConfig(mgr.GetConfig())
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	switch cfg.Offering {
   109  	case offering.K8S:
   110  		ibppeer.Offering = k8speer.New(client, scheme, cfg)
   111  	case offering.OPENSHIFT:
   112  		ibppeer.Offering = openshiftpeer.New(client, scheme, cfg, restClient)
   113  	}
   114  
   115  	return ibppeer, nil
   116  }
   117  
   118  // add adds a new Controller to mgr with r as the reconcile.Reconciler
   119  func add(mgr manager.Manager, r *ReconcileIBPPeer) error {
   120  	// Create a new controller
   121  	predicateFuncs := predicate.Funcs{
   122  		CreateFunc: r.CreateFunc,
   123  		UpdateFunc: r.UpdateFunc,
   124  		DeleteFunc: r.DeleteFunc,
   125  	}
   126  
   127  	c, err := controller.New("ibppeer-controller", mgr, controller.Options{Reconciler: r})
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// Watch for changes to primary resource IBPPeer
   133  	err = c.Watch(&source.Kind{Type: &current.IBPPeer{}}, &handler.EnqueueRequestForObject{}, predicateFuncs)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	// Watch for changes to config maps (Create and Update funcs handle only watching for restart config map)
   139  	err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, predicateFuncs)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// Watch for changes to secondary resource Pods and requeue the owner IBPPeer
   145  	err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
   146  		IsController: true,
   147  		OwnerType:    &current.IBPPeer{},
   148  	})
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	// Watch for changes to tertiary resource Secrets and requeue the owner IBPPeer
   154  	err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{
   155  		IsController: true,
   156  		OwnerType:    &current.IBPPeer{},
   157  	})
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	// Watch for changes to tertiary resource Secrets and requeue the owner IBPPeer
   163  	err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{
   164  		IsController: true,
   165  		OwnerType:    &current.IBPPeer{},
   166  	}, predicateFuncs)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  var _ reconcile.Reconciler = &ReconcileIBPPeer{}
   175  
   176  //go:generate counterfeiter -o mocks/peerreconcile.go -fake-name PeerReconcile . peerReconcile
   177  
   178  type peerReconcile interface {
   179  	Reconcile(*current.IBPPeer, basepeer.Update) (common.Result, error)
   180  }
   181  
   182  // ReconcileIBPPeer reconciles a IBPPeer object
   183  type ReconcileIBPPeer struct {
   184  	// This client, initialized using mgr.Client() above, is a split client
   185  	// that reads objects from the cache and writes to the apiserver
   186  	client controllerclient.Client
   187  	scheme *runtime.Scheme
   188  
   189  	k8sSecret *corev1.Secret
   190  
   191  	Offering       peerReconcile
   192  	Config         *config.Config
   193  	RestartService *staggerrestarts.StaggerRestartsService
   194  
   195  	update map[string][]Update
   196  	mutex  *sync.Mutex
   197  }
   198  
   199  // Reconcile reads that state of the cluster for a IBPPeer object and makes changes based on the state read
   200  // and what is in the IBPPeer.Spec
   201  // Note:
   202  // The Controller will requeue the Request to be processed again if the returned error is non-nil or
   203  // Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
   204  func (r *ReconcileIBPPeer) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
   205  	var err error
   206  
   207  	reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
   208  
   209  	// If peer-restart-config configmap is the object being reconciled, reconcile the
   210  	// restart configmap.
   211  	if request.Name == "peer-restart-config" {
   212  		requeue, err := r.ReconcileRestart(request.Namespace)
   213  		// Error reconciling restart - requeue the request.
   214  		if err != nil {
   215  			return reconcile.Result{}, err
   216  		}
   217  		// Restart reconciled, requeue request if required.
   218  		return reconcile.Result{
   219  			Requeue: requeue,
   220  		}, nil
   221  	}
   222  
   223  	reqLogger.Info("Reconciling IBPPeer")
   224  
   225  	// Fetch the IBPPeer instance
   226  	instance := &current.IBPPeer{}
   227  	err = r.client.Get(context.TODO(), request.NamespacedName, instance)
   228  	if err != nil {
   229  		if k8serrors.IsNotFound(err) {
   230  			// Request object not found, could have been deleted after reconcile request.
   231  			// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
   232  			// Return and don't requeue
   233  			return reconcile.Result{}, nil
   234  		}
   235  		// Error reading the object - requeue the request.
   236  		return reconcile.Result{}, err
   237  	}
   238  
   239  	var maxNameLength *int
   240  
   241  	co, err := instance.GetConfigOverride()
   242  	if err != nil {
   243  		return reconcile.Result{}, err
   244  	}
   245  
   246  	configOverride := co.(CoreConfig)
   247  	maxNameLength = configOverride.GetMaxNameLength()
   248  
   249  	err = util.ValidationChecks(instance.TypeMeta, instance.ObjectMeta, "IBPPeer", maxNameLength)
   250  	if err != nil {
   251  		return reconcile.Result{}, err
   252  	}
   253  
   254  	reqLogger.Info(fmt.Sprintf("Current update stack to process: %+v", GetUpdateStack(r.update)))
   255  
   256  	update := r.GetUpdateStatus(instance)
   257  	reqLogger.Info(fmt.Sprintf("Reconciling IBPPeer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues()))
   258  
   259  	result, err := r.Offering.Reconcile(instance, r.PopUpdate(instance.GetName()))
   260  	setStatusErr := r.SetStatus(instance, result.Status, err)
   261  	if setStatusErr != nil {
   262  		return reconcile.Result{}, operatorerrors.IsBreakingError(setStatusErr, "failed to update status", log)
   263  	}
   264  
   265  	if err != nil {
   266  		return reconcile.Result{}, operatorerrors.IsBreakingError(errors.Wrapf(err, "Peer instance '%s' encountered error", instance.GetName()), "stopping reconcile loop", log)
   267  	}
   268  
   269  	if result.Requeue {
   270  		r.PushUpdate(instance.GetName(), *update)
   271  	}
   272  
   273  	reqLogger.Info(fmt.Sprintf("Finished reconciling IBPPeer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues()))
   274  
   275  	// If the stack still has items that require processing, keep reconciling
   276  	// until the stack has been cleared
   277  	_, found := r.update[instance.GetName()]
   278  	if found {
   279  		if len(r.update[instance.GetName()]) > 0 {
   280  			return reconcile.Result{
   281  				Requeue: true,
   282  			}, nil
   283  		}
   284  	}
   285  
   286  	return result.Result, nil
   287  }
   288  
   289  func (r *ReconcileIBPPeer) SetStatus(instance *current.IBPPeer, reconcileStatus *current.CRStatus, reconcileErr error) error {
   290  	err := r.SaveSpecState(instance)
   291  	if err != nil {
   292  		return errors.Wrap(err, "failed to save spec state")
   293  	}
   294  
   295  	// This is get is required but should not be, the reason we need to get the latest instance is because
   296  	// there is code between the reconcile start and SetStatus that ends up updating the instance. Since
   297  	// instance gets updated, but we are still working with original (outdated) version of instance, trying
   298  	// to update it fails with "object as been modified".
   299  	//
   300  	// TODO: Instance should only be updated at the start of reconcile (e.g. PreReconcileChecks), and if is updated
   301  	// the request should be requeued and not processed. The only only time the intance should be updated is in
   302  	// SetStatus
   303  	err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, instance)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	status := instance.Status.CRStatus
   309  
   310  	if reconcileErr != nil {
   311  		status.Type = current.Error
   312  		status.Status = current.True
   313  		status.Reason = "errorOccurredDuringReconcile"
   314  		status.Message = reconcileErr.Error()
   315  		status.LastHeartbeatTime = time.Now().String()
   316  		status.ErrorCode = operatorerrors.GetErrorCode(reconcileErr)
   317  
   318  		instance.Status = current.IBPPeerStatus{
   319  			CRStatus: status,
   320  		}
   321  
   322  		log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type))
   323  		err = r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{
   324  			Resilient: &controllerclient.ResilientPatch{
   325  				Retry:    2,
   326  				Into:     &current.IBPPeer{},
   327  				Strategy: k8sclient.MergeFrom,
   328  			},
   329  		})
   330  		if err != nil {
   331  			return err
   332  		}
   333  
   334  		return nil
   335  	}
   336  
   337  	status.Versions.Reconciled = instance.Spec.FabricVersion
   338  
   339  	// Check if reconcile loop returned an updated status that differs from exisiting status.
   340  	// If so, set status to the reconcile status.
   341  	if reconcileStatus != nil {
   342  		if instance.Status.Type != reconcileStatus.Type || instance.Status.Reason != reconcileStatus.Reason || instance.Status.Message != reconcileStatus.Message {
   343  			status.Type = reconcileStatus.Type
   344  			status.Status = current.True
   345  			status.Reason = reconcileStatus.Reason
   346  			status.Message = reconcileStatus.Message
   347  			status.LastHeartbeatTime = time.Now().String()
   348  
   349  			instance.Status = current.IBPPeerStatus{
   350  				CRStatus: status,
   351  			}
   352  
   353  			log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type))
   354  			err := r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{
   355  				Resilient: &controllerclient.ResilientPatch{
   356  					Retry:    2,
   357  					Into:     &current.IBPPeer{},
   358  					Strategy: k8sclient.MergeFrom,
   359  				},
   360  			})
   361  			if err != nil {
   362  				return err
   363  			}
   364  
   365  			return nil
   366  		}
   367  	}
   368  
   369  	running, err := r.GetPodStatus(instance)
   370  	if err != nil {
   371  		return err
   372  	}
   373  
   374  	if running {
   375  		if instance.Status.Type == current.Deployed || instance.Status.Type == current.Warning {
   376  			return nil
   377  		}
   378  		status.Type = current.Deployed
   379  		status.Status = current.True
   380  		status.Reason = "allPodsRunning"
   381  	} else {
   382  		if instance.Status.Type == current.Deploying {
   383  			return nil
   384  		}
   385  		status.Type = current.Deploying
   386  		status.Status = current.True
   387  		status.Reason = "waitingForPods"
   388  	}
   389  
   390  	instance.Status = current.IBPPeerStatus{
   391  		CRStatus: status,
   392  	}
   393  	instance.Status.LastHeartbeatTime = time.Now().String()
   394  	log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type))
   395  	err = r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{
   396  		Resilient: &controllerclient.ResilientPatch{
   397  			Retry:    2,
   398  			Into:     &current.IBPPeer{},
   399  			Strategy: k8sclient.MergeFrom,
   400  		},
   401  	})
   402  	if err != nil {
   403  		return err
   404  	}
   405  
   406  	return nil
   407  }
   408  
   409  func (r *ReconcileIBPPeer) SaveSpecState(instance *current.IBPPeer) error {
   410  	data, err := yaml.Marshal(instance.Spec)
   411  	if err != nil {
   412  		return err
   413  	}
   414  
   415  	cm := &corev1.ConfigMap{
   416  		ObjectMeta: v1.ObjectMeta{
   417  			Name:      fmt.Sprintf("%s-spec", instance.GetName()),
   418  			Namespace: instance.GetNamespace(),
   419  			Labels:    instance.GetLabels(),
   420  		},
   421  		BinaryData: map[string][]byte{
   422  			"spec": data,
   423  		},
   424  	}
   425  
   426  	err = r.client.CreateOrUpdate(context.TODO(), cm, controllerclient.CreateOrUpdateOption{Owner: instance, Scheme: r.scheme})
   427  	if err != nil {
   428  		return err
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func (r *ReconcileIBPPeer) GetSpecState(instance *current.IBPPeer) (*corev1.ConfigMap, error) {
   435  	cm := &corev1.ConfigMap{}
   436  	nn := types.NamespacedName{
   437  		Name:      fmt.Sprintf("%s-spec", instance.GetName()),
   438  		Namespace: instance.GetNamespace(),
   439  	}
   440  
   441  	err := r.client.Get(context.TODO(), nn, cm)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	return cm, nil
   447  }
   448  
   449  func (r *ReconcileIBPPeer) GetPodStatus(instance *current.IBPPeer) (bool, error) {
   450  	labelSelector, err := labels.Parse(fmt.Sprintf("app=%s", instance.GetName()))
   451  	if err != nil {
   452  		return false, errors.Wrap(err, "failed to parse label selector for app name")
   453  	}
   454  
   455  	listOptions := &client.ListOptions{
   456  		LabelSelector: labelSelector,
   457  		Namespace:     instance.GetNamespace(),
   458  	}
   459  
   460  	podList := &corev1.PodList{}
   461  	err = r.client.List(context.TODO(), podList, listOptions)
   462  	if err != nil {
   463  		return false, err
   464  	}
   465  
   466  	if len(podList.Items) == 0 {
   467  		return false, nil
   468  	}
   469  
   470  	for _, pod := range podList.Items {
   471  		if pod.Status.Phase != corev1.PodRunning {
   472  			return false, nil
   473  		}
   474  	}
   475  
   476  	return true, nil
   477  }
   478  
   479  func (r *ReconcileIBPPeer) getIgnoreDiffs() []string {
   480  	return []string{
   481  		`Template\.Spec\.Containers\.slice\[\d\]\.Resources\.Requests\.map\[memory\].s`,
   482  		`Template\.Spec\.InitContainers\.slice\[\d\]\.Resources\.Requests\.map\[memory\].s`,
   483  		`Ports\.slice\[\d\]\.Protocol`,
   484  	}
   485  }
   486  
   487  func (r *ReconcileIBPPeer) getSelectorLabels(instance *current.IBPPeer) map[string]string {
   488  	label := os.Getenv("OPERATOR_LABEL_PREFIX")
   489  	if label == "" {
   490  		label = "fabric"
   491  	}
   492  
   493  	return map[string]string{
   494  		"app":                          instance.Name,
   495  		"creator":                      label,
   496  		"orgname":                      instance.Spec.MSPID,
   497  		"release":                      "operator",
   498  		"helm.sh/chart":                "ibm-" + label,
   499  		"app.kubernetes.io/name":       label,
   500  		"app.kubernetes.io/instance":   label + "peer",
   501  		"app.kubernetes.io/managed-by": label + "-operator",
   502  	}
   503  }
   504  
   505  func (r *ReconcileIBPPeer) CreateFunc(e event.CreateEvent) bool {
   506  	update := Update{}
   507  
   508  	switch e.Object.(type) {
   509  	case *current.IBPPeer:
   510  		peer := e.Object.(*current.IBPPeer)
   511  		log.Info(fmt.Sprintf("Create event detected for peer '%s'", peer.GetName()))
   512  
   513  		if peer.Status.HasType() {
   514  			cm, err := r.GetSpecState(peer)
   515  			if err != nil {
   516  				log.Info(fmt.Sprintf("Failed getting saved peer spec '%s', can't perform update checks, triggering reconcile: %s", peer.GetName(), err.Error()))
   517  				return true
   518  			}
   519  
   520  			specBytes := cm.BinaryData["spec"]
   521  			savedPeer := &current.IBPPeer{}
   522  
   523  			err = yaml.Unmarshal(specBytes, &savedPeer.Spec)
   524  			if err != nil {
   525  				log.Info(fmt.Sprintf("Unmarshal failed for saved peer spec '%s', can't perform update checks, triggering reconcile: %s", peer.GetName(), err.Error()))
   526  				return true
   527  			}
   528  
   529  			if !reflect.DeepEqual(peer.Spec, savedPeer.Spec) {
   530  				log.Info(fmt.Sprintf("IBPPeer '%s' spec was updated while operator was down", peer.GetName()))
   531  				update.specUpdated = true
   532  			}
   533  
   534  			if !reflect.DeepEqual(peer.Spec.ConfigOverride, savedPeer.Spec.ConfigOverride) {
   535  				log.Info(fmt.Sprintf("IBPPeer '%s' overrides were updated while operator was down", peer.GetName()))
   536  				update.overridesUpdated = true
   537  			}
   538  
   539  			update.imagesUpdated = imagesUpdated(savedPeer, peer)
   540  			update.fabricVersionUpdated = fabricVersionUpdated(savedPeer, peer)
   541  
   542  			log.Info(fmt.Sprintf("Create event triggering reconcile for updating peer '%s'", peer.GetName()))
   543  			r.PushUpdate(peer.GetName(), update)
   544  			return true
   545  		}
   546  
   547  		// If creating resource for the first time, check that a unique name is provided
   548  		err := commoncontroller.ValidateCRName(r.client, peer.Name, peer.Namespace, commoncontroller.IBPPEER)
   549  		if err != nil {
   550  			log.Error(err, "failed to validate peer name")
   551  			operror := operatorerrors.Wrap(err, operatorerrors.InvalidCustomResourceCreateRequest, "failed to validate custom resource name")
   552  			err = r.SetStatus(peer, nil, operror)
   553  			if err != nil {
   554  				log.Error(err, "failed to set status to error", "peer.name", peer.Name, "error", "InvalidCustomResourceCreateRequest")
   555  			}
   556  			return false
   557  		}
   558  
   559  		log.Info(fmt.Sprintf("Create event triggering reconcile for creating peer '%s'", peer.GetName()))
   560  
   561  	case *corev1.Secret:
   562  		secret := e.Object.(*corev1.Secret)
   563  
   564  		if secret.OwnerReferences == nil || len(secret.OwnerReferences) == 0 {
   565  			isPeerSecret, err := r.AddOwnerReferenceToSecret(secret)
   566  			if err != nil || !isPeerSecret {
   567  				return false
   568  			}
   569  		}
   570  
   571  		if secret.OwnerReferences[0].Kind == KIND {
   572  			log.Info(fmt.Sprintf("Create event detected for secret '%s'", secret.GetName()))
   573  			instanceName := secret.OwnerReferences[0].Name
   574  
   575  			if util.IsSecretTLSCert(secret.Name) {
   576  				update.tlsCertCreated = true
   577  				log.Info(fmt.Sprintf("TLS cert create detected on IBPPeer custom resource %s", instanceName))
   578  			} else if util.IsSecretEcert(secret.Name) {
   579  				update.ecertCreated = true
   580  				log.Info(fmt.Sprintf("Ecert create detected on IBPPeer custom resource %s", instanceName))
   581  			} else {
   582  				return false
   583  			}
   584  
   585  			log.Info(fmt.Sprintf("Peer crypto create triggering reconcile on IBPPeer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues()))
   586  			r.PushUpdate(instanceName, update)
   587  		}
   588  
   589  	case *appsv1.Deployment:
   590  		dep := e.Object.(*appsv1.Deployment)
   591  		log.Info(fmt.Sprintf("Create event detected by IBPPeer controller for deployment '%s', triggering reconcile", dep.GetName()))
   592  	case *corev1.ConfigMap:
   593  		cm := e.Object.(*corev1.ConfigMap)
   594  		if cm.Name == "peer-restart-config" {
   595  			log.Info(fmt.Sprintf("Create event detected by IBPPeer contoller for config map '%s', triggering restart reconcile", cm.GetName()))
   596  		} else {
   597  			return false
   598  		}
   599  
   600  	}
   601  
   602  	return true
   603  }
   604  
   605  func (r *ReconcileIBPPeer) UpdateFunc(e event.UpdateEvent) bool {
   606  	update := Update{}
   607  
   608  	switch e.ObjectOld.(type) {
   609  	case *current.IBPPeer:
   610  		oldPeer := e.ObjectOld.(*current.IBPPeer)
   611  		newPeer := e.ObjectNew.(*current.IBPPeer)
   612  		log.Info(fmt.Sprintf("Update event detected for peer '%s'", oldPeer.GetName()))
   613  
   614  		if util.CheckIfZoneOrRegionUpdated(oldPeer.Spec.Zone, newPeer.Spec.Zone) {
   615  			log.Error(errors.New("Zone update is not allowed"), "invalid spec update")
   616  			return false
   617  		}
   618  
   619  		if util.CheckIfZoneOrRegionUpdated(oldPeer.Spec.Region, newPeer.Spec.Region) {
   620  			log.Error(errors.New("Region update is not allowed"), "invalid spec update")
   621  			return false
   622  		}
   623  
   624  		if reflect.DeepEqual(oldPeer.Spec, newPeer.Spec) {
   625  			return false
   626  		}
   627  		log.Info(fmt.Sprintf("%s spec updated", oldPeer.GetName()))
   628  		update.specUpdated = true
   629  
   630  		// Check for changes to peer tag to determine if any migration logic needs to be executed
   631  		// from old peer version to new peer version
   632  		if oldPeer.Spec.Images != nil && newPeer.Spec.Images != nil {
   633  			if oldPeer.Spec.Images.PeerTag != newPeer.Spec.Images.PeerTag {
   634  				log.Info(fmt.Sprintf("Peer tag update from %s to %s", oldPeer.Spec.Images.PeerTag, newPeer.Spec.Images.PeerTag))
   635  				update.peerTagUpdated = true
   636  			}
   637  		}
   638  
   639  		if !reflect.DeepEqual(oldPeer.Spec.ConfigOverride, newPeer.Spec.ConfigOverride) {
   640  			log.Info(fmt.Sprintf("%s config override updated", oldPeer.GetName()))
   641  			update.overridesUpdated = true
   642  		}
   643  
   644  		update.mspUpdated = commoncontroller.MSPInfoUpdateDetected(oldPeer.Spec.Secret, newPeer.Spec.Secret)
   645  
   646  		if newPeer.Spec.Action.Restart == true {
   647  			update.restartNeeded = true
   648  		}
   649  
   650  		if oldPeer.Spec.Action.Reenroll.Ecert != newPeer.Spec.Action.Reenroll.Ecert {
   651  			update.ecertReenrollNeeded = newPeer.Spec.Action.Reenroll.Ecert
   652  		}
   653  
   654  		if oldPeer.Spec.Action.Reenroll.TLSCert != newPeer.Spec.Action.Reenroll.TLSCert {
   655  			update.tlsReenrollNeeded = newPeer.Spec.Action.Reenroll.TLSCert
   656  		}
   657  
   658  		if oldPeer.Spec.Action.Reenroll.EcertNewKey != newPeer.Spec.Action.Reenroll.EcertNewKey {
   659  			update.ecertNewKeyReenroll = newPeer.Spec.Action.Reenroll.EcertNewKey
   660  		}
   661  
   662  		if oldPeer.Spec.Action.Reenroll.TLSCertNewKey != newPeer.Spec.Action.Reenroll.TLSCertNewKey {
   663  			update.tlscertNewKeyReenroll = newPeer.Spec.Action.Reenroll.TLSCertNewKey
   664  		}
   665  
   666  		oldVer := version.String(oldPeer.Spec.FabricVersion)
   667  		newVer := version.String(newPeer.Spec.FabricVersion)
   668  
   669  		// check if this V1 -> V2.2.x / V2.4.x peer migration
   670  		if (oldPeer.Spec.FabricVersion == "" ||
   671  			version.GetMajorReleaseVersion(oldPeer.Spec.FabricVersion) == version.V1) &&
   672  			version.GetMajorReleaseVersion(newPeer.Spec.FabricVersion) == version.V2 {
   673  			update.migrateToV2 = true
   674  			if newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1) {
   675  				update.migrateToV24 = true
   676  			}
   677  		}
   678  
   679  		// check if this V2.2.x -> V2.4.x peer migration
   680  		if (version.GetMajorReleaseVersion(oldPeer.Spec.FabricVersion) == version.V2) &&
   681  			oldVer.LessThan(version.V2_4_1) &&
   682  			(newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1)) {
   683  			update.migrateToV24 = true
   684  		}
   685  
   686  		if newPeer.Spec.Action.UpgradeDBs == true {
   687  			update.upgradedbs = true
   688  		}
   689  
   690  		if newPeer.Spec.Action.Enroll.Ecert == true {
   691  			update.ecertEnroll = true
   692  		}
   693  
   694  		if newPeer.Spec.Action.Enroll.TLSCert == true {
   695  			update.tlscertEnroll = true
   696  		}
   697  
   698  		if oldPeer.Spec.NodeOUDisabled() != newPeer.Spec.NodeOUDisabled() {
   699  			update.nodeOUUpdated = true
   700  		}
   701  
   702  		// if use updates NumSecondsWarningPeriod field once we have already run the reconcile
   703  		// we need to retrigger the timer logic
   704  		if oldPeer.Spec.NumSecondsWarningPeriod != newPeer.Spec.NumSecondsWarningPeriod {
   705  			update.ecertUpdated = true
   706  			update.tlsCertUpdated = true
   707  			log.Info(fmt.Sprintf("%s NumSecondsWarningPeriod updated", oldPeer.Name))
   708  		}
   709  
   710  		update.imagesUpdated = imagesUpdated(oldPeer, newPeer)
   711  		update.fabricVersionUpdated = fabricVersionUpdated(oldPeer, newPeer)
   712  
   713  		log.Info(fmt.Sprintf("Spec update triggering reconcile on IBPPeer custom resource %s, update [ %+v ]", oldPeer.Name, update.GetUpdateStackWithTrues()))
   714  		r.PushUpdate(oldPeer.Name, update)
   715  		return true
   716  
   717  	case *corev1.Secret:
   718  		oldSecret := e.ObjectOld.(*corev1.Secret)
   719  		newSecret := e.ObjectNew.(*corev1.Secret)
   720  
   721  		if oldSecret.OwnerReferences == nil || len(oldSecret.OwnerReferences) == 0 {
   722  			isPeerSecret, err := r.AddOwnerReferenceToSecret(oldSecret)
   723  			if err != nil || !isPeerSecret {
   724  				return false
   725  			}
   726  		}
   727  
   728  		if oldSecret.OwnerReferences[0].Kind == KIND {
   729  			if reflect.DeepEqual(oldSecret.Data, newSecret.Data) {
   730  				return false
   731  			}
   732  
   733  			log.Info(fmt.Sprintf("Update event detected on secret '%s'", oldSecret.GetName()))
   734  			instanceName := oldSecret.OwnerReferences[0].Name
   735  			if util.IsSecretTLSCert(oldSecret.Name) {
   736  				update.tlsCertUpdated = true
   737  				log.Info(fmt.Sprintf("TLS cert update detected on IBPPeer custom resource %s", instanceName))
   738  			} else if util.IsSecretEcert(oldSecret.Name) {
   739  				update.ecertUpdated = true
   740  				log.Info(fmt.Sprintf("ecert update detected on IBPPeer custom resource %s", instanceName))
   741  			} else {
   742  				return false
   743  			}
   744  
   745  			log.Info(fmt.Sprintf("Peer crypto update triggering reconcile on IBPPeer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues()))
   746  			r.PushUpdate(instanceName, update)
   747  			return true
   748  		}
   749  
   750  	case *appsv1.Deployment:
   751  		oldDeployment := e.ObjectOld.(*appsv1.Deployment)
   752  		log.Info(fmt.Sprintf("Spec update detected by IBPPeer controller on deployment '%s'", oldDeployment.GetName()))
   753  
   754  	case *corev1.ConfigMap:
   755  		cm := e.ObjectOld.(*corev1.ConfigMap)
   756  		if cm.Name == "peer-restart-config" {
   757  			log.Info("Update event detected for peer-restart-config, triggering restart reconcile")
   758  			return true
   759  		}
   760  
   761  	}
   762  
   763  	return false
   764  }
   765  
   766  // DeleteFunc will perform any necessary clean up, such as removing artificates that were
   767  // left dangling after the deletion of the peer resource
   768  func (r *ReconcileIBPPeer) DeleteFunc(e event.DeleteEvent) bool {
   769  	switch e.Object.(type) {
   770  	case *current.IBPPeer:
   771  		peer := e.Object.(*current.IBPPeer)
   772  		log.Info(fmt.Sprintf("Peer (%s) deleted", peer.GetName()))
   773  
   774  		// Deleting this config map manually, in 2.5.1 release of operator this config map was created
   775  		// without proper controller references set and was not cleaned up on peer resource deletion.
   776  		log.Info(fmt.Sprintf("Deleting %s-init-config config map, if found", peer.GetName()))
   777  		if err := r.client.Delete(context.TODO(), &corev1.ConfigMap{
   778  			ObjectMeta: metav1.ObjectMeta{
   779  				Name:      fmt.Sprintf("%s-init-config", peer.GetName()),
   780  				Namespace: peer.GetNamespace(),
   781  			},
   782  		}); client.IgnoreNotFound(err) != nil {
   783  			log.Info(fmt.Sprintf("failed to delete config map: %s", err))
   784  		}
   785  
   786  	case *appsv1.Deployment:
   787  		dep := e.Object.(*appsv1.Deployment)
   788  		log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on deployment '%s'", dep.GetName()))
   789  	case *corev1.Secret:
   790  		secret := e.Object.(*corev1.Secret)
   791  		log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on secret '%s'", secret.GetName()))
   792  	case *corev1.ConfigMap:
   793  		cm := e.Object.(*corev1.ConfigMap)
   794  		log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on configmap '%s'", cm.GetName()))
   795  
   796  	}
   797  
   798  	return true
   799  }
   800  
   801  func (r *ReconcileIBPPeer) GetUpdateStatusAtElement(instance *current.IBPPeer, index int) *Update {
   802  	r.mutex.Lock()
   803  	defer r.mutex.Unlock()
   804  
   805  	update := Update{}
   806  	_, ok := r.update[instance.GetName()]
   807  	if !ok {
   808  		return &update
   809  	}
   810  
   811  	if len(r.update[instance.GetName()]) >= 1 {
   812  		update = r.update[instance.GetName()][index]
   813  	}
   814  
   815  	return &update
   816  }
   817  
   818  func (r *ReconcileIBPPeer) GetUpdateStatus(instance *current.IBPPeer) *Update {
   819  	return r.GetUpdateStatusAtElement(instance, 0)
   820  }
   821  
   822  func (r *ReconcileIBPPeer) PushUpdate(instanceName string, update Update) {
   823  	r.mutex.Lock()
   824  	defer r.mutex.Unlock()
   825  
   826  	r.update[instanceName] = r.AppendUpdateIfMissing(r.update[instanceName], update)
   827  }
   828  
   829  func (r *ReconcileIBPPeer) PopUpdate(instanceName string) *Update {
   830  	r.mutex.Lock()
   831  	defer r.mutex.Unlock()
   832  
   833  	update := Update{}
   834  	if len(r.update[instanceName]) >= 1 {
   835  		update = r.update[instanceName][0]
   836  		if len(r.update[instanceName]) == 1 {
   837  			r.update[instanceName] = []Update{}
   838  		} else {
   839  			r.update[instanceName] = r.update[instanceName][1:]
   840  		}
   841  	}
   842  
   843  	return &update
   844  }
   845  
   846  func (r *ReconcileIBPPeer) AppendUpdateIfMissing(updates []Update, update Update) []Update {
   847  	for _, u := range updates {
   848  		if u == update {
   849  			return updates
   850  		}
   851  	}
   852  	return append(updates, update)
   853  }
   854  
   855  func (r *ReconcileIBPPeer) AddOwnerReferenceToSecret(secret *corev1.Secret) (bool, error) {
   856  	// Peer secrets we are looking to add owner references to are named:
   857  	// <prefix>-<instance name>-<type>
   858  	// <instance name>-init-rootcert
   859  
   860  	// The following secrets are created by operator, and will have owner references:
   861  	// <instance name>-genesis
   862  	// <instance name>-crypto-backup
   863  	// <instance name>-secret
   864  
   865  	items := strings.Split(secret.Name, "-")
   866  	if len(items) < 3 {
   867  		// Secret names we are looking for will be split into at least 3 strings:
   868  		// [prefix, instance name, type] OR [instance name, "init", "rootcert"]
   869  		return false, nil
   870  	}
   871  
   872  	// Account for the case where the instance's name is hyphenated
   873  	var instanceName string
   874  	if strings.Contains(secret.Name, "-init-rootcert") {
   875  		instanceName = strings.Join(items[:len(items)-2], "-") // instance name contains all but last 2 items
   876  	} else {
   877  		instanceName = strings.Join(items[1:len(items)-1], "-") // instance name contains all but first and last item
   878  	}
   879  
   880  	listOptions := &client.ListOptions{
   881  		Namespace: secret.Namespace,
   882  	}
   883  
   884  	peerList := &current.IBPPeerList{}
   885  	err := r.client.List(context.TODO(), peerList, listOptions)
   886  	if err != nil {
   887  		return false, errors.Wrap(err, "failed to get list of peers")
   888  	}
   889  
   890  	for _, o := range peerList.Items {
   891  		peer := o
   892  		if peer.Name == instanceName {
   893  			// Instance 'i' found in list of orderers
   894  			err := r.client.Update(context.TODO(), secret, controllerclient.UpdateOption{
   895  				Owner:  &peer,
   896  				Scheme: r.scheme,
   897  			})
   898  			if err != nil {
   899  				return false, err
   900  			}
   901  			return true, nil
   902  		}
   903  	}
   904  
   905  	return false, nil
   906  }
   907  
   908  func (r *ReconcileIBPPeer) SetupWithManager(mgr ctrl.Manager) error {
   909  	return ctrl.NewControllerManagedBy(mgr).
   910  		For(&current.IBPPeer{}).
   911  		Complete(r)
   912  }
   913  
   914  func GetUpdateStack(allUpdates map[string][]Update) string {
   915  	stack := ""
   916  
   917  	for orderer, updates := range allUpdates {
   918  		currentStack := ""
   919  		for index, update := range updates {
   920  			currentStack += fmt.Sprintf("{ %s}", update.GetUpdateStackWithTrues())
   921  			if index != len(updates)-1 {
   922  				currentStack += " , "
   923  			}
   924  		}
   925  		stack += fmt.Sprintf("%s: [ %s ] ", orderer, currentStack)
   926  	}
   927  
   928  	return stack
   929  }
   930  
   931  func (r *ReconcileIBPPeer) ReconcileRestart(namespace string) (bool, error) {
   932  	requeue, err := r.RestartService.Reconcile("peer", namespace)
   933  	if err != nil {
   934  		log.Error(err, "failed to reconcile restart queues in peer-restart-config")
   935  		return false, err
   936  	}
   937  
   938  	return requeue, nil
   939  }