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 }