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

     1  package powervs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/IBM/go-sdk-core/v5/core"
    12  	// https://github.com/IBM/platform-services-go-sdk/blob/v0.18.16/resourcecontrollerv2/resource_controller_v2.go
    13  	"github.com/IBM/platform-services-go-sdk/resourcecontrollerv2"
    14  	"k8s.io/apimachinery/pkg/util/wait"
    15  )
    16  
    17  const cosTypeName = "cos instance"
    18  
    19  // $ ibmcloud catalog service cloud-object-storage --output json | jq -r '.[].id'
    20  // dff97f5c-bc5e-4455-b470-411c3edbe49c.
    21  const cosResourceID = "dff97f5c-bc5e-4455-b470-411c3edbe49c"
    22  
    23  // listCOSInstances lists COS service instances.
    24  // ibmcloud resource service-instances --output JSON --service-name cloud-object-storage | jq -r '.[] | select(.name|test("rdr-hamzy.*")) | "\(.name) - \(.id)"'
    25  func (o *ClusterUninstaller) listCOSInstances() (cloudResources, error) {
    26  	o.Logger.Debugf("Listing COS instances")
    27  
    28  	ctx, cancel := o.contextWithTimeout()
    29  	defer cancel()
    30  
    31  	var (
    32  		// https://github.com/IBM/platform-services-go-sdk/blob/main/resourcecontrollerv2/resource_controller_v2.go#L3086
    33  		options *resourcecontrollerv2.ListResourceInstancesOptions
    34  
    35  		perPage int64 = 64
    36  
    37  		// https://github.com/IBM/platform-services-go-sdk/blob/main/resourcecontrollerv2/resource_controller_v2.go#L4525-L4534
    38  		resources *resourcecontrollerv2.ResourceInstancesList
    39  
    40  		err error
    41  
    42  		foundOne = false
    43  		moreData = true
    44  	)
    45  
    46  	options = o.controllerSvc.NewListResourceInstancesOptions()
    47  	options.Limit = &perPage
    48  	options.SetResourceID(cosResourceID)
    49  	options.SetType("service_instance")
    50  
    51  	result := []cloudResource{}
    52  
    53  	for moreData {
    54  		// https://github.com/IBM/platform-services-go-sdk/blob/main/resourcecontrollerv2/resource_controller_v2.go#L173
    55  		resources, _, err = o.controllerSvc.ListResourceInstancesWithContext(ctx, options)
    56  		if err != nil {
    57  			return nil, fmt.Errorf("failed to list COS instances: %w", err)
    58  		}
    59  		o.Logger.Debugf("listCOSInstances: RowsCount %v", *resources.RowsCount)
    60  
    61  		for _, instance := range resources.Resources {
    62  			// Match the COS instances created by both the installer and the
    63  			// cluster-image-registry-operator.
    64  			if strings.Contains(*instance.Name, o.InfraID) {
    65  				if !(strings.HasSuffix(*instance.Name, "-cos") ||
    66  					strings.HasSuffix(*instance.Name, "-image-registry")) {
    67  					continue
    68  				}
    69  				foundOne = true
    70  				o.Logger.Debugf("listCOSInstances: FOUND %s %s", *instance.Name, *instance.GUID)
    71  				result = append(result, cloudResource{
    72  					key:      *instance.ID,
    73  					name:     *instance.Name,
    74  					status:   *instance.State,
    75  					typeName: cosTypeName,
    76  					id:       *instance.GUID,
    77  				})
    78  			}
    79  		}
    80  
    81  		if resources.NextURL != nil {
    82  			start, err := resources.GetNextStart()
    83  			if err != nil {
    84  				o.Logger.Debugf("listCOSInstances: err = %v", err)
    85  				return nil, fmt.Errorf("failed to GetNextStart: %w", err)
    86  			}
    87  			if start != nil {
    88  				o.Logger.Debugf("listCOSInstances: start = %v", *start)
    89  				options.SetStart(*start)
    90  			}
    91  		} else {
    92  			o.Logger.Debugf("listCOSInstances: NextURL = nil")
    93  			moreData = false
    94  		}
    95  	}
    96  	if !foundOne {
    97  		options = o.controllerSvc.NewListResourceInstancesOptions()
    98  		options.Limit = &perPage
    99  		options.SetResourceID(cosResourceID)
   100  		options.SetType("service_instance")
   101  
   102  		moreData = true
   103  		for moreData {
   104  			// https://github.com/IBM/platform-services-go-sdk/blob/main/resourcecontrollerv2/resource_controller_v2.go#L173
   105  			resources, _, err = o.controllerSvc.ListResourceInstancesWithContext(ctx, options)
   106  			if err != nil {
   107  				return nil, fmt.Errorf("failed to list COS instances: %w", err)
   108  			}
   109  			o.Logger.Debugf("listCOSInstances: RowsCount %v", *resources.RowsCount)
   110  			if resources.NextURL != nil {
   111  				o.Logger.Debugf("listCOSInstances: NextURL   %v", *resources.NextURL)
   112  			}
   113  
   114  			o.Logger.Debugf("listCOSInstances: NO matching COS instance against: %s", o.InfraID)
   115  			for _, instance := range resources.Resources {
   116  				o.Logger.Debugf("listCOSInstances: only found COS instance: %s", *instance.Name)
   117  			}
   118  
   119  			if resources.NextURL != nil {
   120  				start, err := resources.GetNextStart()
   121  				if err != nil {
   122  					o.Logger.Debugf("listCOSInstances: err = %v", err)
   123  					return nil, fmt.Errorf("failed to GetNextStart: %w", err)
   124  				}
   125  				if start != nil {
   126  					o.Logger.Debugf("listCOSInstances: start = %v", *start)
   127  					options.SetStart(*start)
   128  				}
   129  			} else {
   130  				o.Logger.Debugf("listCOSInstances: NextURL = nil")
   131  				moreData = false
   132  			}
   133  		}
   134  	}
   135  
   136  	return cloudResources{}.insert(result...), nil
   137  }
   138  
   139  func (o *ClusterUninstaller) findReclaimedCOSInstance(item cloudResource) (*resourcecontrollerv2.ResourceInstance, *resourcecontrollerv2.Reclamation) {
   140  	var getReclamationOptions *resourcecontrollerv2.ListReclamationsOptions
   141  	var reclamations *resourcecontrollerv2.ReclamationsList
   142  	var response *core.DetailedResponse
   143  	var err error
   144  	var reclamation resourcecontrollerv2.Reclamation
   145  	var getInstanceOptions *resourcecontrollerv2.GetResourceInstanceOptions
   146  	var cosInstance *resourcecontrollerv2.ResourceInstance
   147  
   148  	getReclamationOptions = o.controllerSvc.NewListReclamationsOptions()
   149  
   150  	ctx, cancel := o.contextWithTimeout()
   151  	defer cancel()
   152  
   153  	reclamations, response, err = o.controllerSvc.ListReclamationsWithContext(ctx, getReclamationOptions)
   154  	if err != nil {
   155  		o.Logger.Debugf("Error: ListReclamationsWithContext: %v, response is %v", err, response)
   156  		return nil, nil
   157  	}
   158  
   159  	// ibmcloud resource reclamations --output json
   160  	for _, reclamation = range reclamations.Resources {
   161  		getInstanceOptions = o.controllerSvc.NewGetResourceInstanceOptions(*reclamation.ResourceInstanceID)
   162  
   163  		cosInstance, response, err = o.controllerSvc.GetResourceInstanceWithContext(ctx, getInstanceOptions)
   164  		if err != nil {
   165  			o.Logger.Debugf("Error: GetResourceInstanceWithContext: %v", err)
   166  			return nil, nil
   167  		}
   168  
   169  		if *cosInstance.Name == item.name {
   170  			return cosInstance, &reclamation
   171  		}
   172  	}
   173  
   174  	return nil, nil
   175  }
   176  
   177  func (o *ClusterUninstaller) destroyCOSInstance(item cloudResource) error {
   178  	var cosInstance *resourcecontrollerv2.ResourceInstance
   179  
   180  	cosInstance, _ = o.findReclaimedCOSInstance(item)
   181  	if cosInstance != nil {
   182  		// The resource is gone
   183  		o.deletePendingItems(item.typeName, []cloudResource{item})
   184  		o.Logger.Infof("Deleted COS Instance %q", item.name)
   185  		return nil
   186  	}
   187  
   188  	var getOptions *resourcecontrollerv2.GetResourceInstanceOptions
   189  	var response *core.DetailedResponse
   190  	var err error
   191  
   192  	getOptions = o.controllerSvc.NewGetResourceInstanceOptions(item.id)
   193  
   194  	ctx, cancel := o.contextWithTimeout()
   195  	defer cancel()
   196  
   197  	_, response, err = o.controllerSvc.GetResourceInstanceWithContext(ctx, getOptions)
   198  
   199  	if err != nil && response != nil && response.StatusCode == http.StatusNotFound {
   200  		// The resource is gone
   201  		o.deletePendingItems(item.typeName, []cloudResource{item})
   202  		o.Logger.Infof("Deleted COS Instance %q", item.name)
   203  		return nil
   204  	}
   205  	if err != nil && response != nil && response.StatusCode == http.StatusInternalServerError {
   206  		o.Logger.Infof("destroyCOSInstance: internal server error")
   207  		return nil
   208  	}
   209  
   210  	options := o.controllerSvc.NewDeleteResourceInstanceOptions(item.id)
   211  	options.SetRecursive(true)
   212  
   213  	response, err = o.controllerSvc.DeleteResourceInstanceWithContext(ctx, options)
   214  
   215  	if err != nil && response != nil && response.StatusCode != http.StatusNotFound {
   216  		return fmt.Errorf("failed to delete COS instance %s: %w", item.name, err)
   217  	}
   218  
   219  	var reclamation *resourcecontrollerv2.Reclamation
   220  
   221  	cosInstance, reclamation = o.findReclaimedCOSInstance(item)
   222  	if cosInstance != nil {
   223  		var reclamationActionOptions *resourcecontrollerv2.RunReclamationActionOptions
   224  
   225  		reclamationActionOptions = o.controllerSvc.NewRunReclamationActionOptions(*reclamation.ID, "reclaim")
   226  
   227  		_, response, err = o.controllerSvc.RunReclamationActionWithContext(ctx, reclamationActionOptions)
   228  		if err != nil {
   229  			return fmt.Errorf("failed RunReclamationActionWithContext: %w", err)
   230  		}
   231  	}
   232  
   233  	o.Logger.Infof("Deleted COS Instance %q", item.name)
   234  	o.deletePendingItems(item.typeName, []cloudResource{item})
   235  
   236  	return nil
   237  }
   238  
   239  // destroyCOSInstances removes the COS service instance resources that have a
   240  // name prefixed with the cluster's infra ID.
   241  func (o *ClusterUninstaller) destroyCOSInstances() error {
   242  	firstPassList, err := o.listCOSInstances()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	if len(firstPassList.list()) == 0 {
   248  		return nil
   249  	}
   250  
   251  	items := o.insertPendingItems(cosTypeName, firstPassList.list())
   252  
   253  	ctx, cancel := o.contextWithTimeout()
   254  	defer cancel()
   255  
   256  	for _, item := range items {
   257  		select {
   258  		case <-ctx.Done():
   259  			o.Logger.Debugf("destroyCOSInstances: case <-ctx.Done()")
   260  			return o.Context.Err() // we're cancelled, abort
   261  		default:
   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  			err2 := o.destroyCOSInstance(item)
   271  			if err2 == nil {
   272  				return true, err2
   273  			}
   274  			o.errorTracker.suppressWarning(item.key, err2, o.Logger)
   275  			return false, err2
   276  		})
   277  		if err != nil {
   278  			o.Logger.Fatal("destroyCOSInstances: ExponentialBackoffWithContext (destroy) returns ", err)
   279  		}
   280  	}
   281  
   282  	if items = o.getPendingItems(cosTypeName); len(items) > 0 {
   283  		for _, item := range items {
   284  			o.Logger.Debugf("destroyCOSInstances: found %s in pending items", item.name)
   285  		}
   286  		return fmt.Errorf("destroyCOSInstances: %d undeleted items pending", len(items))
   287  	}
   288  
   289  	backoff := wait.Backoff{
   290  		Duration: 15 * time.Second,
   291  		Factor:   1.1,
   292  		Cap:      leftInContext(ctx),
   293  		Steps:    math.MaxInt32}
   294  	err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
   295  		secondPassList, err2 := o.listCOSInstances()
   296  		if err2 != nil {
   297  			return false, err2
   298  		}
   299  		if len(secondPassList) == 0 {
   300  			// We finally don't see any remaining instances!
   301  			return true, nil
   302  		}
   303  		for _, item := range secondPassList {
   304  			o.Logger.Debugf("destroyCOSInstances: found %s in second pass", item.name)
   305  		}
   306  		return false, nil
   307  	})
   308  	if err != nil {
   309  		o.Logger.Fatal("destroyCOSInstances: ExponentialBackoffWithContext (list) returns ", err)
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  // COSInstanceID returns the ID of the Cloud Object Storage service instance
   316  // created by the installer during installation.
   317  func (o *ClusterUninstaller) COSInstanceID() (string, error) {
   318  	if o.cosInstanceID != "" {
   319  		return o.cosInstanceID, nil
   320  	}
   321  	cosInstances, err := o.listCOSInstances()
   322  	if err != nil {
   323  		return "", err
   324  	}
   325  	instanceList := cosInstances.list()
   326  	if len(instanceList) == 0 {
   327  		return "", fmt.Errorf("COS instance not found")
   328  	}
   329  
   330  	// Locate the installer's COS instance by name.
   331  	for _, instance := range instanceList {
   332  		if instance.name == fmt.Sprintf("%s-cos", o.InfraID) {
   333  			o.cosInstanceID = instance.id
   334  			return instance.id, nil
   335  		}
   336  	}
   337  	return "", fmt.Errorf("COS instance not found")
   338  }