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

     1  package powervs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	gohttp "net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/IBM/go-sdk-core/v5/core"
    12  	"github.com/IBM/platform-services-go-sdk/resourcecontrollerv2"
    13  	"k8s.io/apimachinery/pkg/util/wait"
    14  )
    15  
    16  const (
    17  	serviceInstanceTypeName = "service instance"
    18  
    19  	// resource ID for Power Systems Virtual Server in the Global catalog.
    20  	virtualServerResourceID = "abd259f0-9990-11e8-acc8-b9f54a8f1661"
    21  )
    22  
    23  // convertResourceGroupNameToID converts a resource group name/id to an id.
    24  func (o *ClusterUninstaller) convertResourceGroupNameToID(resourceGroupID string) (string, error) {
    25  	listResourceGroupsOptions := o.managementSvc.NewListResourceGroupsOptions()
    26  
    27  	resourceGroups, _, err := o.managementSvc.ListResourceGroups(listResourceGroupsOptions)
    28  	if err != nil {
    29  		return "", err
    30  	}
    31  
    32  	for _, resourceGroup := range resourceGroups.Resources {
    33  		if *resourceGroup.Name == resourceGroupID {
    34  			return *resourceGroup.ID, nil
    35  		} else if *resourceGroup.ID == resourceGroupID {
    36  			return resourceGroupID, nil
    37  		}
    38  	}
    39  
    40  	return "", fmt.Errorf("failed to find resource group %v", resourceGroupID)
    41  }
    42  
    43  // listServiceInstances list service instances for the cluster.
    44  func (o *ClusterUninstaller) listServiceInstances() (cloudResources, error) {
    45  	o.Logger.Debugf("Listing service instances")
    46  
    47  	ctx, cancel := o.contextWithTimeout()
    48  	defer cancel()
    49  
    50  	select {
    51  	case <-ctx.Done():
    52  		o.Logger.Debugf("listServiceInstances: case <-ctx.Done()")
    53  		return nil, o.Context.Err() // we're cancelled, abort
    54  	default:
    55  	}
    56  
    57  	var (
    58  		resourceGroupID string
    59  		options         *resourcecontrollerv2.ListResourceInstancesOptions
    60  		resources       *resourcecontrollerv2.ResourceInstancesList
    61  		err             error
    62  		perPage         int64 = 10
    63  		moreData              = true
    64  		nextURL         *string
    65  	)
    66  
    67  	resourceGroupID, err = o.convertResourceGroupNameToID(o.resourceGroupID)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("failed to convert resourceGroupID: %w", err)
    70  	}
    71  	o.Logger.Debugf("listServiceInstances: converted %v to %v", o.resourceGroupID, resourceGroupID)
    72  
    73  	options = o.controllerSvc.NewListResourceInstancesOptions()
    74  	// options.SetType("resource_instance")
    75  	options.SetResourceGroupID(resourceGroupID)
    76  	options.SetResourceID(virtualServerResourceID)
    77  	options.SetLimit(perPage)
    78  
    79  	result := []cloudResource{}
    80  
    81  	for moreData {
    82  		if options.Start != nil {
    83  			o.Logger.Debugf("listServiceInstances: options = %+v, options.Limit = %v, options.Start = %v, options.ResourceGroupID = %v", options, *options.Limit, *options.Start, *options.ResourceGroupID)
    84  		} else {
    85  			o.Logger.Debugf("listServiceInstances: options = %+v, options.Limit = %v, options.ResourceGroupID = %v", options, *options.Limit, *options.ResourceGroupID)
    86  		}
    87  
    88  		resources, _, err = o.controllerSvc.ListResourceInstancesWithContext(ctx, options)
    89  		if err != nil {
    90  			return nil, fmt.Errorf("failed to list resource instances: %w", err)
    91  		}
    92  
    93  		o.Logger.Debugf("listServiceInstances: resources.RowsCount = %v", *resources.RowsCount)
    94  
    95  		for _, resource := range resources.Resources {
    96  			var (
    97  				getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions
    98  				resourceInstance   *resourcecontrollerv2.ResourceInstance
    99  				response           *core.DetailedResponse
   100  			)
   101  
   102  			o.Logger.Debugf("listServiceInstances: resource.Name = %s", *resource.Name)
   103  
   104  			getResourceOptions = o.controllerSvc.NewGetResourceInstanceOptions(*resource.ID)
   105  
   106  			resourceInstance, response, err = o.controllerSvc.GetResourceInstance(getResourceOptions)
   107  			if err != nil {
   108  				return nil, fmt.Errorf("failed to get instance: %s: %w", response, err)
   109  			}
   110  			if response != nil && response.StatusCode == gohttp.StatusNotFound {
   111  				o.Logger.Debugf("listServiceInstances: gohttp.StatusNotFound")
   112  				continue
   113  			} else if response != nil && response.StatusCode == gohttp.StatusInternalServerError {
   114  				o.Logger.Debugf("listServiceInstances: gohttp.StatusInternalServerError")
   115  				continue
   116  			}
   117  
   118  			if resourceInstance.Type == nil {
   119  				o.Logger.Debugf("listServiceInstances: type: nil")
   120  			} else {
   121  				o.Logger.Debugf("listServiceInstances: type: %v", *resourceInstance.Type)
   122  			}
   123  
   124  			if resourceInstance.Type == nil || resourceInstance.GUID == nil {
   125  				continue
   126  			}
   127  			if *resourceInstance.Type != "service_instance" && *resourceInstance.Type != "composite_instance" {
   128  				continue
   129  			}
   130  			if !strings.Contains(*resource.Name, o.InfraID) {
   131  				continue
   132  			}
   133  
   134  			if strings.Contains(*resource.Name, o.InfraID) {
   135  				result = append(result, cloudResource{
   136  					key:      *resource.ID,
   137  					name:     *resource.Name,
   138  					status:   *resource.GUID,
   139  					typeName: serviceInstanceTypeName,
   140  					id:       *resource.ID,
   141  				})
   142  			}
   143  		}
   144  
   145  		// Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances
   146  		nextURL, err = core.GetQueryParam(resources.NextURL, "start")
   147  		if err != nil {
   148  			return nil, fmt.Errorf("failed to GetQueryParam on start: %w", err)
   149  		}
   150  		if nextURL == nil {
   151  			o.Logger.Debugf("nextURL = nil")
   152  			options.SetStart("")
   153  		} else {
   154  			o.Logger.Debugf("nextURL = %v", *nextURL)
   155  			options.SetStart(*nextURL)
   156  		}
   157  
   158  		moreData = *resources.RowsCount == perPage
   159  	}
   160  
   161  	return cloudResources{}.insert(result...), nil
   162  }
   163  
   164  // destroyServiceInstance destroys a service instance.
   165  func (o *ClusterUninstaller) destroyServiceInstance(item cloudResource) error {
   166  	ctx, cancel := o.contextWithTimeout()
   167  	defer cancel()
   168  
   169  	select {
   170  	case <-ctx.Done():
   171  		o.Logger.Debugf("destroyServiceInstance: case <-ctx.Done()")
   172  		return o.Context.Err() // we're cancelled, abort
   173  	default:
   174  	}
   175  
   176  	o.Logger.Debugf("destroyServiceInstance: Preparing to delete, item.name = %v", item.name)
   177  
   178  	var (
   179  		getOptions *resourcecontrollerv2.GetResourceInstanceOptions
   180  		response   *core.DetailedResponse
   181  		err        error
   182  	)
   183  
   184  	getOptions = o.controllerSvc.NewGetResourceInstanceOptions(item.id)
   185  
   186  	_, response, err = o.controllerSvc.GetResourceInstanceWithContext(ctx, getOptions)
   187  
   188  	if err != nil && response != nil && response.StatusCode == gohttp.StatusNotFound {
   189  		// The resource is gone
   190  		o.deletePendingItems(item.typeName, []cloudResource{item})
   191  		o.Logger.Infof("Deleted Service Instance %q", item.name)
   192  		return nil
   193  	}
   194  	if err != nil && response != nil && response.StatusCode == gohttp.StatusInternalServerError {
   195  		o.Logger.Infof("destroyServiceInstance: internal server error")
   196  		return nil
   197  	}
   198  
   199  	options := o.controllerSvc.NewDeleteResourceInstanceOptions(item.id)
   200  	options.SetRecursive(true)
   201  
   202  	response, err = o.controllerSvc.DeleteResourceInstanceWithContext(ctx, options)
   203  
   204  	if err != nil && response != nil && response.StatusCode != gohttp.StatusNotFound {
   205  		return fmt.Errorf("failed to delete service instance %s: %w", item.name, err)
   206  	}
   207  
   208  	o.Logger.Infof("Deleted Service Instance %q", item.name)
   209  	o.deletePendingItems(item.typeName, []cloudResource{item})
   210  
   211  	return nil
   212  }
   213  
   214  // destroyServiceInstances removes all service instances have a name containing
   215  // the cluster's infra ID.
   216  func (o *ClusterUninstaller) destroyServiceInstances() error {
   217  	firstPassList, err := o.listServiceInstances()
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	if len(firstPassList.list()) == 0 {
   223  		return nil
   224  	}
   225  
   226  	items := o.insertPendingItems(serviceInstanceTypeName, firstPassList.list())
   227  
   228  	ctx, cancel := o.contextWithTimeout()
   229  	defer cancel()
   230  
   231  	for _, item := range items {
   232  		select {
   233  		case <-ctx.Done():
   234  			o.Logger.Debugf("destroyServiceInstances: case <-ctx.Done()")
   235  			return o.Context.Err() // we're cancelled, abort
   236  		default:
   237  		}
   238  
   239  		backoff := wait.Backoff{
   240  			Duration: 15 * time.Second,
   241  			Factor:   1.1,
   242  			Cap:      leftInContext(ctx),
   243  			Steps:    math.MaxInt32}
   244  		err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
   245  			err2 := o.destroyServiceInstance(item)
   246  			if err2 == nil {
   247  				return true, err2
   248  			}
   249  			o.errorTracker.suppressWarning(item.key, err2, o.Logger)
   250  			return false, err2
   251  		})
   252  		if err != nil {
   253  			o.Logger.Fatal("destroyServiceInstances: ExponentialBackoffWithContext (destroy) returns ", err)
   254  		}
   255  	}
   256  
   257  	if items = o.getPendingItems(serviceInstanceTypeName); len(items) > 0 {
   258  		for _, item := range items {
   259  			o.Logger.Debugf("destroyServiceInstances: found %s in pending items", item.name)
   260  		}
   261  		return fmt.Errorf("destroyServiceInstances: %d undeleted items pending", len(items))
   262  	}
   263  
   264  	backoff := wait.Backoff{
   265  		Duration: 15 * time.Second,
   266  		Factor:   1.1,
   267  		Cap:      leftInContext(ctx),
   268  		Steps:    math.MaxInt32}
   269  	err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
   270  		secondPassList, err2 := o.listServiceInstances()
   271  		if err2 != nil {
   272  			return false, err2
   273  		}
   274  		if len(secondPassList) == 0 {
   275  			// We finally don't see any remaining instances!
   276  			return true, nil
   277  		}
   278  		for _, item := range secondPassList {
   279  			o.Logger.Debugf("destroyServiceInstances: found %s in second pass", item.name)
   280  		}
   281  		return false, nil
   282  	})
   283  	if err != nil {
   284  		o.Logger.Fatal("destroyServiceInstances: ExponentialBackoffWithContext (list) returns ", err)
   285  	}
   286  
   287  	return nil
   288  }