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 }