Skip to content

Commit b13fe4a

Browse files
committed
Cleanup placement during decomissioning
Usually, there should be no resources on the host left after having migrated all machines. But ocassionally, we are still left with some empty resources. This change removes all empty resources before removing the resource-provider as well.
1 parent c1abcd7 commit b13fe4a

2 files changed

Lines changed: 115 additions & 5 deletions

File tree

internal/controller/decomission_controller.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/gophercloud/gophercloud/v2"
3636
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors"
3737
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/services"
38+
"github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders"
3839
)
3940

4041
const (
@@ -43,8 +44,9 @@ const (
4344

4445
type NodeDecommissionReconciler struct {
4546
k8sclient.Client
46-
Scheme *runtime.Scheme
47-
computeClient *gophercloud.ServiceClient
47+
Scheme *runtime.Scheme
48+
computeClient *gophercloud.ServiceClient
49+
placementClient *gophercloud.ServiceClient
4850
}
4951

5052
// The counter-side in gardener is here:
@@ -126,11 +128,21 @@ func (r *NodeDecommissionReconciler) shutdownService(ctx context.Context, node *
126128

127129
// Deleting and evicted, so better delete the service
128130
err = services.Delete(ctx, r.computeClient, hypervisor.Service.ID).ExtractErr()
129-
if err == nil || gophercloud.ResponseCodeIs(err, http.StatusNotFound) {
130-
return r.removeFinalizer(ctx, node)
131+
if err != nil && !gophercloud.ResponseCodeIs(err, http.StatusNotFound) {
132+
return ctrl.Result{}, fmt.Errorf("cannot delete service due to %w", err)
133+
}
134+
135+
rp, err := resourceproviders.Get(ctx, r.placementClient, hypervisorID).Extract()
136+
if err != nil && !gophercloud.ResponseCodeIs(err, http.StatusNotFound) {
137+
return ctrl.Result{}, fmt.Errorf("cannot get resource provider due to %w", err)
138+
}
139+
140+
err = openstack.CleanupResourceProvider(ctx, r.placementClient, rp)
141+
if err != nil {
142+
return ctrl.Result{}, fmt.Errorf("cannot get resource provider due to %w", err)
131143
}
132144

133-
return ctrl.Result{}, err
145+
return r.removeFinalizer(ctx, node)
134146
}
135147

136148
func (r *NodeDecommissionReconciler) removeFinalizer(ctx context.Context, node *corev1.Node) (ctrl.Result, error) {
@@ -153,6 +165,12 @@ func (r *NodeDecommissionReconciler) SetupWithManager(mgr ctrl.Manager) error {
153165

154166
r.computeClient.Microversion = "2.93"
155167

168+
r.placementClient, err = openstack.GetServiceClient(ctx, "placement")
169+
if err != nil {
170+
return err
171+
}
172+
r.placementClient.Microversion = "1.39" // yoga, or later
173+
156174
return ctrl.NewControllerManagedBy(mgr).
157175
Named("nodeDecommission").
158176
For(&corev1.Node{}).

internal/openstack/placement.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ package openstack
1919

2020
import (
2121
"context"
22+
"fmt"
2223

2324
"github.com/gophercloud/gophercloud/v2"
25+
"github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders"
2426
)
2527

2628
// UpdateTraitsResult is the response of a Put traits operations. Call its Extract method
@@ -67,3 +69,93 @@ func UpdateTraits(ctx context.Context, client *gophercloud.ServiceClient, resour
6769
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
6870
return
6971
}
72+
73+
func getAllocationsURL(client *gophercloud.ServiceClient, consumerID string) string {
74+
return client.ServiceURL("allocations", consumerID)
75+
}
76+
77+
// ListAllocationsResult is the response of a Get allocations operations. Call its Extract method
78+
// to interpret it as a Allocations.
79+
type ListAllocationsResult struct {
80+
gophercloud.Result
81+
}
82+
83+
type ConsumerAllocations struct {
84+
Allocations map[string]resourceproviders.Allocation `json:"allocations"`
85+
ConsumerGeneration int `json:"consumer_generation"`
86+
ProjectID string `json:"project_id"`
87+
UserID string `json:"user_id"`
88+
ConsumerType string `json:"consumer_type"`
89+
}
90+
91+
// Extract interprets a ListAllocationsResult as a Allocations.
92+
func (r ListAllocationsResult) Extract() (*ConsumerAllocations, error) {
93+
var s ConsumerAllocations
94+
err := r.ExtractInto(&s)
95+
return &s, err
96+
}
97+
98+
// List Allocations for a certain consumer
99+
func ListAllocations(ctx context.Context, client *gophercloud.ServiceClient, consumerID string) (r ListAllocationsResult) {
100+
resp, err := client.Get(ctx, getAllocationsURL(client, consumerID), nil, &gophercloud.RequestOpts{
101+
OkCodes: []int{200},
102+
})
103+
if err != nil {
104+
r.Err = err
105+
return
106+
}
107+
108+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
109+
return
110+
}
111+
112+
// Delete all Allocations for a certain consumer
113+
func DeleteConsumerAllocations(ctx context.Context, client *gophercloud.ServiceClient, consumerID string) (r ListAllocationsResult) {
114+
resp, err := client.Delete(ctx, getAllocationsURL(client, consumerID), &gophercloud.RequestOpts{
115+
OkCodes: []int{204, 404},
116+
})
117+
if err != nil {
118+
r.Err = err
119+
return
120+
}
121+
122+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
123+
return
124+
}
125+
126+
// Remove all empty Allocations for a certain provider, and if it is empty, delete it
127+
func CleanupResourceProvider(ctx context.Context, client *gophercloud.ServiceClient, provider *resourceproviders.ResourceProvider) error {
128+
if provider == nil {
129+
return nil
130+
}
131+
result := resourceproviders.GetAllocations(ctx, client, provider.UUID)
132+
providerAllocations, err := result.Extract()
133+
if err != nil {
134+
return err
135+
}
136+
if providerAllocations == nil {
137+
return nil
138+
}
139+
allocations := len(providerAllocations.Allocations)
140+
for consumerID := range providerAllocations.Allocations {
141+
result := ListAllocations(ctx, client, consumerID)
142+
consumerAllocations, err := result.Extract()
143+
if err != nil {
144+
return err
145+
}
146+
if len(consumerAllocations.Allocations) == 0 {
147+
DeleteConsumerAllocations(ctx, client, consumerID)
148+
allocations -= 1
149+
continue
150+
}
151+
return fmt.Errorf("cannot clean up provider, still has some allocation")
152+
}
153+
154+
if allocations == 0 {
155+
result := resourceproviders.Delete(ctx, client, provider.UUID)
156+
if result.Err != nil {
157+
return result.Err
158+
}
159+
}
160+
return nil
161+
}

0 commit comments

Comments
 (0)