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 }