github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/vmo/pvc_update.go (about) 1 // Copyright (C) 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 "strings" 9 10 vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1" 11 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources" 12 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/nodes" 13 appsv1 "k8s.io/api/apps/v1" 14 corev1 "k8s.io/api/core/v1" 15 storagev1 "k8s.io/api/storage/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/labels" 18 ) 19 20 // resizePVC resizes a PVC to a new size 21 // if the underlying storage class does not support expansion, a new PVC will be created. 22 func resizePVC(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, existingPVC, expectedPVC *corev1.PersistentVolumeClaim, storageClass *storagev1.StorageClass) (*string, error) { 23 if storageClass.AllowVolumeExpansion != nil && *storageClass.AllowVolumeExpansion { 24 // Volume expansion means dynamic resize is possible - we can do an Update of the PVC in place 25 updatedPVC := existingPVC.DeepCopy() 26 updatedPVC.Spec.Resources = expectedPVC.Spec.Resources 27 _, err := controller.kubeclientset.CoreV1().PersistentVolumeClaims(vmo.Namespace).Update(context.TODO(), updatedPVC, metav1.UpdateOptions{}) 28 return nil, err 29 } 30 31 // If we are updating an OpenSearch PVC, we need to make sure the OpenSearch cluster is ready 32 // before doing the resize 33 if isOpenSearchPVC(expectedPVC) { 34 if err := controller.osClient.IsDataResizable(vmo); err != nil { 35 return nil, err 36 } 37 } 38 39 // the selectors of the existing PVC should be persisted 40 expectedPVC.Spec.Selector = existingPVC.Spec.Selector 41 // because the new PVC will exist concurrently with the old PVC, it needs a new name 42 // the new name is based off the old name to help correlate the PVC with its deploymnt 43 newName, err := newPVCName(expectedPVC.Name, 5) 44 if err != nil { 45 return nil, err 46 } 47 expectedPVC.Name = newName 48 _, err = controller.kubeclientset.CoreV1().PersistentVolumeClaims(vmo.Namespace).Create(context.TODO(), expectedPVC, metav1.CreateOptions{}) 49 if err != nil { 50 return nil, err 51 } 52 53 // update VMO Spec Storage with the new PVC Name 54 updateVMOStorageForPVC(vmo, existingPVC.Name, expectedPVC.Name) 55 return &expectedPVC.Name, nil 56 } 57 58 // cleanupUnusedPVCs finds any unused PVCs and deletes them 59 func cleanupUnusedPVCs(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error { 60 selector := labels.SelectorFromSet(resources.GetMetaLabels(vmo)) 61 deployments, err := controller.deploymentLister.Deployments(vmo.Namespace).List(selector) 62 if err != nil { 63 return err 64 } 65 inUsePVCNames := getInUsePVCNames(deployments, vmo) 66 allPVCs, err := controller.pvcLister.PersistentVolumeClaims(vmo.Namespace).List(selector) 67 if err != nil { 68 return err 69 } 70 unboundPVCs := getUnboundPVCs(allPVCs, inUsePVCNames) 71 72 for _, unboundPVC := range unboundPVCs { 73 err := controller.kubeclientset.CoreV1().PersistentVolumeClaims(unboundPVC.Namespace).Delete(context.TODO(), unboundPVC.Name, metav1.DeleteOptions{}) 74 if err != nil { 75 return err 76 } 77 } 78 return nil 79 } 80 81 // getInUsePVCNames gets the names of PVCs that are currently used by VMO deployments 82 func getInUsePVCNames(deployments []*appsv1.Deployment, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) map[string]bool { 83 inUsePVCNames := map[string]bool{} 84 for _, deployment := range deployments { 85 for _, volume := range deployment.Spec.Template.Spec.Volumes { 86 if volume.PersistentVolumeClaim != nil { 87 inUsePVCNames[volume.PersistentVolumeClaim.ClaimName] = true 88 } 89 } 90 } 91 92 for _, node := range nodes.DataNodes(vmo) { 93 if node.Storage != nil { 94 for _, pvc := range node.Storage.PvcNames { 95 inUsePVCNames[pvc] = true 96 } 97 } 98 } 99 return inUsePVCNames 100 } 101 102 // getUnboundPVCs gets the VMO-managed PVCs which are not currently used by VMO deployments 103 func getUnboundPVCs(pvcs []*corev1.PersistentVolumeClaim, inUsePVCNames map[string]bool) []*corev1.PersistentVolumeClaim { 104 var unboundPVCs []*corev1.PersistentVolumeClaim 105 for _, pvc := range pvcs { 106 if _, ok := inUsePVCNames[pvc.Name]; !ok { 107 unboundPVCs = append(unboundPVCs, pvc) 108 } 109 } 110 return unboundPVCs 111 } 112 113 // isOpenSearchPVC checks if a PVC is an OpenSearch PVC 114 func isOpenSearchPVC(pvc *corev1.PersistentVolumeClaim) bool { 115 return strings.Contains(pvc.Name, "es-data") 116 } 117 118 // pvcNeedsResize a PVC only needs resize if it is greater than the existing PVC size 119 func pvcNeedsResize(existingPVC, expectedPVC *corev1.PersistentVolumeClaim) bool { 120 existingStorage := existingPVC.Spec.Resources.Requests.Storage() 121 expectedStorage := expectedPVC.Spec.Resources.Requests.Storage() 122 compare := expectedStorage.Cmp(*existingStorage) 123 return compare > 0 124 } 125 126 // newPVCName adds a prefix if not present, otherwise it rewrites the existing prefix 127 func newPVCName(pvcName string, size int) (string, error) { 128 pvcNameSplit := strings.Split(pvcName, "-") 129 suffix, err := resources.GetNewRandomID(size) 130 if err != nil { 131 return "", err 132 } 133 lastIdx := len(pvcNameSplit) - 1 134 if len(pvcNameSplit[lastIdx]) != size { 135 pvcNameSplit = append(pvcNameSplit, suffix) 136 } else { 137 pvcNameSplit[lastIdx] = suffix 138 } 139 140 return strings.Join(pvcNameSplit, "-"), nil 141 } 142 143 // updateVMOStorageForPVC updates the VMO storage to replace an old PVC with a new PVC 144 func updateVMOStorageForPVC(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, oldPVCName, newPVCName string) { 145 updateStorage := func(storage *vmcontrollerv1.Storage) { 146 if storage != nil { 147 for idx, pvcName := range storage.PvcNames { 148 if pvcName == oldPVCName { 149 storage.PvcNames[idx] = newPVCName 150 } 151 } 152 } 153 } 154 155 // Look for the PVC reference and update it 156 updateStorage(&vmo.Spec.Grafana.Storage) 157 updateStorage(vmo.Spec.Elasticsearch.DataNode.Storage) 158 } 159 160 // setPerNodeStorage updates the VMO OpenSearch storage spec to reflect the current API 161 func setPerNodeStorage(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 162 updateFunc := func(storage *vmcontrollerv1.Storage, node *vmcontrollerv1.ElasticsearchNode) { 163 if node.Replicas > 0 && node.Storage == nil { 164 node.Storage = &vmcontrollerv1.Storage{ 165 Size: storage.Size, 166 } 167 } 168 } 169 170 updateFunc(&vmo.Spec.Elasticsearch.Storage, &vmo.Spec.Elasticsearch.MasterNode) 171 updateFunc(&vmo.Spec.Elasticsearch.Storage, &vmo.Spec.Elasticsearch.DataNode) 172 if vmo.Spec.Elasticsearch.DataNode.Storage != nil && len(vmo.Spec.Elasticsearch.Storage.PvcNames) > 0 { 173 vmo.Spec.Elasticsearch.DataNode.Storage.PvcNames = make([]string, len(vmo.Spec.Elasticsearch.Storage.PvcNames)) 174 copy(vmo.Spec.Elasticsearch.DataNode.Storage.PvcNames, vmo.Spec.Elasticsearch.Storage.PvcNames) 175 } 176 177 vmo.Spec.Elasticsearch.Storage = vmcontrollerv1.Storage{} 178 }