Skip to content

Commit a03b9e2

Browse files
Cecile Robert-Michonshysank
andcommitted
Make vnets reconcile/delete async
Co-Authored-By: Shyam Sankaran <[email protected]>
1 parent b5c7a99 commit a03b9e2

File tree

10 files changed

+476
-572
lines changed

10 files changed

+476
-572
lines changed

azure/scope/cluster.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"sigs.k8s.io/cluster-api-provider-azure/azure/services/loadbalancers"
4141
"sigs.k8s.io/cluster-api-provider-azure/azure/services/natgateways"
4242
"sigs.k8s.io/cluster-api-provider-azure/azure/services/routetables"
43+
"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualnetworks"
4344
"sigs.k8s.io/cluster-api-provider-azure/azure/services/vnetpeerings"
4445
"sigs.k8s.io/cluster-api-provider-azure/util/futures"
4546
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
@@ -362,11 +363,14 @@ func (s *ClusterScope) VnetPeeringSpecs() []azure.ResourceSpecGetter {
362363
}
363364

364365
// VNetSpec returns the virtual network spec.
365-
func (s *ClusterScope) VNetSpec() azure.VNetSpec {
366-
return azure.VNetSpec{
367-
ResourceGroup: s.Vnet().ResourceGroup,
368-
Name: s.Vnet().Name,
369-
CIDRs: s.Vnet().CIDRBlocks,
366+
func (s *ClusterScope) VNetSpec() azure.ResourceSpecGetter {
367+
return &virtualnetworks.VNetSpec{
368+
ResourceGroup: s.Vnet().ResourceGroup,
369+
Name: s.Vnet().Name,
370+
CIDRs: s.Vnet().CIDRBlocks,
371+
Location: s.Location(),
372+
ClusterName: s.ClusterName(),
373+
AdditionalTags: s.AdditionalTags(),
370374
}
371375
}
372376

@@ -662,6 +666,7 @@ func (s *ClusterScope) PatchObject(ctx context.Context) error {
662666
infrav1.NATGatewaysReadyCondition,
663667
infrav1.LoadBalancersReadyCondition,
664668
infrav1.BastionHostReadyCondition,
669+
infrav1.VNetReadyCondition,
665670
),
666671
)
667672

@@ -678,6 +683,7 @@ func (s *ClusterScope) PatchObject(ctx context.Context) error {
678683
infrav1.NATGatewaysReadyCondition,
679684
infrav1.LoadBalancersReadyCondition,
680685
infrav1.BastionHostReadyCondition,
686+
infrav1.VNetReadyCondition,
681687
}})
682688
}
683689

azure/scope/managedcontrolplane.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
4242
"sigs.k8s.io/cluster-api-provider-azure/azure"
4343
"sigs.k8s.io/cluster-api-provider-azure/azure/services/groups"
44+
"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualnetworks"
4445
infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
4546
"sigs.k8s.io/cluster-api-provider-azure/util/futures"
4647
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
@@ -226,11 +227,14 @@ func (s *ManagedControlPlaneScope) GroupSpec() azure.ResourceSpecGetter {
226227
}
227228

228229
// VNetSpec returns the virtual network spec.
229-
func (s *ManagedControlPlaneScope) VNetSpec() azure.VNetSpec {
230-
return azure.VNetSpec{
231-
ResourceGroup: s.Vnet().ResourceGroup,
232-
Name: s.Vnet().Name,
233-
CIDRs: s.Vnet().CIDRBlocks,
230+
func (s *ManagedControlPlaneScope) VNetSpec() azure.ResourceSpecGetter {
231+
return &virtualnetworks.VNetSpec{
232+
ResourceGroup: s.Vnet().ResourceGroup,
233+
Name: s.Vnet().Name,
234+
CIDRs: s.Vnet().CIDRBlocks,
235+
Location: s.Location(),
236+
ClusterName: s.ClusterName(),
237+
AdditionalTags: s.AdditionalTags(),
234238
}
235239
}
236240

azure/services/virtualnetworks/client.go

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,35 @@ package virtualnetworks
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"
27+
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
2428

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.VirtualNetwork, error)
32-
CreateOrUpdate(context.Context, string, string, network.VirtualNetwork) error
33-
Delete(context.Context, string, string) error
34-
CheckIPAddressAvailability(context.Context, string, string, string) (network.IPAddressAvailabilityResult, error)
34+
// Getter is an interface that can get a virtual network.
35+
type Getter interface {
36+
Get(ctx context.Context, spec azure.ResourceSpecGetter) (result interface{}, err error)
3537
}
3638

37-
// AzureClient contains the Azure go-sdk Client.
38-
type AzureClient struct {
39+
// azureClient contains the Azure go-sdk Client.
40+
type azureClient struct {
3941
virtualnetworks network.VirtualNetworksClient
4042
}
4143

42-
var _ Client = &AzureClient{}
44+
var _ Getter = &azureClient{}
4345

44-
// NewClient creates a new VM client from subscription ID.
45-
func NewClient(auth azure.Authorizer) *AzureClient {
46+
// newClient creates a new VM client from subscription ID.
47+
func newClient(auth azure.Authorizer) *azureClient {
4648
c := newVirtualNetworksClient(auth.SubscriptionID(), auth.BaseURI(), auth.Authorizer())
47-
return &AzureClient{
49+
return &azureClient{
4850
virtualnetworks: c,
4951
}
5052
}
@@ -56,52 +58,113 @@ func newVirtualNetworksClient(subscriptionID string, baseURI string, authorizer
5658
return vnetsClient
5759
}
5860

59-
// Get gets the specified virtual network by resource group.
60-
func (ac *AzureClient) Get(ctx context.Context, resourceGroupName, vnetName string) (network.VirtualNetwork, error) {
61-
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.AzureClient.Get")
61+
// Get gets the specified virtual network.
62+
func (ac *azureClient) Get(ctx context.Context, spec azure.ResourceSpecGetter) (result interface{}, err error) {
63+
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.azureClient.Get")
6264
defer done()
6365

64-
return ac.virtualnetworks.Get(ctx, resourceGroupName, vnetName, "")
66+
return ac.virtualnetworks.Get(ctx, spec.ResourceGroupName(), spec.ResourceName(), "")
6567
}
6668

67-
// CreateOrUpdate creates or updates a virtual network in the specified resource group.
68-
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, vnetName string, vn network.VirtualNetwork) error {
69-
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.AzureClient.CreateOrUpdate")
69+
// CreateOrUpdateAsync creates or updates a virtual network in the specified resource group asynchronously.
70+
// 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
71+
// progress of the operation.
72+
func (ac *azureClient) CreateOrUpdateAsync(ctx context.Context, spec azure.ResourceSpecGetter, parameters interface{}) (result interface{}, future azureautorest.FutureAPI, err error) {
73+
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.azureClient.CreateOrUpdateAsync")
7074
defer done()
7175

72-
future, err := ac.virtualnetworks.CreateOrUpdate(ctx, resourceGroupName, vnetName, vn)
76+
vn, ok := parameters.(network.VirtualNetwork)
77+
if !ok {
78+
return nil, nil, errors.Errorf("%T is not a network.VirtualNetwork", parameters)
79+
}
80+
81+
createFuture, err := ac.virtualnetworks.CreateOrUpdate(ctx, spec.ResourceGroupName(), spec.ResourceName(), vn)
7382
if err != nil {
74-
return err
83+
return nil, nil, err
7584
}
76-
err = future.WaitForCompletionRef(ctx, ac.virtualnetworks.Client)
85+
86+
ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureCallTimeout)
87+
defer cancel()
88+
89+
err = createFuture.WaitForCompletionRef(ctx, ac.virtualnetworks.Client)
7790
if err != nil {
78-
return err
91+
// if an error occurs, return the future.
92+
// this means the long-running operation didn't finish in the specified timeout.
93+
return nil, &createFuture, err
7994
}
80-
_, err = future.Result(ac.virtualnetworks)
81-
return err
95+
result, err = createFuture.Result(ac.virtualnetworks)
96+
// if the operation completed, return a nil future.
97+
return result, nil, err
8298
}
8399

84-
// Delete deletes the specified virtual network.
85-
func (ac *AzureClient) Delete(ctx context.Context, resourceGroupName, vnetName string) error {
86-
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.AzureClient.Delete")
100+
// DeleteAsync deletes a virtual network asynchronously. DeleteAsync sends a DELETE
101+
// request to Azure and if accepted without error, the func will return a Future which can be used to track the ongoing
102+
// progress of the operation.
103+
func (ac *azureClient) DeleteAsync(ctx context.Context, spec azure.ResourceSpecGetter) (future azureautorest.FutureAPI, err error) {
104+
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.azureClient.DeleteAsync")
87105
defer done()
88106

89-
future, err := ac.virtualnetworks.Delete(ctx, resourceGroupName, vnetName)
107+
deleteFuture, err := ac.virtualnetworks.Delete(ctx, spec.ResourceGroupName(), spec.ResourceName())
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureCallTimeout)
113+
defer cancel()
114+
115+
err = deleteFuture.WaitForCompletionRef(ctx, ac.virtualnetworks.Client)
90116
if err != nil {
91-
return err
117+
// if an error occurs, return the future.
118+
// this means the long-running operation didn't finish in the specified timeout.
119+
return &deleteFuture, err
92120
}
93-
err = future.WaitForCompletionRef(ctx, ac.virtualnetworks.Client)
121+
_, err = deleteFuture.Result(ac.virtualnetworks)
122+
// if the operation completed, return a nil future.
123+
return nil, err
124+
}
125+
126+
// IsDone returns true if the long-running operation has completed.
127+
func (ac *azureClient) IsDone(ctx context.Context, future azureautorest.FutureAPI) (isDone bool, err error) {
128+
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.azureClient.IsDone")
129+
defer done()
130+
131+
isDone, err = future.DoneWithContext(ctx, ac.virtualnetworks)
94132
if err != nil {
95-
return err
133+
return false, errors.Wrap(err, "failed checking if the operation was complete")
96134
}
97-
_, err = future.Result(ac.virtualnetworks)
98-
return err
135+
136+
return isDone, nil
99137
}
100138

101-
// CheckIPAddressAvailability checks whether a private IP address is available for use.
102-
func (ac *AzureClient) CheckIPAddressAvailability(ctx context.Context, resourceGroupName, vnetName, ip string) (network.IPAddressAvailabilityResult, error) {
103-
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.AzureClient.CheckIPAddressAvailability")
139+
// Result fetches the result of a long-running operation future.
140+
func (ac *azureClient) Result(ctx context.Context, future azureautorest.FutureAPI, futureType string) (result interface{}, err error) {
141+
_, _, done := tele.StartSpanWithLogger(ctx, "virtualnetworks.azureClient.Result")
104142
defer done()
105143

106-
return ac.virtualnetworks.CheckIPAddressAvailability(ctx, resourceGroupName, vnetName, ip)
144+
if future == nil {
145+
return nil, errors.Errorf("cannot get result from nil future")
146+
}
147+
148+
switch futureType {
149+
case infrav1.PutFuture:
150+
// Marshal and Unmarshal the future to put it into the correct future type so we can access the Result function.
151+
// Unfortunately the FutureAPI can't be casted directly to VirtualNetworksCreateOrUpdateFuture because it is a azureautorest.Future, which doesn't implement the Result function. See PR #1686 for discussion on alternatives.
152+
// 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.
153+
var createFuture *network.VirtualNetworksCreateOrUpdateFuture
154+
jsonData, err := future.MarshalJSON()
155+
if err != nil {
156+
return nil, errors.Wrap(err, "failed to marshal future")
157+
}
158+
if err := json.Unmarshal(jsonData, &createFuture); err != nil {
159+
return nil, errors.Wrap(err, "failed to unmarshal future data")
160+
}
161+
return (*createFuture).Result(ac.virtualnetworks)
162+
163+
case infrav1.DeleteFuture:
164+
// Delete does not return a result vnet.
165+
return nil, nil
166+
167+
default:
168+
return nil, errors.Errorf("unknown future type %q", futureType)
169+
}
107170
}

azure/services/virtualnetworks/mock_virtualnetworks/client_mock.go

Lines changed: 17 additions & 60 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

azure/services/virtualnetworks/mock_virtualnetworks/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ limitations under the License.
1515
*/
1616

1717
// Run go generate to regenerate this mock.
18-
//go:generate ../../../../hack/tools/bin/mockgen -destination client_mock.go -package mock_virtualnetworks -source ../client.go Client
18+
//go:generate ../../../../hack/tools/bin/mockgen -destination client_mock.go -package mock_virtualnetworks -source ../client.go Getter
1919
//go:generate ../../../../hack/tools/bin/mockgen -destination virtualnetworks_mock.go -package mock_virtualnetworks -source ../virtualnetworks.go VNetScope
2020
//go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt client_mock.go > _client_mock.go && mv _client_mock.go client_mock.go"
2121
//go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt virtualnetworks_mock.go > _virtualnetworks_mock.go && mv _virtualnetworks_mock.go virtualnetworks_mock.go"

0 commit comments

Comments
 (0)