@@ -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,96 @@ 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+ // It is a map of consumer-ids to their alloctions, we just go over their ids
138+ // to cross-check, what is stored for the consumer itself
139+ for consumerID := range providerAllocations .Allocations {
140+ // Allocations of the consumer mapped by the resource provider, so the
141+ // "reverse" of what we got before
142+ result := ListAllocations (ctx , client , consumerID )
143+ consumerAllocations , err := result .Extract ()
144+ if err != nil {
145+ return err
146+ }
147+
148+ if len (consumerAllocations .Allocations ) > 0 {
149+ return fmt .Errorf ("cannot clean up provider, cannot handle non-empty consumer allocations" )
150+ }
151+
152+ // The consumer actually doesn't have *any* allocations, so it is just
153+ // inconsistent, and we can drop them all
154+ DeleteConsumerAllocations (ctx , client , consumerID )
155+ }
156+
157+ // We are done, let's clean it up
158+ err = resourceproviders .Delete (ctx , client , provider .UUID ).ExtractErr ()
159+ if err != nil {
160+ return fmt .Errorf ("failed to delete after cleanup due to %w" , err )
161+ }
162+
163+ return nil
164+ }
0 commit comments