github.com/openshift/installer@v1.4.17/pkg/destroy/ibmcloud/ibmcloud.go (about) 1 package ibmcloud 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "os" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/IBM/go-sdk-core/v5/core" 13 "github.com/IBM/networking-go-sdk/dnsrecordsv1" 14 "github.com/IBM/networking-go-sdk/dnssvcsv1" 15 "github.com/IBM/networking-go-sdk/dnszonesv1" 16 "github.com/IBM/networking-go-sdk/zonesv1" 17 "github.com/IBM/platform-services-go-sdk/iampolicymanagementv1" 18 "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" 19 "github.com/IBM/platform-services-go-sdk/resourcemanagerv2" 20 "github.com/IBM/vpc-go-sdk/vpcv1" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 utilerrors "k8s.io/apimachinery/pkg/util/errors" 24 "k8s.io/apimachinery/pkg/util/wait" 25 26 configv1 "github.com/openshift/api/config/v1" 27 icibmcloud "github.com/openshift/installer/pkg/asset/installconfig/ibmcloud" 28 "github.com/openshift/installer/pkg/destroy/providers" 29 "github.com/openshift/installer/pkg/types" 30 ibmcloudtypes "github.com/openshift/installer/pkg/types/ibmcloud" 31 "github.com/openshift/installer/pkg/version" 32 ) 33 34 var ( 35 defaultTimeout = 2 * time.Minute 36 ) 37 38 // ClusterUninstaller holds the various options for the cluster we want to delete 39 type ClusterUninstaller struct { 40 ClusterName string 41 Context context.Context 42 Logger logrus.FieldLogger 43 InfraID string 44 AccountID string 45 BaseDomain string 46 CISInstanceCRN string 47 DNSInstanceID string 48 Region string 49 ResourceGroupName string 50 UserProvidedSubnets []string 51 UserProvidedVPC string 52 53 managementSvc *resourcemanagerv2.ResourceManagerV2 54 controllerSvc *resourcecontrollerv2.ResourceControllerV2 55 vpcSvc *vpcv1.VpcV1 56 iamPolicyManagementSvc *iampolicymanagementv1.IamPolicyManagementV1 57 zonesSvc *zonesv1.ZonesV1 58 dnsZonesSvc *dnszonesv1.DnsZonesV1 59 dnsServicesSvc *dnssvcsv1.DnsSvcsV1 60 dnsRecordsSvc *dnsrecordsv1.DnsRecordsV1 61 maxRetryAttempt int 62 serviceEndpoints []configv1.IBMCloudServiceEndpoint 63 64 // Cache endpoint override for IBM Cloud IAM, if one was provided in serviceEndpoints 65 iamServiceEndpointOverride string 66 67 resourceGroupID string 68 cosInstanceID string 69 zoneID string 70 71 errorTracker 72 pendingItemTracker 73 } 74 75 // New returns an IBMCloud destroyer from ClusterMetadata. 76 func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (providers.Destroyer, error) { 77 cUninstaller := &ClusterUninstaller{ 78 ClusterName: metadata.ClusterName, 79 Context: context.Background(), 80 Logger: logger, 81 InfraID: metadata.InfraID, 82 AccountID: metadata.ClusterPlatformMetadata.IBMCloud.AccountID, 83 BaseDomain: metadata.ClusterPlatformMetadata.IBMCloud.BaseDomain, 84 CISInstanceCRN: metadata.ClusterPlatformMetadata.IBMCloud.CISInstanceCRN, 85 DNSInstanceID: metadata.ClusterPlatformMetadata.IBMCloud.DNSInstanceID, 86 Region: metadata.ClusterPlatformMetadata.IBMCloud.Region, 87 ResourceGroupName: metadata.ClusterPlatformMetadata.IBMCloud.ResourceGroupName, 88 serviceEndpoints: metadata.ClusterPlatformMetadata.IBMCloud.ServiceEndpoints, 89 UserProvidedSubnets: metadata.ClusterPlatformMetadata.IBMCloud.Subnets, 90 UserProvidedVPC: metadata.ClusterPlatformMetadata.IBMCloud.VPC, 91 pendingItemTracker: newPendingItemTracker(), 92 maxRetryAttempt: 30, 93 } 94 95 for _, endpoint := range cUninstaller.serviceEndpoints { 96 if endpoint.Name == configv1.IBMCloudServiceIAM { 97 cUninstaller.iamServiceEndpointOverride = endpoint.URL 98 break 99 } 100 } 101 102 return cUninstaller, nil 103 } 104 105 // Retry ... 106 func (o *ClusterUninstaller) Retry(funcToRetry func() (error, bool)) error { 107 var err error 108 var stopRetry bool 109 retryGap := 10 110 for i := 0; i < o.maxRetryAttempt; i++ { 111 if i > 0 { 112 time.Sleep(time.Duration(retryGap) * time.Second) 113 } 114 // Call function which required retry, retry is decided by function itself 115 err, stopRetry = funcToRetry() 116 if stopRetry { 117 break 118 } 119 120 if (i + 1) < o.maxRetryAttempt { 121 o.Logger.Infof("UNEXPECTED RESULT, Re-attempting execution .., attempt=%d, retry-gap=%d, max-retry-Attempts=%d, stopRetry=%t, error=%v", i+1, 122 retryGap, o.maxRetryAttempt, stopRetry, err) 123 } 124 } 125 return err 126 } 127 128 // Run is the entrypoint to start the uninstall process 129 func (o *ClusterUninstaller) Run() (*types.ClusterQuota, error) { 130 err := o.loadSDKServices() 131 if err != nil { 132 return nil, err 133 } 134 135 err = o.destroyCluster() 136 if err != nil { 137 return nil, errors.Wrap(err, "failed to destroy cluster") 138 } 139 140 return nil, nil 141 } 142 143 func (o *ClusterUninstaller) destroyCluster() error { 144 stagedFuncs := [][]struct { 145 name string 146 execute func() error 147 }{{ 148 {name: "Stop instances", execute: o.stopInstances}, 149 }, { 150 // Instances must occur before LB cleanup 151 {name: "Instances", execute: o.destroyInstances}, 152 {name: "Disks", execute: o.destroyDisks}, 153 }, { 154 // LB's must occur before Subnet cleanup 155 {name: "Load Balancers", execute: o.destroyLoadBalancers}, 156 }, { 157 {name: "Subnets", execute: o.destroySubnets}, 158 }, { 159 // Public Gateways must occur before FIP's cleanup 160 // Security Groups must occur before VPC cleanup 161 {name: "Images", execute: o.destroyImages}, 162 {name: "Public Gateways", execute: o.destroyPublicGateways}, 163 {name: "Security Groups", execute: o.destroySecurityGroups}, 164 }, { 165 {name: "Floating IPs", execute: o.destroyFloatingIPs}, 166 }, { 167 {name: "Dedicated Hosts", execute: o.destroyDedicatedHosts}, 168 {name: "VPCs", execute: o.destroyVPCs}, 169 }, { 170 // IAM must occur before COS cleanup 171 {name: "IAM Authorizations", execute: o.destroyIAMAuthorizations}, 172 }, { 173 // COS must occur before RG cleanup 174 {name: "Cloud Object Storage Instances", execute: o.destroyCOSInstances}, 175 {name: "Dedicated Host Groups", execute: o.destroyDedicatedHostGroups}, 176 }, { 177 {name: "DNS Records", execute: o.destroyDNSRecords}, 178 {name: "Resource Groups", execute: o.destroyResourceGroups}, 179 }} 180 181 for _, stage := range stagedFuncs { 182 var wg sync.WaitGroup 183 errCh := make(chan error) 184 wgDone := make(chan bool) 185 186 for _, f := range stage { 187 wg.Add(1) 188 go o.executeStageFunction(f, errCh, &wg) 189 } 190 191 go func() { 192 wg.Wait() 193 close(wgDone) 194 }() 195 196 select { 197 case <-wgDone: 198 // On to the next stage 199 continue 200 case err := <-errCh: 201 return err 202 } 203 } 204 205 return nil 206 } 207 208 func (o *ClusterUninstaller) executeStageFunction(f struct { 209 name string 210 execute func() error 211 }, errCh chan error, wg *sync.WaitGroup) error { 212 defer wg.Done() 213 214 err := wait.PollImmediateInfinite( 215 time.Second*10, 216 func() (bool, error) { 217 ferr := f.execute() 218 if ferr != nil { 219 o.Logger.Debugf("%s: %v", f.name, ferr) 220 return false, nil 221 } 222 return true, nil 223 }, 224 ) 225 226 if err != nil { 227 errCh <- err 228 } 229 return nil 230 } 231 232 func (o *ClusterUninstaller) loadSDKServices() error { 233 apiKey := os.Getenv("IC_API_KEY") 234 235 userAgentString := fmt.Sprintf("OpenShift/4.x Destroyer/%s", version.Raw) 236 237 // ResourceManagerV2 238 rmAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 239 if err != nil { 240 return err 241 } 242 rmOptions := &resourcemanagerv2.ResourceManagerV2Options{ 243 Authenticator: rmAuthenticator, 244 } 245 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceResourceManager, o.serviceEndpoints); overrideURL != "" { 246 rmOptions.URL = overrideURL 247 } 248 o.managementSvc, err = resourcemanagerv2.NewResourceManagerV2(rmOptions) 249 if err != nil { 250 return err 251 } 252 o.managementSvc.Service.SetUserAgent(userAgentString) 253 254 // Attempt to retrieve the ResourceGroupID as soon as possible to validate ResourceGroupName 255 _, err = o.ResourceGroupID() 256 if err != nil { 257 return err 258 } 259 260 // ResourceControllerV2 261 rcAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 262 if err != nil { 263 return err 264 } 265 rcOptions := &resourcecontrollerv2.ResourceControllerV2Options{ 266 Authenticator: rcAuthenticator, 267 } 268 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceResourceController, o.serviceEndpoints); overrideURL != "" { 269 rcOptions.URL = overrideURL 270 } 271 o.controllerSvc, err = resourcecontrollerv2.NewResourceControllerV2(rcOptions) 272 if err != nil { 273 return err 274 } 275 o.controllerSvc.Service.SetUserAgent(userAgentString) 276 277 // IamPolicyManagementV1 278 ipmAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 279 if err != nil { 280 return err 281 } 282 ipmOptions := &iampolicymanagementv1.IamPolicyManagementV1Options{ 283 Authenticator: ipmAuthenticator, 284 } 285 if o.iamServiceEndpointOverride != "" { 286 ipmOptions.URL = o.iamServiceEndpointOverride 287 } 288 o.iamPolicyManagementSvc, err = iampolicymanagementv1.NewIamPolicyManagementV1(ipmOptions) 289 if err != nil { 290 return err 291 } 292 o.iamPolicyManagementSvc.Service.SetUserAgent(userAgentString) 293 294 if len(o.CISInstanceCRN) > 0 { 295 // ZonesV1 296 zAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 297 if err != nil { 298 return err 299 } 300 zonesOptions := &zonesv1.ZonesV1Options{ 301 Authenticator: zAuthenticator, 302 Crn: core.StringPtr(o.CISInstanceCRN), 303 } 304 // Check for CIS override endpoint once for zonesv1 and dnsrecordsv1 API calls 305 overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceCIS, o.serviceEndpoints) 306 if overrideURL != "" { 307 zonesOptions.URL = overrideURL 308 } 309 o.zonesSvc, err = zonesv1.NewZonesV1(zonesOptions) 310 if err != nil { 311 return err 312 } 313 o.zonesSvc.Service.SetUserAgent(userAgentString) 314 315 // Get the Zone ID 316 options := o.zonesSvc.NewListZonesOptions() 317 resources, _, err := o.zonesSvc.ListZonesWithContext(o.Context, options) 318 if err != nil { 319 return err 320 } 321 322 zoneID := "" 323 for _, zone := range resources.Result { 324 if strings.Contains(o.BaseDomain, *zone.Name) { 325 zoneID = *zone.ID 326 } 327 } 328 if zoneID == "" { 329 return errors.Errorf("Could not determine CIS DNS zone ID from base domain %q", o.BaseDomain) 330 } 331 332 // DnsRecordsV1 333 dnsAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 334 if err != nil { 335 return err 336 } 337 dnsRecordsOptions := &dnsrecordsv1.DnsRecordsV1Options{ 338 Authenticator: dnsAuthenticator, 339 Crn: core.StringPtr(o.CISInstanceCRN), 340 ZoneIdentifier: core.StringPtr(zoneID), 341 } 342 if overrideURL != "" { 343 dnsRecordsOptions.URL = overrideURL 344 } 345 o.dnsRecordsSvc, err = dnsrecordsv1.NewDnsRecordsV1(dnsRecordsOptions) 346 if err != nil { 347 return err 348 } 349 o.dnsRecordsSvc.Service.SetUserAgent(userAgentString) 350 } else if len(o.DNSInstanceID) > 0 { 351 // DnsSvcsV1 352 dnsAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 353 if err != nil { 354 return err 355 } 356 dnssvcsOptions := &dnssvcsv1.DnsSvcsV1Options{ 357 Authenticator: dnsAuthenticator, 358 } 359 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceDNSServices, o.serviceEndpoints); overrideURL != "" { 360 dnssvcsOptions.URL = overrideURL 361 } 362 o.dnsServicesSvc, err = dnssvcsv1.NewDnsSvcsV1(dnssvcsOptions) 363 if err != nil { 364 return err 365 } 366 o.dnsServicesSvc.Service.SetUserAgent(userAgentString) 367 368 // Get the Zone ID 369 dzOptions := o.dnsServicesSvc.NewListDnszonesOptions(o.DNSInstanceID) 370 dzResult, _, err := o.dnsServicesSvc.ListDnszonesWithContext(o.Context, dzOptions) 371 if err != nil { 372 return err 373 } 374 375 zoneID := "" 376 for _, zone := range dzResult.Dnszones { 377 if o.BaseDomain == *zone.Name { 378 zoneID = *zone.ID 379 break 380 } 381 } 382 if zoneID == "" { 383 return errors.Errorf("Could not determine DNS Services DNS zone ID from base domain %q", o.BaseDomain) 384 } 385 o.Logger.Debugf("Found DNS Services DNS zone ID for base domain %q: %s", o.BaseDomain, zoneID) 386 o.zoneID = zoneID 387 } 388 389 // VpcV1 390 vpcAuthenticator, err := icibmcloud.NewIamAuthenticator(apiKey, o.iamServiceEndpointOverride) 391 if err != nil { 392 return err 393 } 394 vpcOptions := &vpcv1.VpcV1Options{ 395 Authenticator: vpcAuthenticator, 396 } 397 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceVPC, o.serviceEndpoints); overrideURL != "" { 398 vpcOptions.URL = overrideURL 399 } 400 o.vpcSvc, err = vpcv1.NewVpcV1(vpcOptions) 401 if err != nil { 402 return err 403 } 404 o.vpcSvc.Service.SetUserAgent(userAgentString) 405 406 region, _, err := o.vpcSvc.GetRegion(o.vpcSvc.NewGetRegionOptions(o.Region)) 407 if err != nil { 408 return err 409 } 410 411 err = o.vpcSvc.SetServiceURL(fmt.Sprintf("%s/v1", *region.Endpoint)) 412 if err != nil { 413 return err 414 } 415 416 return nil 417 } 418 419 func (o *ClusterUninstaller) contextWithTimeout() (context.Context, context.CancelFunc) { 420 return context.WithTimeout(o.Context, defaultTimeout) 421 } 422 423 // ResourceGroupID returns the ID of the resource group using its name 424 func (o *ClusterUninstaller) ResourceGroupID() (string, error) { 425 if o.resourceGroupID != "" { 426 return o.resourceGroupID, nil 427 } 428 429 // If no ResourceGroupName is available, raise an error 430 if o.ResourceGroupName == "" { 431 return "", errors.Errorf("No ResourceGroupName provided") 432 } 433 434 ctx, cancel := o.contextWithTimeout() 435 defer cancel() 436 437 options := o.managementSvc.NewListResourceGroupsOptions() 438 options.SetAccountID(o.AccountID) 439 options.SetName(o.ResourceGroupName) 440 resources, _, err := o.managementSvc.ListResourceGroupsWithContext(ctx, options) 441 if err != nil { 442 return "", err 443 } 444 if len(resources.Resources) == 0 { 445 return "", errors.Errorf("ResourceGroup '%q' not found", o.ResourceGroupName) 446 } else if len(resources.Resources) > 1 { 447 return "", errors.Errorf("Too many resource groups matched name %q", o.ResourceGroupName) 448 } 449 450 o.SetResourceGroupID(*resources.Resources[0].ID) 451 return o.resourceGroupID, nil 452 } 453 454 // SetResourceGroupID sets the resource group ID 455 func (o *ClusterUninstaller) SetResourceGroupID(id string) { 456 o.resourceGroupID = id 457 } 458 459 type ibmError struct { 460 Status int 461 Message string 462 } 463 464 func isNoOp(err *ibmError) bool { 465 if err == nil { 466 return false 467 } 468 469 return err.Status == http.StatusNotFound 470 } 471 472 // aggregateError is a utility function that takes a slice of errors and an 473 // optional pending argument, and returns an error or nil 474 func aggregateError(errs []error, pending ...int) error { 475 err := utilerrors.NewAggregate(errs) 476 if err != nil { 477 return err 478 } 479 if len(pending) > 0 && pending[0] > 0 { 480 return errors.Errorf("%d items pending", pending[0]) 481 } 482 return nil 483 } 484 485 // pendingItemTracker tracks a set of pending item names for a given type of resource 486 type pendingItemTracker struct { 487 pendingItems map[string]cloudResources 488 } 489 490 func newPendingItemTracker() pendingItemTracker { 491 return pendingItemTracker{ 492 pendingItems: map[string]cloudResources{}, 493 } 494 } 495 496 // GetAllPendintItems returns a slice of all of the pending items across all types. 497 func (t pendingItemTracker) GetAllPendingItems() []cloudResource { 498 var items []cloudResource 499 for _, is := range t.pendingItems { 500 for _, i := range is { 501 items = append(items, i) 502 } 503 } 504 return items 505 } 506 507 // getPendingItems returns the list of resources to be deleted. 508 func (t pendingItemTracker) getPendingItems(itemType string) []cloudResource { 509 lastFound, exists := t.pendingItems[itemType] 510 if !exists { 511 lastFound = cloudResources{} 512 } 513 return lastFound.list() 514 } 515 516 // insertPendingItems adds to the list of resources to be deleted. 517 func (t pendingItemTracker) insertPendingItems(itemType string, items []cloudResource) []cloudResource { 518 lastFound, exists := t.pendingItems[itemType] 519 if !exists { 520 lastFound = cloudResources{} 521 } 522 lastFound = lastFound.insert(items...) 523 t.pendingItems[itemType] = lastFound 524 return lastFound.list() 525 } 526 527 // deletePendingItems removes from the list of resources to be deleted. 528 func (t pendingItemTracker) deletePendingItems(itemType string, items []cloudResource) []cloudResource { 529 lastFound, exists := t.pendingItems[itemType] 530 if !exists { 531 lastFound = cloudResources{} 532 } 533 lastFound = lastFound.delete(items...) 534 t.pendingItems[itemType] = lastFound 535 return lastFound.list() 536 } 537 538 func isErrorStatus(code int64) bool { 539 return code != 0 && (code < 200 || code >= 300) 540 } 541 542 func (o *ClusterUninstaller) clusterLabelFilter() string { 543 return fmt.Sprintf("kubernetes-io-cluster-%s:owned", o.InfraID) 544 }