github.com/openshift/installer@v1.4.17/pkg/destroy/gcp/instance.go (about)

     1  package gcp
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/pkg/errors"
     9  	"google.golang.org/api/compute/v1"
    10  	"google.golang.org/api/googleapi"
    11  
    12  	"github.com/openshift/installer/pkg/types/gcp"
    13  )
    14  
    15  // getInstanceNameAndZone extracts an instance and zone name from an instance URL in the form:
    16  // https://www.googleapis.com/compute/v1/projects/project-id/zones/us-central1-a/instances/instance-name
    17  // After splitting the service's base path with the work `/projects/`, you get:
    18  // project-id/zones/us-central1-a/instances/instance-name
    19  // TODO: Find a better way to get the instance name and zone to account for changes in base path
    20  func (o *ClusterUninstaller) getInstanceNameAndZone(instanceURL string) (string, string) {
    21  	path := strings.Split(instanceURL, "/projects/")[1]
    22  	parts := strings.Split(path, "/")
    23  	if len(parts) >= 5 {
    24  		return parts[4], parts[2]
    25  	}
    26  	return "", ""
    27  }
    28  
    29  func (o *ClusterUninstaller) listInstances(ctx context.Context) ([]cloudResource, error) {
    30  	byName, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType),nextPageToken", o.clusterIDFilter(), nil)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	byLabel, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType),nextPageToken", o.clusterLabelFilter(), nil)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	return append(byName, byLabel...), nil
    40  }
    41  
    42  // listInstancesWithFilter lists instances in the project that satisfy the filter criteria.
    43  // The fields parameter specifies which fields should be returned in the result, the filter string contains
    44  // a filter string passed to the API to filter results. The filterFunc is a client-side filtering function
    45  // that determines whether a particular result should be returned or not.
    46  func (o *ClusterUninstaller) listInstancesWithFilter(ctx context.Context, fields string, filter string, filterFunc func(*compute.Instance) bool) ([]cloudResource, error) {
    47  	o.Logger.Debugf("Listing compute instances")
    48  	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
    49  	defer cancel()
    50  	result := []cloudResource{}
    51  	req := o.computeSvc.Instances.AggregatedList(o.ProjectID).Fields(googleapi.Field(fields))
    52  	if len(filter) > 0 {
    53  		req = req.Filter(filter)
    54  	}
    55  	err := req.Pages(ctx, func(list *compute.InstanceAggregatedList) error {
    56  		for _, scopedList := range list.Items {
    57  			for _, item := range scopedList.Instances {
    58  				if filterFunc == nil || filterFunc != nil && filterFunc(item) {
    59  					zoneName := o.getZoneName(item.Zone)
    60  					o.Logger.Debugf("Found instance: %s in zone %s, status %s", item.Name, zoneName, item.Status)
    61  					result = append(result, cloudResource{
    62  						key:      fmt.Sprintf("%s/%s", zoneName, item.Name),
    63  						name:     item.Name,
    64  						status:   item.Status,
    65  						typeName: "instance",
    66  						zone:     zoneName,
    67  						quota: []gcp.QuotaUsage{{
    68  							Metric: &gcp.Metric{
    69  								Service: gcp.ServiceComputeEngineAPI,
    70  								Limit:   "cpus",
    71  								Dimensions: map[string]string{
    72  									"region": getRegionFromZone(zoneName),
    73  								},
    74  							},
    75  							Amount: o.cpusByMachineType[getNameFromURL("machineTypes", item.MachineType)],
    76  						}},
    77  					})
    78  				}
    79  			}
    80  		}
    81  		return nil
    82  	})
    83  	if err != nil {
    84  		return nil, errors.Wrapf(err, "failed to fetch compute instances")
    85  	}
    86  	return result, nil
    87  }
    88  
    89  func (o *ClusterUninstaller) deleteInstance(ctx context.Context, item cloudResource) error {
    90  	o.Logger.Debugf("Deleting compute instance %s in zone %s", item.name, item.zone)
    91  	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
    92  	defer cancel()
    93  	op, err := o.computeSvc.Instances.Delete(o.ProjectID, item.zone, item.name).RequestId(o.requestID(item.typeName, item.zone, item.name)).Context(ctx).Do()
    94  	if err != nil && !isNoOp(err) {
    95  		o.resetRequestID(item.typeName, item.zone, item.name)
    96  		return errors.Wrapf(err, "failed to delete instance %s in zone %s", item.name, item.zone)
    97  	}
    98  	if op != nil && op.Status == "DONE" && isErrorStatus(op.HttpErrorStatusCode) {
    99  		o.resetRequestID(item.typeName, item.zone, item.name)
   100  		return errors.Errorf("failed to delete instance %s in zone %s with error: %s", item.name, item.zone, operationErrorMessage(op))
   101  	}
   102  	if (err != nil && isNoOp(err)) || (op != nil && op.Status == "DONE") {
   103  		o.resetRequestID(item.typeName, item.name)
   104  		o.deletePendingItems(item.typeName, []cloudResource{item})
   105  		o.Logger.Infof("Deleted instance %s", item.name)
   106  	}
   107  	return nil
   108  }
   109  
   110  // destroyInstances searches for instances across all zones that have a name that starts with
   111  // with the cluster's infra ID.
   112  func (o *ClusterUninstaller) destroyInstances(ctx context.Context) error {
   113  	found, err := o.listInstances(ctx)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	items := o.insertPendingItems("instance", found)
   118  	errs := []error{}
   119  	for _, item := range items {
   120  		err := o.deleteInstance(ctx, item)
   121  		if err != nil {
   122  			errs = append(errs, err)
   123  		}
   124  	}
   125  	items = o.getPendingItems("instance")
   126  	return aggregateError(errs, len(items))
   127  }
   128  
   129  func (o *ClusterUninstaller) stopInstance(ctx context.Context, item cloudResource) error {
   130  	o.Logger.Debugf("Stopping compute instance %s in zone %s", item.name, item.zone)
   131  	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
   132  	defer cancel()
   133  	op, err := o.computeSvc.Instances.
   134  		Stop(o.ProjectID, item.zone, item.name).
   135  		RequestId(o.requestID("stopinstance", item.zone, item.name)).
   136  		DiscardLocalSsd(true).
   137  		Context(ctx).
   138  		Do()
   139  	if err != nil && !isNoOp(err) {
   140  		o.resetRequestID("stopinstance", item.zone, item.name)
   141  		return errors.Wrapf(err, "failed to stop instance %s in zone %s", item.name, item.zone)
   142  	}
   143  	if op != nil && op.Status == "DONE" && isErrorStatus(op.HttpErrorStatusCode) {
   144  		o.resetRequestID("stopinstance", item.zone, item.name)
   145  		return errors.Errorf("failed to stop instance %s in zone %s with error: %s", item.name, item.zone, operationErrorMessage(op))
   146  	}
   147  	if (err != nil && isNoOp(err)) || (op != nil && op.Status == "DONE") {
   148  		o.resetRequestID("stopinstance", item.name)
   149  		o.deletePendingItems("stopinstance", []cloudResource{item})
   150  		o.Logger.Infof("Stopped instance %s", item.name)
   151  	}
   152  	return nil
   153  }
   154  
   155  // stopComputeInstances searches for instances across all zones that have a name that starts with
   156  // the infra ID prefix and are not yet stopped. It then stops each instance found.
   157  func (o *ClusterUninstaller) stopInstances(ctx context.Context) error {
   158  	found, err := o.listInstances(ctx)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	for _, item := range found {
   163  		if item.status != "TERMINATED" {
   164  			// we record instance quota when we delete the instance, not when we terminate it
   165  			item.quota = nil
   166  			o.insertPendingItems("stopinstance", []cloudResource{item})
   167  		}
   168  	}
   169  	items := o.getPendingItems("stopinstance")
   170  	for _, item := range items {
   171  		err := o.stopInstance(ctx, item)
   172  		if err != nil {
   173  			o.errorTracker.suppressWarning(item.key, err, o.Logger)
   174  		}
   175  	}
   176  	if items = o.getPendingItems("stopinstance"); len(items) > 0 {
   177  		return errors.Errorf("%d items pending", len(items))
   178  	}
   179  	return nil
   180  }