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  }