Skip to content

Commit f4677a6

Browse files
committed
add machineset migration e2e
1 parent 5c02314 commit f4677a6

File tree

6 files changed

+1481
-2
lines changed

6 files changed

+1481
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ unit:
4444

4545
.PHONY: e2e
4646
e2e:
47-
./hack/test.sh "./e2e/..." 30m
47+
./hack/test.sh "./e2e/..." 60m
4848

4949
# Run against the configured Kubernetes cluster in ~/.kube/config
5050
run:

e2e/framework/machine.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package framework
22

33
import (
4+
"context"
45
"fmt"
56

67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -54,3 +55,15 @@ func FilterRunningMachines(machines []*clusterv1.Machine) []*clusterv1.Machine {
5455

5556
return result
5657
}
58+
59+
// GetMachine get a machine by its name from the cluster API namespace.
60+
func GetMachine(cl client.Client, name string) (*clusterv1.Machine, error) {
61+
machine := &clusterv1.Machine{}
62+
key := client.ObjectKey{Namespace: CAPINamespace, Name: name}
63+
64+
if err := cl.Get(context.Background(), key, machine); err != nil {
65+
return nil, fmt.Errorf("error querying api for machine object: %w", err)
66+
}
67+
68+
return machine, nil
69+
}

e2e/framework/machineset.go

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
package framework
22

33
import (
4+
"context"
45
"fmt"
6+
"time"
57

68
. "github.com/onsi/ginkgo/v2"
79
. "github.com/onsi/gomega"
810
corev1 "k8s.io/api/core/v1"
911
apierrors "k8s.io/apimachinery/pkg/api/errors"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
"k8s.io/apimachinery/pkg/util/wait"
15+
"k8s.io/client-go/discovery"
16+
"k8s.io/client-go/dynamic"
17+
"k8s.io/client-go/rest"
18+
"k8s.io/client-go/scale"
19+
"k8s.io/klog"
1120
"k8s.io/utils/pointer"
1221
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1322
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
24+
"sigs.k8s.io/controller-runtime/pkg/client/config"
1425
)
1526

1627
type machineSetParams struct {
@@ -171,7 +182,9 @@ func GetMachineSet(cl client.Client, name string) (*clusterv1.MachineSet, error)
171182
machineSet := &clusterv1.MachineSet{}
172183
key := client.ObjectKey{Namespace: CAPINamespace, Name: name}
173184

174-
Expect(cl.Get(ctx, key, machineSet)).To(Succeed())
185+
if err := cl.Get(ctx, key, machineSet); err != nil {
186+
return nil, fmt.Errorf("error querying api for machineSet object: %w", err)
187+
}
175188

176189
return machineSet, nil
177190
}
@@ -190,3 +203,106 @@ func GetMachinesFromMachineSet(cl client.Client, machineSet *clusterv1.MachineSe
190203
}
191204
return machinesForSet, nil
192205
}
206+
207+
// GetLatestMachineFromMachineSet returns the new created machine by a given machineSet.
208+
func GetLatestMachineFromMachineSet(cl client.Client, machineSet *clusterv1.MachineSet) (*clusterv1.Machine, error) {
209+
machines, err := GetMachinesFromMachineSet(cl, machineSet)
210+
if err != nil {
211+
return nil, fmt.Errorf("error getting machines: %w", err)
212+
}
213+
214+
var machine *clusterv1.Machine
215+
216+
newest := time.Date(2020, 0, 1, 12, 0, 0, 0, time.UTC)
217+
218+
for key := range machines {
219+
time := machines[key].CreationTimestamp.Time
220+
if time.After(newest) {
221+
newest = time
222+
machine = machines[key]
223+
}
224+
}
225+
226+
return machine, nil
227+
}
228+
229+
// DeleteMachines deletes the specified machines and returns an error on failure.
230+
func DeleteMachines(cl client.Client, machines ...*clusterv1.Machine) error {
231+
return wait.PollUntilContextTimeout(ctx, RetryShort, time.Minute, true, func(ctx context.Context) (bool, error) {
232+
for _, machine := range machines {
233+
if err := cl.Delete(ctx, machine); err != nil {
234+
klog.Errorf("Error querying api for machine object %q: %v, retrying...", machine.Name, err)
235+
return false, err
236+
}
237+
}
238+
239+
return true, nil
240+
})
241+
}
242+
243+
// WaitForMachinesDeleted polls until the given Machines are not found.
244+
func WaitForMachinesDeleted(cl client.Client, machines ...*clusterv1.Machine) {
245+
Eventually(func() bool {
246+
for _, m := range machines {
247+
if err := cl.Get(context.Background(), client.ObjectKey{
248+
Name: m.GetName(),
249+
Namespace: m.GetNamespace(),
250+
}, &clusterv1.Machine{}); !apierrors.IsNotFound(err) {
251+
return false // Not deleted, or other error.
252+
}
253+
}
254+
255+
return true // Everything was deleted.
256+
}, WaitLong, RetryMedium).Should(BeTrue(), "error encountered while waiting for Machines to be deleted.")
257+
}
258+
259+
// ScaleMachineSet scales a machineSet with a given name to the given number of replicas.
260+
func ScaleMachineSet(name string, replicas int) error {
261+
scaleClient, err := getScaleClient()
262+
if err != nil {
263+
return fmt.Errorf("error calling getScaleClient %w", err)
264+
}
265+
266+
scale, err := scaleClient.Scales(CAPINamespace).Get(ctx, schema.GroupResource{Group: "cluster.x-k8s.io", Resource: "MachineSet"}, name, metav1.GetOptions{})
267+
if err != nil {
268+
return fmt.Errorf("error calling scaleClient.Scales get: %w", err)
269+
}
270+
271+
scaleUpdate := scale.DeepCopy()
272+
scaleUpdate.Spec.Replicas = int32(replicas)
273+
274+
_, err = scaleClient.Scales(CAPINamespace).Update(ctx, schema.GroupResource{Group: "cluster.x-k8s.io", Resource: "MachineSet"}, scaleUpdate, metav1.UpdateOptions{})
275+
if err != nil {
276+
return fmt.Errorf("error calling scaleClient.Scales update: %w", err)
277+
}
278+
279+
return nil
280+
}
281+
282+
// getScaleClient returns a ScalesGetter object to manipulate scale subresources.
283+
func getScaleClient() (scale.ScalesGetter, error) {
284+
cfg, err := config.GetConfig()
285+
if err != nil {
286+
return nil, fmt.Errorf("error getting config %w", err)
287+
}
288+
289+
httpClient, err := rest.HTTPClientFor(cfg)
290+
if err != nil {
291+
return nil, fmt.Errorf("error calling rest.HTTPClientFor %w", err)
292+
}
293+
294+
mapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient)
295+
if err != nil {
296+
return nil, fmt.Errorf("error calling NewDiscoveryRESTMapper %w", err)
297+
}
298+
299+
discovery := discovery.NewDiscoveryClientForConfigOrDie(cfg)
300+
scaleKindResolver := scale.NewDiscoveryScaleKindResolver(discovery)
301+
302+
scaleClient, err := scale.NewForConfig(cfg, mapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver)
303+
if err != nil {
304+
return nil, fmt.Errorf("error calling building scale client %w", err)
305+
}
306+
307+
return scaleClient, nil
308+
}

e2e/framework/machinetemplate.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package framework
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
apierrors "k8s.io/apimachinery/pkg/api/errors"
9+
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
10+
azurev1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
11+
gcpv1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
)
14+
15+
// GetAWSMachineTemplateByName gets awsMachineTemplate by its name from the default cluster API namespace.
16+
func GetAWSMachineTemplateByName(cl client.Client, name string) (*awsv1.AWSMachineTemplate, error) {
17+
var awsMachineTemplate = &awsv1.AWSMachineTemplate{}
18+
19+
key := client.ObjectKey{Namespace: CAPINamespace, Name: name}
20+
if err := cl.Get(ctx, key, awsMachineTemplate); err != nil {
21+
return nil, err
22+
}
23+
return awsMachineTemplate, nil
24+
}
25+
26+
// GetAWSMachineTemplateByPrefix gets awsMachineTemplate by its prefix from the default cluster API namespace.
27+
func GetAWSMachineTemplateByPrefix(cl client.Client, prefix string) (*awsv1.AWSMachineTemplate, error) {
28+
templateList := &awsv1.AWSMachineTemplateList{}
29+
if err := cl.List(ctx, templateList, client.InNamespace(CAPINamespace)); err != nil {
30+
return nil, fmt.Errorf("failed to list AWSMachineTemplates: %w", err)
31+
}
32+
33+
var matches []*awsv1.AWSMachineTemplate
34+
for i, t := range templateList.Items {
35+
if strings.HasPrefix(t.Name, prefix) {
36+
matches = append(matches, &templateList.Items[i])
37+
}
38+
}
39+
40+
switch len(matches) {
41+
case 0:
42+
return nil, fmt.Errorf("no AWSMachineTemplate found with prefix %q", prefix)
43+
case 1:
44+
return matches[0], nil
45+
default:
46+
return nil, fmt.Errorf("multiple AWSMachineTemplates found with prefix %q (%d matches)", prefix, len(matches))
47+
}
48+
}
49+
50+
// DeleteAWSMachineTemplateByPrefix deletes all AWSMachineTemplates with matching name prefix
51+
func DeleteAWSMachineTemplateByPrefix(cl client.Client, prefix string) error {
52+
templateList := &awsv1.AWSMachineTemplateList{}
53+
if err := cl.List(ctx, templateList, client.InNamespace(CAPINamespace)); err != nil {
54+
return fmt.Errorf("failed to list AWSMachineTemplates: %w", err)
55+
}
56+
57+
var deleteErrors []error
58+
var deletedCount int
59+
60+
for i := range templateList.Items {
61+
if strings.HasPrefix(templateList.Items[i].Name, prefix) {
62+
if err := cl.Delete(ctx, &templateList.Items[i]); err != nil {
63+
if !apierrors.IsNotFound(err) {
64+
deleteErrors = append(deleteErrors, fmt.Errorf("failed to delete %s: %w", templateList.Items[i].Name, err))
65+
}
66+
continue
67+
}
68+
deletedCount++
69+
}
70+
}
71+
72+
if len(deleteErrors) > 0 {
73+
return fmt.Errorf("deleted %d templates, but encountered %d errors: %v",
74+
deletedCount, len(deleteErrors), errors.Join(deleteErrors...))
75+
}
76+
77+
if deletedCount == 0 {
78+
return fmt.Errorf("no templates found with prefix %q", prefix)
79+
}
80+
81+
return nil
82+
}
83+
84+
// GetAzureMachineTemplate gets azureMachineTemplate by its name from the default cluster API namespace.
85+
func GetAzureMachineTemplate(cl client.Client, name string) (*azurev1.AzureMachineTemplate, error) {
86+
var azureMachineTemplate = &azurev1.AzureMachineTemplate{}
87+
88+
key := client.ObjectKey{Namespace: CAPINamespace, Name: name}
89+
if err := cl.Get(ctx, key, azureMachineTemplate); err != nil {
90+
return nil, err
91+
}
92+
return azureMachineTemplate, nil
93+
}
94+
95+
// GetGCPMachineTemplate gets gcpMachineTemplate by its name from the default cluster API namespace.
96+
func GetGCPMachineTemplate(cl client.Client, name string) (*gcpv1.GCPMachineTemplate, error) {
97+
var gcpMachineTemplate = &gcpv1.GCPMachineTemplate{}
98+
99+
key := client.ObjectKey{Namespace: CAPINamespace, Name: name}
100+
if err := cl.Get(ctx, key, gcpMachineTemplate); err != nil {
101+
return nil, err
102+
}
103+
return gcpMachineTemplate, nil
104+
}

e2e/framework/util.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package framework
22

33
import (
4+
"context"
45
"fmt"
56
"net/url"
67
"strconv"
78

89
configv1 "github.com/openshift/api/config/v1"
10+
"k8s.io/apimachinery/pkg/types"
911
"sigs.k8s.io/controller-runtime/pkg/client"
1012
)
1113

@@ -32,3 +34,21 @@ func GetControlPlaneHostAndPort(cl client.Client) (string, int32, error) {
3234

3335
return apiUrl.Hostname(), int32(port), nil
3436
}
37+
38+
// IsMachineAPIMigrationEnabled checks if the "MachineAPIMigration" feature is enabled via FeatureGate status
39+
func IsMachineAPIMigrationEnabled(ctx context.Context, cl client.Client) bool {
40+
featureGate := &configv1.FeatureGate{}
41+
if err := cl.Get(ctx, types.NamespacedName{Name: "cluster"}, featureGate); err != nil {
42+
return false
43+
}
44+
45+
for _, fg := range featureGate.Status.FeatureGates {
46+
for _, enabled := range fg.Enabled {
47+
if enabled.Name == "MachineAPIMigration" {
48+
return true
49+
}
50+
}
51+
}
52+
53+
return false
54+
}

0 commit comments

Comments
 (0)