Skip to content

Commit 13758dc

Browse files
mboersmak8s-infra-cherrypick-robot
authored andcommitted
Retry VMSS Flex validation if no parent MP is found
1 parent 835174a commit 13758dc

File tree

4 files changed

+191
-22
lines changed

4 files changed

+191
-22
lines changed

exp/api/v1beta1/azuremachinepool_webhook.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20-
"context"
2120
"fmt"
2221
"reflect"
2322

@@ -30,7 +29,7 @@ import (
3029
"k8s.io/apimachinery/pkg/util/validation/field"
3130
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
3231
"sigs.k8s.io/cluster-api-provider-azure/feature"
33-
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
32+
"sigs.k8s.io/cluster-api-provider-azure/util/azure"
3433
capifeature "sigs.k8s.io/cluster-api/feature"
3534
ctrl "sigs.k8s.io/controller-runtime"
3635
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -249,22 +248,15 @@ func (amp *AzureMachinePool) ValidateOrchestrationMode(c client.Client) func() e
249248
return func() error {
250249
// Only Flexible orchestration mode requires validation.
251250
if amp.Spec.OrchestrationMode == infrav1.OrchestrationModeType(compute.OrchestrationModeFlexible) {
252-
// Find the owner MachinePool
253-
ownerMachinePool := &expv1.MachinePool{}
254-
key := client.ObjectKey{
255-
Namespace: amp.Namespace,
256-
Name: amp.Name,
257-
}
258-
ctx := context.Background()
259-
if err := c.Get(ctx, key, ownerMachinePool); err != nil {
260-
return errors.Wrap(err, "failed to get owner MachinePool")
251+
parent, err := azure.FindParentMachinePoolWithRetry(amp.Name, c, 5)
252+
if err != nil {
253+
return errors.Wrap(err, "failed to find parent MachinePool")
261254
}
262-
263255
// Kubernetes must be >= 1.26.0 for cloud-provider-azure Helm chart support.
264-
if ownerMachinePool.Spec.Template.Spec.Version == nil {
256+
if parent.Spec.Template.Spec.Version == nil {
265257
return errors.New("could not find Kubernetes version in MachinePool")
266258
}
267-
k8sVersion, err := semver.ParseTolerant(*ownerMachinePool.Spec.Template.Spec.Version)
259+
k8sVersion, err := semver.ParseTolerant(*parent.Spec.Template.Spec.Version)
268260
if err != nil {
269261
return errors.Wrap(err, "failed to parse Kubernetes version")
270262
}

exp/api/v1beta1/azuremachinepool_webhook_test.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/Azure/go-autorest/autorest/to"
2828
guuid "github.com/google/uuid"
2929
. "github.com/onsi/gomega"
30+
"github.com/pkg/errors"
3031
"golang.org/x/crypto/ssh"
3132
"k8s.io/apimachinery/pkg/util/intstr"
3233
"k8s.io/apimachinery/pkg/util/uuid"
@@ -52,10 +53,11 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) {
5253
g := NewWithT(t)
5354

5455
tests := []struct {
55-
name string
56-
amp *AzureMachinePool
57-
version string
58-
wantErr bool
56+
name string
57+
amp *AzureMachinePool
58+
version string
59+
ownerNotFound bool
60+
wantErr bool
5961
}{
6062
{
6163
name: "valid",
@@ -201,10 +203,17 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) {
201203
version: "v1.25.6",
202204
wantErr: true,
203205
},
206+
{
207+
name: "azuremachinepool with Flexible orchestration mode and invalid Kubernetes version, no owner",
208+
amp: createMachinePoolWithOrchestrationMode(compute.OrchestrationModeFlexible),
209+
version: "v1.25.6",
210+
ownerNotFound: true,
211+
wantErr: true,
212+
},
204213
}
205214

206215
for _, tc := range tests {
207-
client := mockClient{Version: tc.version}
216+
client := mockClient{Version: tc.version, ReturnError: tc.ownerNotFound}
208217
t.Run(tc.name, func(t *testing.T) {
209218
err := tc.amp.ValidateCreate(client)
210219
if tc.wantErr {
@@ -218,11 +227,18 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) {
218227

219228
type mockClient struct {
220229
client.Client
221-
Version string
230+
Version string
231+
ReturnError bool
222232
}
223233

224-
func (m mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
225-
obj.(*expv1.MachinePool).Spec.Template.Spec.Version = &m.Version
234+
func (m mockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
235+
if m.ReturnError {
236+
return errors.New("MachinePool.cluster.x-k8s.io \"mock-machinepool-mp-0\" not found")
237+
}
238+
mp := &expv1.MachinePool{}
239+
mp.Spec.Template.Spec.Version = &m.Version
240+
list.(*expv1.MachinePoolList).Items = []expv1.MachinePool{*mp}
241+
226242
return nil
227243
}
228244

util/azure/azure.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package azure
1818

1919
import (
20+
"context"
2021
"os"
2122
"strings"
23+
"time"
2224

2325
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
2426
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
@@ -27,6 +29,9 @@ import (
2729
azureautorest "github.com/Azure/go-autorest/autorest/azure"
2830
"github.com/Azure/go-autorest/autorest/azure/auth"
2931
"github.com/jongio/azidext/go/azidext"
32+
"github.com/pkg/errors"
33+
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
34+
"sigs.k8s.io/controller-runtime/pkg/client"
3035
)
3136

3237
// AzureSystemNodeLabelPrefix is a standard node label prefix for Azure features, e.g., kubernetes.azure.com/scalesetpriority.
@@ -91,3 +96,33 @@ func GetAuthorizer(settings auth.EnvironmentSettings) (autorest.Authorizer, erro
9196
}
9297
return azidext.NewTokenCredentialAdapter(cred, []string{scope}), nil
9398
}
99+
100+
// FindParentMachinePool finds the parent MachinePool for the AzureMachinePool.
101+
func FindParentMachinePool(ampName string, cli client.Client) (*expv1.MachinePool, error) {
102+
ctx := context.Background()
103+
machinePoolList := &expv1.MachinePoolList{}
104+
if err := cli.List(ctx, machinePoolList); err != nil {
105+
return nil, errors.Wrapf(err, "failed to list MachinePools for %s", ampName)
106+
}
107+
for _, mp := range machinePoolList.Items {
108+
if mp.Spec.Template.Spec.InfrastructureRef.Name == ampName {
109+
return &mp, nil
110+
}
111+
}
112+
return nil, errors.Errorf("failed to get MachinePool for %s", ampName)
113+
}
114+
115+
// FindParentMachinePoolWithRetry finds the parent MachinePool for the AzureMachinePool with retry.
116+
func FindParentMachinePoolWithRetry(ampName string, cli client.Client, maxAttempts int) (*expv1.MachinePool, error) {
117+
for i := 1; ; i++ {
118+
p, err := FindParentMachinePool(ampName, cli)
119+
if err != nil {
120+
if i >= maxAttempts {
121+
return nil, errors.Wrap(err, "failed to find parent MachinePool")
122+
}
123+
time.Sleep(1 * time.Second)
124+
continue
125+
}
126+
return p, nil
127+
}
128+
}

util/azure/azure_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package azure
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
. "github.com/onsi/gomega"
24+
utilfeature "k8s.io/component-base/featuregate/testing"
25+
"sigs.k8s.io/cluster-api-provider-azure/feature"
26+
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
27+
capifeature "sigs.k8s.io/cluster-api/feature"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
)
30+
31+
func TestFindParentMachinePool(t *testing.T) {
32+
defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, capifeature.MachinePool, true)()
33+
g := NewWithT(t)
34+
client := mockClient{}
35+
36+
tests := []struct {
37+
name string
38+
mpName string
39+
wantErr bool
40+
}{
41+
{
42+
name: "valid",
43+
mpName: "mock-machinepool-mp-0",
44+
wantErr: false,
45+
},
46+
{
47+
name: "invalid",
48+
mpName: "mock-machinepool-mp-1",
49+
wantErr: true,
50+
},
51+
}
52+
53+
for _, tc := range tests {
54+
t.Run(tc.name, func(t *testing.T) {
55+
mp, err := FindParentMachinePool(tc.mpName, client)
56+
if tc.wantErr {
57+
g.Expect(err).To(HaveOccurred())
58+
} else {
59+
g.Expect(err).NotTo(HaveOccurred())
60+
g.Expect(mp).NotTo(BeNil())
61+
}
62+
})
63+
}
64+
}
65+
66+
func TestFindParentMachinePoolWithRetry(t *testing.T) {
67+
defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, capifeature.MachinePool, true)()
68+
g := NewWithT(t)
69+
client := mockClient{}
70+
71+
tests := []struct {
72+
name string
73+
mpName string
74+
maxAttempts int
75+
wantErr bool
76+
}{
77+
{
78+
name: "valid",
79+
mpName: "mock-machinepool-mp-0",
80+
maxAttempts: 1,
81+
wantErr: false,
82+
},
83+
{
84+
name: "valid with retries",
85+
mpName: "mock-machinepool-mp-0",
86+
maxAttempts: 5,
87+
wantErr: false,
88+
},
89+
{
90+
name: "invalid",
91+
mpName: "mock-machinepool-mp-1",
92+
maxAttempts: 1,
93+
wantErr: true,
94+
},
95+
{
96+
name: "invalid with retries",
97+
mpName: "mock-machinepool-mp-1",
98+
maxAttempts: 5,
99+
wantErr: true,
100+
},
101+
}
102+
103+
for _, tc := range tests {
104+
t.Run(tc.name, func(t *testing.T) {
105+
mp, err := FindParentMachinePoolWithRetry(tc.mpName, client, tc.maxAttempts)
106+
if tc.wantErr {
107+
g.Expect(err).To(HaveOccurred())
108+
} else {
109+
g.Expect(err).NotTo(HaveOccurred())
110+
g.Expect(mp).NotTo(BeNil())
111+
}
112+
})
113+
}
114+
}
115+
116+
type mockClient struct {
117+
client.Client
118+
}
119+
120+
func (m mockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
121+
mp := &expv1.MachinePool{}
122+
mp.Spec.Template.Spec.InfrastructureRef.Name = "mock-machinepool-mp-0"
123+
list.(*expv1.MachinePoolList).Items = []expv1.MachinePool{*mp}
124+
125+
return nil
126+
}

0 commit comments

Comments
 (0)