Skip to content

Commit 4305550

Browse files
authored
Merge pull request #1886 from Jont828/async-loadbalancers
Make load balancer reconcile/delete async
2 parents 8f3d3bf + e958b3d commit 4305550

File tree

10 files changed

+1057
-860
lines changed

10 files changed

+1057
-860
lines changed

azure/scope/cluster.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
3737
"sigs.k8s.io/cluster-api-provider-azure/azure"
3838
"sigs.k8s.io/cluster-api-provider-azure/azure/services/groups"
39+
"sigs.k8s.io/cluster-api-provider-azure/azure/services/loadbalancers"
3940
"sigs.k8s.io/cluster-api-provider-azure/azure/services/natgateways"
4041
"sigs.k8s.io/cluster-api-provider-azure/azure/services/routetables"
4142
"sigs.k8s.io/cluster-api-provider-azure/azure/services/vnetpeerings"
@@ -165,11 +166,17 @@ func (s *ClusterScope) PublicIPSpecs() []azure.PublicIPSpec {
165166
}
166167

167168
// LBSpecs returns the load balancer specs.
168-
func (s *ClusterScope) LBSpecs() []azure.LBSpec {
169-
specs := []azure.LBSpec{
170-
{
169+
func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter {
170+
specs := []azure.ResourceSpecGetter{
171+
&loadbalancers.LBSpec{
171172
// API Server LB
172173
Name: s.APIServerLB().Name,
174+
ResourceGroup: s.ResourceGroup(),
175+
SubscriptionID: s.SubscriptionID(),
176+
ClusterName: s.ClusterName(),
177+
Location: s.Location(),
178+
VNetName: s.Vnet().Name,
179+
VNetResourceGroup: s.Vnet().ResourceGroup,
173180
SubnetName: s.ControlPlaneSubnet().Name,
174181
FrontendIPConfigs: s.APIServerLB().FrontendIPs,
175182
APIServerPort: s.APIServerPort(),
@@ -178,32 +185,47 @@ func (s *ClusterScope) LBSpecs() []azure.LBSpec {
178185
Role: infrav1.APIServerRole,
179186
BackendPoolName: s.APIServerLBPoolName(s.APIServerLB().Name),
180187
IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes,
188+
AdditionalTags: s.AdditionalTags(),
181189
},
182190
}
183191

184192
// Node outbound LB
185193
if s.NodeOutboundLB() != nil {
186-
specs = append(specs, azure.LBSpec{
194+
specs = append(specs, &loadbalancers.LBSpec{
187195
Name: s.NodeOutboundLBName(),
196+
ResourceGroup: s.ResourceGroup(),
197+
SubscriptionID: s.SubscriptionID(),
198+
ClusterName: s.ClusterName(),
199+
Location: s.Location(),
200+
VNetName: s.Vnet().Name,
201+
VNetResourceGroup: s.Vnet().ResourceGroup,
188202
FrontendIPConfigs: s.NodeOutboundLB().FrontendIPs,
189203
Type: s.NodeOutboundLB().Type,
190204
SKU: s.NodeOutboundLB().SKU,
191205
BackendPoolName: s.OutboundPoolName(s.NodeOutboundLBName()),
192206
IdleTimeoutInMinutes: s.NodeOutboundLB().IdleTimeoutInMinutes,
193207
Role: infrav1.NodeOutboundRole,
208+
AdditionalTags: s.AdditionalTags(),
194209
})
195210
}
196211

197212
// Control Plane Outbound LB
198213
if s.ControlPlaneOutboundLB() != nil {
199-
specs = append(specs, azure.LBSpec{
214+
specs = append(specs, &loadbalancers.LBSpec{
200215
Name: s.ControlPlaneOutboundLB().Name,
216+
ResourceGroup: s.ResourceGroup(),
217+
SubscriptionID: s.SubscriptionID(),
218+
ClusterName: s.ClusterName(),
219+
Location: s.Location(),
220+
VNetName: s.Vnet().Name,
221+
VNetResourceGroup: s.Vnet().ResourceGroup,
201222
FrontendIPConfigs: s.ControlPlaneOutboundLB().FrontendIPs,
202223
Type: s.ControlPlaneOutboundLB().Type,
203224
SKU: s.ControlPlaneOutboundLB().SKU,
204225
BackendPoolName: s.OutboundPoolName(azure.GenerateControlPlaneOutboundLBName(s.ClusterName())),
205226
IdleTimeoutInMinutes: s.NodeOutboundLB().IdleTimeoutInMinutes,
206227
Role: infrav1.ControlPlaneOutboundRole,
228+
AdditionalTags: s.AdditionalTags(),
207229
})
208230
}
209231

@@ -623,6 +645,7 @@ func (s *ClusterScope) PatchObject(ctx context.Context) error {
623645
infrav1.VnetPeeringReadyCondition,
624646
infrav1.DisksReadyCondition,
625647
infrav1.NATGatewaysReadyCondition,
648+
infrav1.LoadBalancersReadyCondition,
626649
),
627650
)
628651

@@ -637,6 +660,7 @@ func (s *ClusterScope) PatchObject(ctx context.Context) error {
637660
infrav1.VnetPeeringReadyCondition,
638661
infrav1.DisksReadyCondition,
639662
infrav1.NATGatewaysReadyCondition,
663+
infrav1.LoadBalancersReadyCondition,
640664
}})
641665
}
642666

azure/services/loadbalancers/client.go

Lines changed: 104 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,28 @@ package loadbalancers
1818

1919
import (
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

Comments
 (0)