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 }