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 }