Skip to content

Commit 93cdf0e

Browse files
committed
PowerVS: Cleanup service instances for destroy cluster
New code has been added to create a new PowerVS service instance during create cluster. Therefore, we need to clean it up during destroy cluster.
1 parent 9b5eed9 commit 93cdf0e

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

pkg/destroy/powervs/powervs.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ func (o *ClusterUninstaller) destroyCluster() error {
322322
}, {
323323
{name: "DNS Records", execute: o.destroyDNSRecords},
324324
{name: "DNS Resource Records", execute: o.destroyResourceRecords},
325+
}, {
326+
{name: "Service Instances", execute: o.destroyServiceInstances},
325327
}}
326328

327329
for _, stage := range stagedFuncs {
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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+
"github.com/pkg/errors"
14+
"k8s.io/apimachinery/pkg/util/wait"
15+
)
16+
17+
const (
18+
serviceInstanceTypeName = "service instance"
19+
20+
// resource ID for Power Systems Virtual Server in the Global catalog.
21+
virtualServerResourceID = "abd259f0-9990-11e8-acc8-b9f54a8f1661"
22+
)
23+
24+
// convertResourceGroupNameToID converts a resource group name/id to an id.
25+
func (o *ClusterUninstaller) convertResourceGroupNameToID(resourceGroupID string) (string, error) {
26+
listResourceGroupsOptions := o.managementSvc.NewListResourceGroupsOptions()
27+
28+
resourceGroups, _, err := o.managementSvc.ListResourceGroups(listResourceGroupsOptions)
29+
if err != nil {
30+
return "", err
31+
}
32+
33+
for _, resourceGroup := range resourceGroups.Resources {
34+
if *resourceGroup.Name == resourceGroupID {
35+
return *resourceGroup.ID, nil
36+
} else if *resourceGroup.ID == resourceGroupID {
37+
return resourceGroupID, nil
38+
}
39+
}
40+
41+
return "", errors.New(fmt.Sprintf("failed to find resource group %v", resourceGroupID))
42+
}
43+
44+
// listServiceInstances list service instances for the cluster.
45+
func (o *ClusterUninstaller) listServiceInstances() (cloudResources, error) {
46+
o.Logger.Debugf("Listing service instances")
47+
48+
ctx, cancel := o.contextWithTimeout()
49+
defer cancel()
50+
51+
select {
52+
case <-ctx.Done():
53+
o.Logger.Debugf("listServiceInstances: case <-ctx.Done()")
54+
return nil, o.Context.Err() // we're cancelled, abort
55+
default:
56+
}
57+
58+
var (
59+
resourceGroupID string
60+
options *resourcecontrollerv2.ListResourceInstancesOptions
61+
resources *resourcecontrollerv2.ResourceInstancesList
62+
err error
63+
perPage int64 = 10
64+
moreData = true
65+
nextURL *string
66+
)
67+
68+
resourceGroupID, err = o.convertResourceGroupNameToID(o.resourceGroupID)
69+
if err != nil {
70+
return nil, errors.Wrapf(err, "failed to convert resourceGroupID")
71+
}
72+
o.Logger.Debugf("listServiceInstances: converted %v to %v", o.resourceGroupID, resourceGroupID)
73+
74+
options = o.controllerSvc.NewListResourceInstancesOptions()
75+
// options.SetType("resource_instance")
76+
options.SetResourceGroupID(resourceGroupID)
77+
options.SetResourceID(virtualServerResourceID)
78+
options.SetLimit(perPage)
79+
80+
result := []cloudResource{}
81+
82+
for moreData {
83+
if options.Start != nil {
84+
o.Logger.Debugf("listServiceInstances: options = %+v, options.Limit = %v, options.Start = %v, options.ResourceGroupID = %v", options, *options.Limit, *options.Start, *options.ResourceGroupID)
85+
} else {
86+
o.Logger.Debugf("listServiceInstances: options = %+v, options.Limit = %v, options.ResourceGroupID = %v", options, *options.Limit, *options.ResourceGroupID)
87+
}
88+
89+
resources, _, err = o.controllerSvc.ListResourceInstancesWithContext(ctx, options)
90+
if err != nil {
91+
return nil, errors.Wrapf(err, "failed to list resource instances")
92+
}
93+
94+
o.Logger.Debugf("listServiceInstances: resources.RowsCount = %v", *resources.RowsCount)
95+
96+
for _, resource := range resources.Resources {
97+
var (
98+
getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions
99+
resourceInstance *resourcecontrollerv2.ResourceInstance
100+
response *core.DetailedResponse
101+
)
102+
103+
o.Logger.Debugf("listServiceInstances: resource.Name = %s", *resource.Name)
104+
105+
getResourceOptions = o.controllerSvc.NewGetResourceInstanceOptions(*resource.ID)
106+
107+
resourceInstance, response, err = o.controllerSvc.GetResourceInstance(getResourceOptions)
108+
if err != nil {
109+
return nil, errors.Wrapf(err, "failed to get instance: %s", response)
110+
}
111+
if response != nil && response.StatusCode == gohttp.StatusNotFound {
112+
o.Logger.Debugf("listServiceInstances: gohttp.StatusNotFound")
113+
continue
114+
} else if response != nil && response.StatusCode == gohttp.StatusInternalServerError {
115+
o.Logger.Debugf("listServiceInstances: gohttp.StatusInternalServerError")
116+
continue
117+
}
118+
119+
if resourceInstance.Type == nil {
120+
o.Logger.Debugf("listServiceInstances: type: nil")
121+
} else {
122+
o.Logger.Debugf("listServiceInstances: type: %v", *resourceInstance.Type)
123+
}
124+
125+
if resourceInstance.Type != nil && *resourceInstance.Type == "service_instance" {
126+
if strings.Contains(*resource.Name, o.InfraID) {
127+
result = append(result, cloudResource{
128+
key: *resource.ID,
129+
name: *resource.Name,
130+
status: "",
131+
typeName: serviceInstanceTypeName,
132+
id: *resource.ID,
133+
})
134+
}
135+
}
136+
}
137+
138+
// Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances
139+
nextURL, err = core.GetQueryParam(resources.NextURL, "start")
140+
if err != nil {
141+
return nil, errors.Wrapf(err, "failed to GetQueryParam on start: %s", err)
142+
}
143+
if nextURL == nil {
144+
o.Logger.Debugf("nextURL = nil")
145+
options.SetStart("")
146+
} else {
147+
o.Logger.Debugf("nextURL = %v", *nextURL)
148+
options.SetStart(*nextURL)
149+
}
150+
151+
moreData = *resources.RowsCount == perPage
152+
}
153+
154+
return cloudResources{}.insert(result...), nil
155+
}
156+
157+
// destroyServiceInstance destroys a service instance.
158+
func (o *ClusterUninstaller) destroyServiceInstance(item cloudResource) error {
159+
ctx, cancel := o.contextWithTimeout()
160+
defer cancel()
161+
162+
select {
163+
case <-ctx.Done():
164+
o.Logger.Debugf("destroyServiceInstance: case <-ctx.Done()")
165+
return o.Context.Err() // we're cancelled, abort
166+
default:
167+
}
168+
169+
o.Logger.Debugf("destroyServiceInstance: Preparing to delete, item.name = %v", item.name)
170+
171+
var (
172+
getOptions *resourcecontrollerv2.GetResourceInstanceOptions
173+
response *core.DetailedResponse
174+
err error
175+
)
176+
177+
getOptions = o.controllerSvc.NewGetResourceInstanceOptions(item.id)
178+
179+
_, response, err = o.controllerSvc.GetResourceInstanceWithContext(ctx, getOptions)
180+
181+
if err != nil && response != nil && response.StatusCode == gohttp.StatusNotFound {
182+
// The resource is gone
183+
o.deletePendingItems(item.typeName, []cloudResource{item})
184+
o.Logger.Infof("Deleted Service Instance %q", item.name)
185+
return nil
186+
}
187+
if err != nil && response != nil && response.StatusCode == gohttp.StatusInternalServerError {
188+
o.Logger.Infof("destroyServiceInstance: internal server error")
189+
return nil
190+
}
191+
192+
options := o.controllerSvc.NewDeleteResourceInstanceOptions(item.id)
193+
options.SetRecursive(true)
194+
195+
response, err = o.controllerSvc.DeleteResourceInstanceWithContext(ctx, options)
196+
197+
if err != nil && response != nil && response.StatusCode != gohttp.StatusNotFound {
198+
return errors.Wrapf(err, "failed to delete service instance %s", item.name)
199+
}
200+
201+
o.Logger.Infof("Deleted Service Instance %q", item.name)
202+
o.deletePendingItems(item.typeName, []cloudResource{item})
203+
204+
return nil
205+
}
206+
207+
// destroyServiceInstances removes all service instances have a name containing
208+
// the cluster's infra ID.
209+
func (o *ClusterUninstaller) destroyServiceInstances() error {
210+
firstPassList, err := o.listServiceInstances()
211+
if err != nil {
212+
return err
213+
}
214+
215+
if len(firstPassList.list()) == 0 {
216+
return nil
217+
}
218+
219+
items := o.insertPendingItems(serviceInstanceTypeName, firstPassList.list())
220+
221+
ctx, cancel := o.contextWithTimeout()
222+
defer cancel()
223+
224+
for _, item := range items {
225+
select {
226+
case <-ctx.Done():
227+
o.Logger.Debugf("destroyServiceInstances: case <-ctx.Done()")
228+
return o.Context.Err() // we're cancelled, abort
229+
default:
230+
}
231+
232+
backoff := wait.Backoff{
233+
Duration: 15 * time.Second,
234+
Factor: 1.1,
235+
Cap: leftInContext(ctx),
236+
Steps: math.MaxInt32}
237+
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
238+
err2 := o.destroyServiceInstance(item)
239+
if err2 == nil {
240+
return true, err2
241+
}
242+
o.errorTracker.suppressWarning(item.key, err2, o.Logger)
243+
return false, err2
244+
})
245+
if err != nil {
246+
o.Logger.Fatal("destroyServiceInstances: ExponentialBackoffWithContext (destroy) returns ", err)
247+
}
248+
}
249+
250+
if items = o.getPendingItems(serviceInstanceTypeName); len(items) > 0 {
251+
for _, item := range items {
252+
o.Logger.Debugf("destroyServiceInstances: found %s in pending items", item.name)
253+
}
254+
return errors.Errorf("destroyServiceInstances: %d undeleted items pending", len(items))
255+
}
256+
257+
backoff := wait.Backoff{
258+
Duration: 15 * time.Second,
259+
Factor: 1.1,
260+
Cap: leftInContext(ctx),
261+
Steps: math.MaxInt32}
262+
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
263+
secondPassList, err2 := o.listServiceInstances()
264+
if err2 != nil {
265+
return false, err2
266+
}
267+
if len(secondPassList) == 0 {
268+
// We finally don't see any remaining instances!
269+
return true, nil
270+
}
271+
for _, item := range secondPassList {
272+
o.Logger.Debugf("destroyServiceInstances: found %s in second pass", item.name)
273+
}
274+
return false, nil
275+
})
276+
if err != nil {
277+
o.Logger.Fatal("destroyServiceInstances: ExponentialBackoffWithContext (list) returns ", err)
278+
}
279+
280+
return nil
281+
}

0 commit comments

Comments
 (0)