@@ -19,8 +19,10 @@ package openstack
1919
2020import (
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,110 @@ 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+
132+ providerAllocations , err := resourceproviders .GetAllocations (ctx , client , provider .UUID ).Extract ()
133+ if err != nil {
134+ return err
135+ }
136+
137+ // No allocations, we can delete the provider
138+ if providerAllocations == nil {
139+ err = resourceproviders .Delete (ctx , client , provider .UUID ).ExtractErr ()
140+ return fmt .Errorf ("failed to delete without cleanup due to %w" , err )
141+ }
142+
143+ // We have some allocations on the provider, let's see if we can delete them all
144+ allocations := len (providerAllocations .Allocations ) // Number of allocations on the provider
145+
146+ // It is a map of consumer-ids to their alloctions, we just go over their ids
147+ // to cross-check, what is stored for the consumer itself
148+ for consumerID := range providerAllocations .Allocations {
149+ // Allocations of the consumer mapped by the resource provider, so the
150+ // "reverse" of what we got before
151+ result := ListAllocations (ctx , client , consumerID )
152+ consumerAllocations , err := result .Extract ()
153+ if err != nil {
154+ return err
155+ }
156+
157+ // The consumer actually doesn't have *any* allocations, so it is just
158+ // inconsistent, and we can drop them all
159+ if len (consumerAllocations .Allocations ) == 0 {
160+ DeleteConsumerAllocations (ctx , client , consumerID )
161+ allocations -= 1 // That clears up one allocation
162+ } else {
163+ return fmt .Errorf ("cannot clean up provider, cannot handle non-empty consumer allocations" )
164+ }
165+ }
166+
167+ if allocations != 0 {
168+ return fmt .Errorf ("cannot clean up provider, still has some allocation" )
169+ }
170+
171+ // We are done, let's clean it up
172+ err = resourceproviders .Delete (ctx , client , provider .UUID ).ExtractErr ()
173+ if err != nil {
174+ return fmt .Errorf ("failed to delete after cleanup due to %w" , err )
175+ }
176+
177+ return nil
178+ }
0 commit comments