github.com/openshift/installer@v1.4.17/pkg/destroy/nutanix/nutanix.go (about)

     1  package nutanix
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	nutanixclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
    10  	"github.com/pkg/errors"
    11  	"github.com/sirupsen/logrus"
    12  	"k8s.io/apimachinery/pkg/util/wait"
    13  
    14  	"github.com/openshift/installer/pkg/destroy/providers"
    15  	installertypes "github.com/openshift/installer/pkg/types"
    16  	nutanixtypes "github.com/openshift/installer/pkg/types/nutanix"
    17  )
    18  
    19  const (
    20  	emptyFilter                = ""
    21  	expectedCategoryKeyFormat  = "kubernetes-io-cluster-%s"
    22  	expectedCategoryValueOwned = "owned"
    23  )
    24  
    25  // clusterUninstaller holds the various options for the cluster we want to delete.
    26  type clusterUninstaller struct {
    27  	clusterID string
    28  	infraID   string
    29  	v3Client  *nutanixclientv3.Client
    30  	logger    logrus.FieldLogger
    31  }
    32  
    33  // New returns an Nutanix destroyer from ClusterMetadata.
    34  func New(logger logrus.FieldLogger, metadata *installertypes.ClusterMetadata) (providers.Destroyer, error) {
    35  	ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second)
    36  	defer cancel()
    37  
    38  	v3Client, err := nutanixtypes.CreateNutanixClient(ctx,
    39  		metadata.ClusterPlatformMetadata.Nutanix.PrismCentral,
    40  		metadata.ClusterPlatformMetadata.Nutanix.Port,
    41  		metadata.ClusterPlatformMetadata.Nutanix.Username,
    42  		metadata.ClusterPlatformMetadata.Nutanix.Password,
    43  	)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	return &clusterUninstaller{
    49  		clusterID: metadata.ClusterID,
    50  		infraID:   metadata.InfraID,
    51  		v3Client:  v3Client,
    52  		logger:    logger,
    53  	}, nil
    54  }
    55  
    56  // Run is the entrypoint to start the uninstall process.
    57  func (o *clusterUninstaller) Run() (*installertypes.ClusterQuota, error) {
    58  	o.logger.Infof("Starting deletion of Nutanix infrastructure for Openshift cluster %q", o.infraID)
    59  	err := wait.PollImmediateInfinite(time.Second*30, o.destroyCluster)
    60  	if err != nil {
    61  		return nil, errors.Wrap(err, "failed to destroy cluster")
    62  	}
    63  
    64  	return nil, nil
    65  }
    66  
    67  func (o *clusterUninstaller) destroyCluster() (bool, error) {
    68  	cleanupFuncs := []struct {
    69  		name    string
    70  		execute func(*clusterUninstaller) error
    71  	}{
    72  		{name: "VMs", execute: cleanupVMs},
    73  		{name: "Images", execute: cleanupImages},
    74  		{name: "Categories", execute: cleanupCategories},
    75  	}
    76  
    77  	done := true
    78  	for _, cleanupFunc := range cleanupFuncs {
    79  		if err := cleanupFunc.execute(o); err != nil {
    80  			o.logger.Debugf("%s: %v", cleanupFunc.name, err)
    81  			done = false
    82  		}
    83  	}
    84  	return done, nil
    85  }
    86  
    87  func cleanupVMs(o *clusterUninstaller) error {
    88  	ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second)
    89  	defer cancel()
    90  
    91  	matchedVirtualMachineList := make([]*nutanixclientv3.VMIntentResource, 0)
    92  	allVMs, err := o.v3Client.V3.ListAllVM(ctx, emptyFilter)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	for _, v := range allVMs.Entities {
    98  		if hasCategoryOwned(v.Metadata, expectedCategoryKey(o.infraID)) {
    99  			matchedVirtualMachineList = append(matchedVirtualMachineList, v)
   100  		}
   101  	}
   102  
   103  	if len(matchedVirtualMachineList) == 0 {
   104  		o.logger.Infof("No VMs found that require deletion for cluster %q", o.clusterID)
   105  	} else {
   106  		logToBeDeletedVMs(matchedVirtualMachineList, o.logger)
   107  		err := deleteVMs(o.v3Client.V3, matchedVirtualMachineList, o.logger)
   108  		if err != nil {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func cleanupImages(o *clusterUninstaller) error {
   116  	ctx, cancel := context.WithTimeout(context.TODO(), 120*time.Second)
   117  	defer cancel()
   118  
   119  	allImages, err := o.v3Client.V3.ListAllImage(ctx, emptyFilter)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	imageDeletionFailed := false
   125  	for _, image := range allImages.Entities {
   126  		if hasCategoryOwned(image.Metadata, expectedCategoryKey(o.infraID)) {
   127  			imageName := *image.Spec.Name
   128  			imageUUID := *image.Metadata.UUID
   129  			o.logger.Infof("Deleting image %q with UUID %q", imageName, imageUUID)
   130  			response, err := o.v3Client.V3.DeleteImage(ctx, imageUUID)
   131  			if err != nil {
   132  				o.logger.Errorf("Failed to delete image %q: %v", imageUUID, err)
   133  				imageDeletionFailed = true
   134  				continue
   135  			}
   136  
   137  			if err := nutanixtypes.WaitForTask(o.v3Client.V3, response.Status.ExecutionContext.TaskUUID.(string)); err != nil {
   138  				o.logger.Errorf("Failed to confirm image deletion %q: %v", imageUUID, err)
   139  				imageDeletionFailed = true
   140  			}
   141  		}
   142  	}
   143  
   144  	if imageDeletionFailed {
   145  		return fmt.Errorf("failed to cleanup images")
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func cleanupCategories(o *clusterUninstaller) error {
   152  	ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
   153  	defer cancel()
   154  
   155  	expCatKey := expectedCategoryKey(o.infraID)
   156  	key, err := o.v3Client.V3.GetCategoryKey(ctx, expCatKey)
   157  	if err != nil {
   158  		if strings.Contains(err.Error(), "does not exist") {
   159  			//Already deleted
   160  			return nil
   161  		}
   162  		return err
   163  	}
   164  
   165  	values, err := o.v3Client.V3.ListCategoryValues(context.TODO(), *key.Name, &nutanixclientv3.CategoryListMetadata{})
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	categoryDeletionFailed := false
   171  	for _, value := range values.Entities {
   172  		o.logger.Infof("Deleting category value : %s", *value.Value)
   173  		err := o.v3Client.V3.DeleteCategoryValue(ctx, expCatKey, *value.Value)
   174  		if err != nil {
   175  			o.logger.Errorf("Failed to delete category value %q: %v", *value.Value, err)
   176  			categoryDeletionFailed = true
   177  		}
   178  	}
   179  
   180  	o.logger.Infof("Deleting category key : %s", expCatKey)
   181  	err = o.v3Client.V3.DeleteCategoryKey(ctx, expCatKey)
   182  	if err != nil {
   183  		o.logger.Errorf("Failed to delete category key %q: %v", expCatKey, err)
   184  		categoryDeletionFailed = true
   185  	}
   186  
   187  	if categoryDeletionFailed {
   188  		return fmt.Errorf("failed to delete category")
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func deleteVMs(clientV3 nutanixclientv3.Service, vms []*nutanixclientv3.VMIntentResource, l logrus.FieldLogger) error {
   195  	ctx, cancel := context.WithTimeout(context.TODO(), 120*time.Second)
   196  	defer cancel()
   197  
   198  	taskUUIDs := make([]string, 0)
   199  	vmDeletionFailed := false
   200  	for _, vm := range vms {
   201  		l.Infof("Deleting VM %s with ID %s", *vm.Spec.Name, *vm.Metadata.UUID)
   202  		response, err := clientV3.DeleteVM(ctx, *vm.Metadata.UUID)
   203  		if err != nil {
   204  			l.Errorf("Failed to delete VM %q: %v", *vm.Metadata.UUID, err)
   205  			vmDeletionFailed = true
   206  			continue
   207  		}
   208  
   209  		taskUUIDs = append(taskUUIDs, response.Status.ExecutionContext.TaskUUID.(string))
   210  	}
   211  
   212  	err := nutanixtypes.WaitForTasks(clientV3, taskUUIDs)
   213  	if err != nil {
   214  		l.Errorf("Failed to confirm deletion of VMs: %v", err)
   215  		vmDeletionFailed = true
   216  	}
   217  
   218  	if vmDeletionFailed {
   219  		return fmt.Errorf("failed to delete VMs")
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func logToBeDeletedVMs(vms []*nutanixclientv3.VMIntentResource, l logrus.FieldLogger) {
   226  	l.Info("Virtual machines scheduled to be deleted: ")
   227  	for _, vm := range vms {
   228  		l.Infof("- %s", *vm.Spec.Name)
   229  	}
   230  }
   231  
   232  func expectedCategoryKey(infraID string) string {
   233  	return fmt.Sprintf(expectedCategoryKeyFormat, infraID)
   234  }
   235  
   236  func hasCategoryOwned(metadata *nutanixclientv3.Metadata, expectedCategoryKey string) bool {
   237  	value, keyExists := metadata.Categories[expectedCategoryKey]
   238  	return keyExists && value == expectedCategoryValueOwned
   239  }