@@ -18,32 +18,28 @@ package loadbalancers
1818
1919import (
2020 "context"
21+ "encoding/json"
2122
2223 "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
2324 "github.com/Azure/go-autorest/autorest"
25+ azureautorest "github.com/Azure/go-autorest/autorest/azure"
26+ "github.com/pkg/errors"
2427
28+ infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
2529 "sigs.k8s.io/cluster-api-provider-azure/azure"
30+ "sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
2631 "sigs.k8s.io/cluster-api-provider-azure/util/tele"
2732)
2833
29- // Client wraps go-sdk.
30- type Client interface {
31- Get (context.Context , string , string ) (network.LoadBalancer , error )
32- CreateOrUpdate (context.Context , string , string , network.LoadBalancer ) error
33- Delete (context.Context , string , string ) error
34- }
35-
36- // AzureClient contains the Azure go-sdk Client.
37- type AzureClient struct {
34+ // azureClient contains the Azure go-sdk Client.
35+ type azureClient struct {
3836 loadbalancers network.LoadBalancersClient
3937}
4038
41- var _ Client = & AzureClient {}
42-
43- // NewClient creates a new load balancer client from subscription ID.
44- func NewClient (auth azure.Authorizer ) * AzureClient {
39+ // newClient creates a new load balancer client from subscription ID.
40+ func newClient (auth azure.Authorizer ) * azureClient {
4541 c := newLoadBalancersClient (auth .SubscriptionID (), auth .BaseURI (), auth .Authorizer ())
46- return & AzureClient {c }
42+ return & azureClient {c }
4743}
4844
4945// newLoadbalancersClient creates a new load balancer client from subscription ID.
@@ -54,61 +50,129 @@ func newLoadBalancersClient(subscriptionID string, baseURI string, authorizer au
5450}
5551
5652// Get gets the specified load balancer.
57- func (ac * AzureClient ) Get (ctx context.Context , resourceGroupName , lbName string ) (network. LoadBalancer , error ) {
58- ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.AzureClient .Get" )
53+ func (ac * azureClient ) Get (ctx context.Context , spec azure. ResourceSpecGetter ) (result interface {}, err error ) {
54+ ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.azureClient .Get" )
5955 defer done ()
6056
61- return ac .loadbalancers .Get (ctx , resourceGroupName , lbName , "" )
57+ return ac .loadbalancers .Get (ctx , spec . ResourceGroupName (), spec . ResourceName () , "" )
6258}
6359
64- // CreateOrUpdate creates or updates a load balancer.
65- func (ac * AzureClient ) CreateOrUpdate (ctx context.Context , resourceGroupName string , lbName string , lb network.LoadBalancer ) error {
66- ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.AzureClient.CreateOrUpdate" )
60+ // CreateOrUpdateAsync creates or updates a load balancer asynchronously.
61+ // It sends a PUT request to Azure and if accepted without error, the func will return a Future which can be used to track the ongoing
62+ // progress of the operation.
63+ func (ac * azureClient ) CreateOrUpdateAsync (ctx context.Context , spec azure.ResourceSpecGetter , parameters interface {}) (result interface {}, future azureautorest.FutureAPI , err error ) {
64+ ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.azureClient.CreateOrUpdate" )
6765 defer done ()
6866
67+ loadBalancer , ok := parameters .(network.LoadBalancer )
68+ if ! ok {
69+ return nil , nil , errors .Errorf ("%T is not a network.LoadBalancer" , parameters )
70+ }
71+
6972 var etag string
70- if lb .Etag != nil {
71- etag = * lb .Etag
73+ if loadBalancer .Etag != nil {
74+ etag = * loadBalancer .Etag
7275 }
7376
74- req , err := ac .loadbalancers .CreateOrUpdatePreparer (ctx , resourceGroupName , lbName , lb )
77+ req , err := ac .loadbalancers .CreateOrUpdatePreparer (ctx , spec . ResourceGroupName (), spec . ResourceName (), loadBalancer )
7578 if err != nil {
7679 err = autorest .NewErrorWithError (err , "network.LoadBalancersClient" , "CreateOrUpdate" , nil , "Failure preparing request" )
77- return err
80+ return nil , nil , err
7881 }
7982
8083 if etag != "" {
8184 req .Header .Add ("If-Match" , etag )
8285 }
8386
84- future , err := ac .loadbalancers .CreateOrUpdateSender (req )
87+ createFuture , err := ac .loadbalancers .CreateOrUpdateSender (req )
8588 if err != nil {
86- err = autorest .NewErrorWithError (err , "network.LoadBalancersClient" , "CreateOrUpdate" , future .Response (), "Failure sending request" )
87- return err
89+ err = autorest .NewErrorWithError (err , "network.LoadBalancersClient" , "CreateOrUpdate" , createFuture .Response (), "Failure sending request" )
90+ return nil , nil , err
8891 }
8992
90- err = future .WaitForCompletionRef (ctx , ac .loadbalancers .Client )
93+ ctx , cancel := context .WithTimeout (ctx , reconciler .DefaultAzureCallTimeout )
94+ defer cancel ()
95+
96+ err = createFuture .WaitForCompletionRef (ctx , ac .loadbalancers .Client )
9197 if err != nil {
92- return err
98+ // if an error occurs, return the future.
99+ // this means the long-running operation didn't finish in the specified timeout.
100+ return nil , & createFuture , err
93101 }
94102
95- _ , err = future .Result (ac .loadbalancers )
96- return err
103+ result , err = createFuture .Result (ac .loadbalancers )
104+ // if the operation completed, return a nil future
105+ return result , nil , err
97106}
98107
99- // Delete deletes the specified load balancer.
100- func (ac * AzureClient ) Delete (ctx context.Context , resourceGroupName , lbName string ) error {
101- ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.AzureClient.Delete" )
108+ // DeleteAsync deletes a load balancer asynchronously. DeleteAsync sends a DELETE
109+ // request to Azure and if accepted without error, the func will return a Future which can be used to track the ongoing
110+ // progress of the operation.
111+ func (ac * azureClient ) DeleteAsync (ctx context.Context , spec azure.ResourceSpecGetter ) (future azureautorest.FutureAPI , err error ) {
112+ ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.azureClient.Delete" )
102113 defer done ()
103114
104- future , err := ac .loadbalancers .Delete (ctx , resourceGroupName , lbName )
115+ deleteFuture , err := ac .loadbalancers .Delete (ctx , spec . ResourceGroupName (), spec . ResourceName () )
105116 if err != nil {
106- return err
117+ return nil , err
107118 }
108- err = future .WaitForCompletionRef (ctx , ac .loadbalancers .Client )
119+
120+ ctx , cancel := context .WithTimeout (ctx , reconciler .DefaultAzureCallTimeout )
121+ defer cancel ()
122+
123+ err = deleteFuture .WaitForCompletionRef (ctx , ac .loadbalancers .Client )
124+ if err != nil {
125+ // if an error occurs, return the future.
126+ // this means the long-running operation didn't finish in the specified timeout.
127+ return & deleteFuture , err
128+ }
129+ _ , err = deleteFuture .Result (ac .loadbalancers )
130+ // if the operation completed, return a nil future.
131+ return nil , err
132+ }
133+
134+ // IsDone returns true if the long-running operation has completed.
135+ func (ac * azureClient ) IsDone (ctx context.Context , future azureautorest.FutureAPI ) (isDone bool , err error ) {
136+ ctx , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.azureClient.IsDone" )
137+ defer done ()
138+
139+ isDone , err = future .DoneWithContext (ctx , ac .loadbalancers )
109140 if err != nil {
110- return err
141+ return false , errors .Wrap (err , "failed checking if the operation was complete" )
142+ }
143+
144+ return isDone , nil
145+ }
146+
147+ // Result fetches the result of a long-running operation future.
148+ func (ac * azureClient ) Result (ctx context.Context , future azureautorest.FutureAPI , futureType string ) (result interface {}, err error ) {
149+ _ , _ , done := tele .StartSpanWithLogger (ctx , "loadbalancers.azureClient.Result" )
150+ defer done ()
151+
152+ if future == nil {
153+ return nil , errors .Errorf ("cannot get result from nil future" )
154+ }
155+
156+ switch futureType {
157+ case infrav1 .PutFuture :
158+ // Marshal and Unmarshal the future to put it into the correct future type so we can access the Result function.
159+ // Unfortunately the FutureAPI can't be casted directly to LoadBalancersCreateOrUpdateFuture because it is a azureautorest.Future, which doesn't implement the Result function. See PR #1686 for discussion on alternatives.
160+ // It was converted back to a generic azureautorest.Future from the CAPZ infrav1.Future type stored in Status: https://github.com/kubernetes-sigs/cluster-api-provider-azure/blob/main/azure/converters/futures.go#L49.
161+ var createFuture * network.LoadBalancersCreateOrUpdateFuture
162+ jsonData , err := future .MarshalJSON ()
163+ if err != nil {
164+ return nil , errors .Wrap (err , "failed to marshal future" )
165+ }
166+ if err := json .Unmarshal (jsonData , & createFuture ); err != nil {
167+ return nil , errors .Wrap (err , "failed to unmarshal future data" )
168+ }
169+ return (* createFuture ).Result (ac .loadbalancers )
170+
171+ case infrav1 .DeleteFuture :
172+ // Delete does not return a result load balancer
173+ return nil , nil
174+
175+ default :
176+ return nil , errors .Errorf ("unknown future type %q" , futureType )
111177 }
112- _ , err = future .Result (ac .loadbalancers )
113- return err
114178}
0 commit comments