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  }