Skip to content

Commit 24bfad5

Browse files
committed
worker node selection with capabilities
* added function for finding best machine image (based on AWS GEP-33 implementation) * added machineImage.Image logic to ported GEP-33 functions, if machineImage.Image is defined and no imageIDs are specified for each region, the machineImage.Image must be used for selection. refactor machine_test.go: * use describeTableSubTree to test capabilities and legacy clpf * use describeTableSubTree to test if imageName is used instead of imageID and vice versa * replace setup function with BeforeEach
1 parent 3298c32 commit 24bfad5

File tree

5 files changed

+547
-128
lines changed

5 files changed

+547
-128
lines changed

pkg/apis/openstack/helper/helper.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ func FindImageInCloudProfile(
118118

119119
capabilitySet, err := findMachineImageFlavor(machineImages, name, version, region, arch, machineCapabilities, capabilityDefinitions)
120120
if err != nil {
121-
return nil, fmt.Errorf("could not find an AMI for region %q, image %q, version %q that supports %v: %w", region, name, version, machineCapabilities, err)
121+
return nil, fmt.Errorf("could not find an Image ID for region %q, image %q, version %q that supports %v: %w", region, name, version, machineCapabilities, err)
122122
}
123123

124-
if capabilitySet != nil && len(capabilitySet.Regions) > 0 && capabilitySet.Regions[0].ID != "" {
124+
// TODO: what if imageName is used?
125+
if capabilitySet != nil && len(capabilitySet.Regions) > 0 && (capabilitySet.Regions[0].ID != "" || capabilitySet.Image != "") {
125126
return capabilitySet, nil
126127
}
127-
return nil, fmt.Errorf("could not find an AMI for region %q, image %q, version %q that supports %v", region, name, version, machineCapabilities)
128+
return nil, fmt.Errorf("could not find an Image ID for region %q, image %q, version %q that supports %v", region, name, version, machineCapabilities)
128129
}
129130

130131
// FindImageInWorkerStatus takes a list of machine images from the worker status and tries to find the first entry
@@ -173,10 +174,12 @@ func findMachineImageFlavor(
173174
continue
174175
}
175176

177+
// No capability definitions means legacy format, so we match Architecture field of region
176178
if len(capabilityDefinitions) == 0 {
177179
for _, mapping := range version.Regions {
178180
if region == mapping.Name && ptr.Equal(arch, mapping.Architecture) {
179181
return &api.MachineImageFlavor{
182+
Image: version.Image,
180183
Regions: []api.RegionIDMapping{mapping},
181184
Capabilities: gardencorev1beta1.Capabilities{},
182185
}, nil
@@ -185,6 +188,7 @@ func findMachineImageFlavor(
185188
continue
186189
}
187190

191+
// TODO VR: filteredCapabilityFlavors.Image is empty. We need to set it here if it exists
188192
filteredCapabilityFlavors := filterCapabilityFlavorsByRegion(version.CapabilityFlavors, region)
189193
bestMatch, err := worker.FindBestImageFlavor(filteredCapabilityFlavors, machineCapabilities, capabilityDefinitions)
190194
if err != nil {
@@ -212,6 +216,7 @@ func filterCapabilityFlavorsByRegion(capabilityFlavors []api.MachineImageFlavor,
212216
if regionIDMapping != nil {
213217
compatibleFlavors = append(compatibleFlavors, &api.MachineImageFlavor{
214218
Regions: []api.RegionIDMapping{*regionIDMapping},
219+
Image: capabilityFlavor.Image,
215220
Capabilities: capabilityFlavor.Capabilities,
216221
})
217222
}

pkg/apis/openstack/helper/helper_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package helper_test
66

77
import (
8+
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
89
. "github.com/onsi/ginkgo/v2"
910
. "github.com/onsi/gomega"
1011
"k8s.io/utils/ptr"

pkg/controller/worker/machine_images.go

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import (
99
"fmt"
1010

1111
"github.com/gardener/gardener/extensions/pkg/controller/worker"
12+
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
1213
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
13-
"k8s.io/utils/ptr"
14+
gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
1415
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
1516

1617
api "github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack"
@@ -38,10 +39,33 @@ func (w *workerDelegate) UpdateMachineImagesStatus(ctx context.Context) error {
3839
return nil
3940
}
4041

41-
func (w *workerDelegate) findMachineImage(name, version, architecture string) (*api.MachineImage, error) {
42-
image, err := helper.FindImageFromCloudProfile(w.cloudProfileConfig, name, version, w.cluster.Shoot.Spec.Region, architecture)
43-
if err == nil {
44-
return image, nil
42+
func (w *workerDelegate) selectMachineImageForWorkerPool(name, version string, region string, arch *string, machineCapabilities gardencorev1beta1.Capabilities) (*api.MachineImage, error) {
43+
selectedMachineImage := &api.MachineImage{
44+
Name: name,
45+
Version: version,
46+
}
47+
48+
if capabilitySet, err := helper.FindImageInCloudProfile(w.cloudProfileConfig, name, version, region, arch, machineCapabilities, w.cluster.CloudProfile.Spec.MachineCapabilities); err == nil {
49+
selectedMachineImage.Capabilities = capabilitySet.Capabilities
50+
// ID takes precedence over Image attribute. Reminder: Image is global name while ID is region specific.
51+
if capabilitySet.Regions[0].ID == "" {
52+
selectedMachineImage.Image = capabilitySet.Image
53+
} else {
54+
selectedMachineImage.ID = capabilitySet.Regions[0].ID
55+
}
56+
57+
if len(selectedMachineImage.Capabilities[v1beta1constants.ArchitectureName]) > 0 {
58+
var selectedArch *string
59+
selectedArch = &selectedMachineImage.Capabilities[v1beta1constants.ArchitectureName][0]
60+
// Verify that selectedMachineImage has correct architecture
61+
if selectedArch == nil || arch != nil && *selectedArch != *arch {
62+
return nil, fmt.Errorf("architecture does not match for machine image")
63+
}
64+
} else {
65+
selectedMachineImage.Architecture = capabilitySet.Regions[0].Architecture
66+
}
67+
68+
return selectedMachineImage, nil
4569
}
4670

4771
// Try to look up machine image in worker provider status as it was not found in componentconfig.
@@ -51,27 +75,46 @@ func (w *workerDelegate) findMachineImage(name, version, architecture string) (*
5175
return nil, fmt.Errorf("could not decode worker status of worker '%s': %w", k8sclient.ObjectKeyFromObject(w.worker), err)
5276
}
5377

54-
machineImage, err := helper.FindMachineImage(workerStatus.MachineImages, name, version, architecture)
55-
if err != nil {
56-
return nil, worker.ErrorMachineImageNotFound(name, version)
57-
}
78+
return helper.FindImageInWorkerStatus(workerStatus.MachineImages, name, version, arch, machineCapabilities, w.cluster.CloudProfile.Spec.MachineCapabilities)
79+
}
5880

59-
// The architecture field might not be present in the WorkerStatus if the Shoot has been created before introduction
60-
// of the field. Hence, initialize it if it's empty.
61-
machineImage = machineImage.DeepCopy()
62-
if machineImage.Architecture == nil {
63-
machineImage.Architecture = &architecture
64-
}
81+
return nil, worker.ErrorMachineImageNotFound(name, version, *arch, region)
82+
}
6583

66-
return machineImage, nil
84+
func appendMachineImage(machineImages []api.MachineImage, machineImage api.MachineImage, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition) []api.MachineImage {
85+
// support for cloudprofile machine images without capabilities
86+
if len(capabilityDefinitions) == 0 {
87+
for _, image := range machineImages {
88+
if image.Name == machineImage.Name && image.Version == machineImage.Version && machineImage.Architecture == image.Architecture {
89+
// If the image already exists without capabilities, we can just return the existing list.
90+
return machineImages
91+
}
92+
}
93+
return append(machineImages, api.MachineImage{
94+
Name: machineImage.Name,
95+
Version: machineImage.Version,
96+
ID: machineImage.ID,
97+
Architecture: machineImage.Architecture,
98+
})
6799
}
68100

69-
return nil, worker.ErrorMachineImageNotFound(name, version)
70-
}
101+
defaultedCapabilities := gardencorev1beta1.GetCapabilitiesWithAppliedDefaults(machineImage.Capabilities, capabilityDefinitions)
71102

72-
func appendMachineImage(machineImages []api.MachineImage, machineImage api.MachineImage) []api.MachineImage {
73-
if _, err := helper.FindMachineImage(machineImages, machineImage.Name, machineImage.Version, ptr.Deref(machineImage.Architecture, v1beta1constants.ArchitectureAMD64)); err != nil {
74-
return append(machineImages, machineImage)
103+
for _, existingMachineImage := range machineImages {
104+
existingDefaultedCapabilities := gardencorev1beta1.GetCapabilitiesWithAppliedDefaults(existingMachineImage.Capabilities, capabilityDefinitions)
105+
if existingMachineImage.Name == machineImage.Name && existingMachineImage.Version == machineImage.Version && gardencorev1beta1helper.AreCapabilitiesEqual(defaultedCapabilities, existingDefaultedCapabilities) {
106+
// If the image already exists with the same capabilities return the existing list.
107+
return machineImages
108+
}
75109
}
110+
111+
// If the image does not exist, we create a new machine image entry with the capabilities.
112+
machineImages = append(machineImages, api.MachineImage{
113+
Name: machineImage.Name,
114+
Version: machineImage.Version,
115+
ID: machineImage.ID,
116+
Capabilities: machineImage.Capabilities,
117+
})
118+
76119
return machineImages
77120
}

pkg/controller/worker/machines.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller"
1616
"github.com/gardener/gardener/extensions/pkg/controller/worker"
1717
genericworkeractuator "github.com/gardener/gardener/extensions/pkg/controller/worker/genericactuator"
18+
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
1819
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
1920
gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
2021
extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
@@ -27,6 +28,7 @@ import (
2728

2829
"github.com/gardener/gardener-extension-provider-openstack/charts"
2930
api "github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack"
31+
openstackapi "github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack"
3032
"github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack/helper"
3133
"github.com/gardener/gardener-extension-provider-openstack/pkg/openstack"
3234
)
@@ -99,12 +101,20 @@ func (w *workerDelegate) generateMachineConfig(ctx context.Context) error {
99101
for _, pool := range w.worker.Spec.Pools {
100102
zoneLen := int32(len(pool.Zones)) // #nosec: G115 - We validate if num pool zones exceeds max_int32.
101103

104+
//TODO: this returns a capability with arch amd64, but worker pool defined architecture arm64.
105+
machineTypeFromCloudProfile := gardencorev1beta1helper.FindMachineTypeByName(w.cluster.CloudProfile.Spec.MachineTypes, pool.MachineType)
106+
if machineTypeFromCloudProfile == nil {
107+
return fmt.Errorf("machine type %q not found in cloud profile %q", pool.MachineType, w.cluster.CloudProfile.Name)
108+
}
109+
102110
architecture := ptr.Deref(pool.Architecture, v1beta1constants.ArchitectureAMD64)
103-
machineImage, err := w.findMachineImage(pool.MachineImage.Name, pool.MachineImage.Version, architecture)
111+
machineImage, err := w.selectMachineImageForWorkerPool(pool.MachineImage.Name, pool.MachineImage.Version, w.worker.Spec.Region, &architecture, machineTypeFromCloudProfile.Capabilities)
104112
if err != nil {
105113
return err
106114
}
107-
machineImages = appendMachineImage(machineImages, *machineImage)
115+
116+
machineImages = EnsureUniformMachineImages(machineImages, w.cluster.CloudProfile.Spec.MachineCapabilities)
117+
machineImages = appendMachineImage(machineImages, *machineImage, w.cluster.CloudProfile.Spec.MachineCapabilities)
108118

109119
var volumeSize int
110120
if pool.Volume != nil {
@@ -324,3 +334,50 @@ func addTopologyLabel(labels map[string]string, zone string) map[string]string {
324334
openstack.CSIManilaDriverTopologyKey: zone,
325335
})
326336
}
337+
338+
// EnsureUniformMachineImages ensures that all machine images are in the same format, either with or without Capabilities.
339+
func EnsureUniformMachineImages(images []openstackapi.MachineImage, definitions []gardencorev1beta1.CapabilityDefinition) []openstackapi.MachineImage {
340+
var uniformMachineImages []openstackapi.MachineImage
341+
342+
if len(definitions) == 0 {
343+
// transform images that were added with Capabilities to the legacy format without Capabilities
344+
for _, img := range images {
345+
if len(img.Capabilities) == 0 {
346+
// image is already legacy format
347+
uniformMachineImages = appendMachineImage(uniformMachineImages, img, definitions)
348+
continue
349+
}
350+
// transform to legacy format by using the Architecture capability if it exists
351+
var architecture *string
352+
if len(img.Capabilities[v1beta1constants.ArchitectureName]) > 0 {
353+
architecture = &img.Capabilities[v1beta1constants.ArchitectureName][0]
354+
}
355+
// TODO: make uniform for imageID is not set BUT the global image name is set
356+
uniformMachineImages = appendMachineImage(uniformMachineImages, openstackapi.MachineImage{
357+
Name: img.Name,
358+
Version: img.Version,
359+
ID: img.ID,
360+
Architecture: architecture,
361+
}, definitions)
362+
}
363+
return uniformMachineImages
364+
}
365+
366+
// transform images that were added without Capabilities to contain a MachineImageFlavor with defaulted Architecture
367+
for _, img := range images {
368+
if len(img.Capabilities) > 0 {
369+
// image is already in the new format with Capabilities
370+
uniformMachineImages = appendMachineImage(uniformMachineImages, img, definitions)
371+
} else {
372+
// add image as a capability set with defaulted Architecture
373+
architecture := ptr.Deref(img.Architecture, v1beta1constants.ArchitectureAMD64)
374+
uniformMachineImages = appendMachineImage(uniformMachineImages, openstackapi.MachineImage{
375+
Name: img.Name,
376+
Version: img.Version,
377+
ID: img.ID,
378+
Capabilities: gardencorev1beta1.Capabilities{v1beta1constants.ArchitectureName: []string{architecture}},
379+
}, definitions)
380+
}
381+
}
382+
return uniformMachineImages
383+
}

0 commit comments

Comments
 (0)