Skip to content

Commit a3f5b44

Browse files
Merge pull request openshift#8699 from tsmetana/filestore-destroy-again
STOR-1353: Attempt to cleanup GCP Filestore instances on destroy
2 parents 30e01b0 + 98c16f9 commit a3f5b44

File tree

6 files changed

+8091
-1
lines changed

6 files changed

+8091
-1
lines changed

pkg/asset/installconfig/gcp/validation.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,8 @@ func ValidateEnabledServices(ctx context.Context, client API, project string) er
444444
"servicemanagement.googleapis.com",
445445
"deploymentmanager.googleapis.com",
446446
"storage-api.googleapis.com",
447-
"storage-component.googleapis.com")
447+
"storage-component.googleapis.com",
448+
"file.googleapis.com")
448449
projectServices, err := client.GetEnabledServices(ctx, project)
449450
if err != nil {
450451
if IsForbidden(err) {

pkg/destroy/gcp/filestore.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package gcp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"google.golang.org/api/file/v1"
8+
9+
gcpconsts "github.com/openshift/installer/pkg/constants/gcp"
10+
)
11+
12+
func (o *ClusterUninstaller) filestoreParentPath() string {
13+
return fmt.Sprintf("projects/%s/locations/-", o.ProjectID)
14+
}
15+
16+
func (o *ClusterUninstaller) clusterFilestoreLabelFilter() string {
17+
return fmt.Sprintf("labels.%s = \"owned\"", fmt.Sprintf(gcpconsts.ClusterIDLabelFmt, o.ClusterID))
18+
}
19+
20+
func (o *ClusterUninstaller) listFilestores(ctx context.Context) ([]cloudResource, error) {
21+
o.Logger.Debug("Listing filestores")
22+
result := []cloudResource{}
23+
24+
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
25+
defer cancel()
26+
instSvc := file.NewProjectsLocationsInstancesService(o.fileSvc)
27+
lCall := instSvc.List(o.filestoreParentPath()).Context(ctx).Filter(o.clusterFilestoreLabelFilter())
28+
29+
nextPageToken := "pageToken"
30+
for nextPageToken != "" {
31+
instances, err := lCall.Do()
32+
if err != nil {
33+
return nil, fmt.Errorf("error retrieving filestore instances: %w", err)
34+
}
35+
36+
for _, activeInstance := range instances.Instances {
37+
o.Logger.Debugf("Found filestore %s", activeInstance.Name)
38+
result = append(result, cloudResource{
39+
name: activeInstance.Name,
40+
typeName: "filestore",
41+
})
42+
}
43+
44+
nextPageToken = instances.NextPageToken
45+
lCall.PageToken(nextPageToken)
46+
}
47+
return result, nil
48+
}
49+
50+
func (o *ClusterUninstaller) deleteFilestore(ctx context.Context, item cloudResource) error {
51+
o.Logger.Debugf("Deleting filestore %s", item.name)
52+
53+
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
54+
defer cancel()
55+
instSvc := file.NewProjectsLocationsInstancesService(o.fileSvc)
56+
_, err := instSvc.Delete(item.name).Context(ctx).Do()
57+
if err != nil && isForbidden(err) {
58+
o.deletePendingItems(item.typeName, []cloudResource{item})
59+
return fmt.Errorf("insufficient permissions to delete filestore %s", item.name)
60+
}
61+
if err != nil && !isNoOp(err) {
62+
o.resetRequestID(item.typeName, item.name)
63+
return fmt.Errorf("failed to delete filestore %s", item.name)
64+
}
65+
if err != nil && isNoOp(err) {
66+
o.resetRequestID(item.typeName, item.name)
67+
o.deletePendingItems(item.typeName, []cloudResource{item})
68+
o.Logger.Infof("Deleted filestore %s", item.name)
69+
}
70+
71+
return nil
72+
}
73+
74+
func (o *ClusterUninstaller) destroyFilestores(ctx context.Context) error {
75+
found, err := o.listFilestores(ctx)
76+
if err != nil {
77+
if isForbidden(err) {
78+
o.Logger.Warning("Skipping deletion of filestores: insufficient Filestore API permissions or API disabled")
79+
return nil
80+
}
81+
return err
82+
}
83+
items := o.insertPendingItems("filestore", found)
84+
for _, item := range items {
85+
err := o.deleteFilestore(ctx, item)
86+
if err != nil {
87+
o.errorTracker.suppressWarning(item.key, err, o.Logger)
88+
}
89+
}
90+
if items = o.getPendingItems("filestore"); len(items) > 0 {
91+
return fmt.Errorf("%d items pending", len(items))
92+
}
93+
94+
return nil
95+
}

pkg/destroy/gcp/gcp.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
resourcemanager "google.golang.org/api/cloudresourcemanager/v3"
1414
"google.golang.org/api/compute/v1"
1515
"google.golang.org/api/dns/v1"
16+
"google.golang.org/api/file/v1"
1617
"google.golang.org/api/googleapi"
1718
"google.golang.org/api/iam/v1"
1819
"google.golang.org/api/option"
@@ -63,6 +64,7 @@ type ClusterUninstaller struct {
6364
dnsSvc *dns.Service
6465
storageSvc *storage.Service
6566
rmSvc *resourcemanager.Service
67+
fileSvc *file.Service
6668

6769
// cpusByMachineType caches the number of CPUs per machine type, used in quota
6870
// calculations on deletion
@@ -147,6 +149,11 @@ func (o *ClusterUninstaller) Run() (*types.ClusterQuota, error) {
147149
return nil, errors.Wrap(err, "failed to create resourcemanager service")
148150
}
149151

152+
o.fileSvc, err = file.NewService(ctx, options...)
153+
if err != nil {
154+
return nil, fmt.Errorf("failed to create filestore service: %w", err)
155+
}
156+
150157
err = wait.PollImmediateInfinite(
151158
time.Second*10,
152159
o.destroyCluster,
@@ -187,6 +194,7 @@ func (o *ClusterUninstaller) destroyCluster() (bool, error) {
187194
{name: "Routers", execute: o.destroyRouters},
188195
{name: "Subnetworks", execute: o.destroySubnetworks},
189196
{name: "Networks", execute: o.destroyNetworks},
197+
{name: "Filestores", execute: o.destroyFilestores},
190198
}}
191199

192200
// create the main Context, so all stages can accept and make context children
@@ -259,6 +267,18 @@ func (o *ClusterUninstaller) clusterLabelOrClusterIDFilter() string {
259267
return fmt.Sprintf("(%s) OR (%s)", o.clusterIDFilter(), o.clusterLabelFilter())
260268
}
261269

270+
func isForbidden(err error) bool {
271+
if err == nil {
272+
return false
273+
}
274+
var ae *googleapi.Error
275+
if errors.As(err, &ae) {
276+
return ae.Code == http.StatusForbidden
277+
}
278+
279+
return false
280+
}
281+
262282
func isNoOp(err error) bool {
263283
if err == nil {
264284
return false

0 commit comments

Comments
 (0)