github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/vmo/statefulset.go (about)

     1  // Copyright (C) 2020, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package vmo
     5  
     6  import (
     7  	"context"
     8  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
     9  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    10  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/nodes"
    11  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/statefulsets"
    12  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs/vzlog"
    13  	appsv1 "k8s.io/api/apps/v1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/labels"
    16  )
    17  
    18  // CreateStatefulSets creates/updates/deletes VMO statefulset k8s resources
    19  func CreateStatefulSets(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) (bool, error) {
    20  	storageClass, err := getStorageClassOverride(controller, vmo.Spec.StorageClass)
    21  	if err != nil {
    22  		controller.log.Errorf("Failed to determine storage class for VMI %s: %v", vmo.Name, err)
    23  		return false, err
    24  	}
    25  
    26  	selector := labels.SelectorFromSet(map[string]string{constants.VMOLabel: vmo.Name})
    27  	existingList, err := controller.statefulSetLister.StatefulSets(vmo.Namespace).List(selector)
    28  	if err != nil {
    29  		return false, err
    30  	}
    31  	initialMasterNodes := getInitialMasterNodes(vmo, existingList)
    32  	expectedList, err := statefulsets.New(controller.log, vmo, storageClass, initialMasterNodes)
    33  	if err != nil {
    34  		controller.log.Errorf("Failed to create StatefulSet specs for VMI %s: %v", vmo.Name, err)
    35  		return false, err
    36  	}
    37  
    38  	// Loop through the existing stateful sets and create/update as needed
    39  	controller.log.Oncef("Creating/updating Statefulsets for VMI %s", vmo.Name)
    40  	plan := statefulsets.CreatePlan(controller.log, existingList, expectedList)
    41  
    42  	for _, sts := range plan.Create {
    43  		if _, err := controller.kubeclientset.AppsV1().StatefulSets(vmo.Namespace).Create(context.TODO(), sts, metav1.CreateOptions{}); err != nil {
    44  			return plan.ExistingCluster, logReturnError(controller.log, sts, err)
    45  		}
    46  	}
    47  
    48  	// Loop through existing statefulsets again to update PVC owner references
    49  	latestList, err := controller.statefulSetLister.StatefulSets(vmo.Namespace).List(selector)
    50  	if err != nil {
    51  		return plan.ExistingCluster, err
    52  	}
    53  	for _, sts := range latestList {
    54  		if err := updateOwnerForPVCs(controller, sts, vmo.Name, vmo.Namespace); err != nil {
    55  			return plan.ExistingCluster, err
    56  		}
    57  	}
    58  
    59  	for _, sts := range plan.Update {
    60  		if err := updateStatefulSet(controller, sts, vmo, plan); err != nil {
    61  			return plan.ExistingCluster, logReturnError(controller.log, sts, err)
    62  		}
    63  	}
    64  
    65  	for _, sts := range plan.Delete {
    66  		if err := scaleDownStatefulSet(controller, expectedList, sts, vmo); err != nil {
    67  			return plan.ExistingCluster, err
    68  		}
    69  		// We only scale down one statefulset at a time. This gives the statefulset data
    70  		// time to migrate and the cluster to heal itself.
    71  		break
    72  	}
    73  
    74  	if plan.Conflict == nil {
    75  		controller.log.Oncef("Successfully applied StatefulSets for VMI %s", vmo.Name)
    76  	} else {
    77  		controller.log.Errorf("StatefulSet update plan conflict: %v", plan.Conflict)
    78  	}
    79  	return plan.ExistingCluster, plan.Conflict
    80  }
    81  
    82  func logReturnError(log vzlog.VerrazzanoLogger, sts *appsv1.StatefulSet, err error) error {
    83  	return log.ErrorfNewErr("Failed to update StatefulSets %s:%s: %v", sts.Namespace, sts.Name, err)
    84  }
    85  
    86  // getInitialMasterNodes returns the initial master nodes string if the cluster is not already bootstrapped
    87  func getInitialMasterNodes(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, existing []*appsv1.StatefulSet) string {
    88  	if len(existing) > 0 {
    89  		return ""
    90  	}
    91  	return nodes.InitialMasterNodes(vmo.Name, nodes.MasterNodes(vmo))
    92  }
    93  
    94  func updateStatefulSet(c *Controller, sts *appsv1.StatefulSet, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, plan *statefulsets.StatefulSetPlan) error {
    95  	// if the cluster is alive, but unhealthy we shouldn't do an update - may cause data loss/corruption
    96  	if !plan.BounceNodes && plan.ExistingCluster {
    97  		// We should only update an existing cluster if it is healthy
    98  		if err := c.osClient.IsGreen(vmo); err != nil {
    99  			return err
   100  		}
   101  	}
   102  
   103  	if _, err := c.kubeclientset.AppsV1().StatefulSets(vmo.Namespace).Update(context.TODO(), sts, metav1.UpdateOptions{}); err != nil {
   104  		return err
   105  	}
   106  	// if it was a single node cluster, delete the pod to ensure it picks up the updated settings.
   107  	if plan.BounceNodes {
   108  		return c.kubeclientset.CoreV1().Pods(vmo.Namespace).Delete(context.TODO(), sts.Name+"-0", metav1.DeleteOptions{})
   109  	}
   110  	return nil
   111  }
   112  
   113  // scaleDownStatefulSet scales down a statefulset, and deletes the statefulset if it is already at 1 or fewer replicas.
   114  func scaleDownStatefulSet(c *Controller, expectedList []*appsv1.StatefulSet, statefulSet *appsv1.StatefulSet, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
   115  	deleteSTS := func() error {
   116  		err := c.kubeclientset.AppsV1().StatefulSets(vmo.Namespace).Delete(context.TODO(), statefulSet.Name, metav1.DeleteOptions{})
   117  		if err != nil {
   118  			c.log.Errorf("Failed to delete StatefulSet %s: %v", statefulSet.Name, err)
   119  			return err
   120  		}
   121  		return nil
   122  	}
   123  
   124  	// don't worry about cluster health if we are deleting the cluster or the statefulset is unhealthy
   125  	if len(expectedList) < 1 || statefulSet.Status.ReadyReplicas < 1 {
   126  		return deleteSTS()
   127  	}
   128  
   129  	// The cluster should be in steady state before any nodes are removed
   130  	if err := c.osClient.IsUpdated(vmo); err != nil {
   131  		return err
   132  	}
   133  
   134  	// If the statefulset has multiple replicas, scale it down. this allows existing data to be migrated to another node on the cluster.
   135  	// If the statefulset already has one replica, then it can be deleted.
   136  	if *statefulSet.Spec.Replicas > 1 {
   137  		*statefulSet.Spec.Replicas--
   138  		if _, err := c.kubeclientset.AppsV1().StatefulSets(vmo.Namespace).Update(context.TODO(), statefulSet, metav1.UpdateOptions{}); err != nil {
   139  			return err
   140  		}
   141  
   142  	} else {
   143  		return deleteSTS()
   144  	}
   145  	return nil
   146  }
   147  
   148  // Update each PVC metadata.ownerReferences field to refer to the StatefulSet (STS).
   149  // PVCs are created automatically by Kubernetes when the STS is created
   150  // because the STS has a volumeClaimTemplate.  However, the PVCs are not deleted
   151  // when the STS is deleted. By setting the PVC metadata.ownerReferences field to refer
   152  // to the STS resource, the PVC will automatically get deleted when the STS is deleted.
   153  // Because PVC is dynamic, when it is deleted, the bound PV will also get deleted.
   154  // NOTE: This cannot be done automatically using the STS VolumeClaimTemplate.
   155  func updateOwnerForPVCs(controller *Controller, statefulSet *appsv1.StatefulSet, vmoName string, vmoNamespace string) error {
   156  	pvcNames := statefulsets.GetPVCNames(statefulSet)
   157  	for _, pvcName := range pvcNames {
   158  		pvc, err := controller.pvcLister.PersistentVolumeClaims(vmoNamespace).Get(pvcName)
   159  		if err != nil {
   160  			return err
   161  		}
   162  		if len(pvc.OwnerReferences) != 0 {
   163  			continue
   164  		}
   165  		pvc.OwnerReferences = []metav1.OwnerReference{{
   166  			APIVersion: "apps/v1",
   167  			Kind:       "StatefulSet",
   168  			Name:       statefulSet.Name,
   169  			UID:        statefulSet.UID,
   170  		}}
   171  		controller.log.Debugf("Setting StatefulSet owner reference for PVC %s", pvc.Name)
   172  		_, err = controller.kubeclientset.CoreV1().PersistentVolumeClaims(vmoNamespace).Update(context.TODO(), pvc, metav1.UpdateOptions{})
   173  		if err != nil {
   174  			controller.log.Errorf("Failed to update the owner reference in PVC %s: %v", pvc.Name, err)
   175  			return err
   176  		}
   177  	}
   178  	return nil
   179  }