Skip to content

Commit 710f50d

Browse files
Merge pull request #5508 from djoshy/skip-unknown-arch
OCPBUGS-69674: Skip boot image updates if arch annotation is not found
2 parents bf7ba38 + 89d1c1c commit 710f50d

4 files changed

Lines changed: 140 additions & 41 deletions

File tree

cmd/machine-config-controller/start.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ func runStartCmd(_ *cobra.Command, _ []string) {
158158
ctrlctx.ConfigInformerFactory.Config().V1().Infrastructures(),
159159
ctrlctx.ClientBuilder.OperatorClientOrDie(componentName),
160160
ctrlctx.OperatorInformerFactory.Operator().V1().MachineConfigurations(),
161+
ctrlctx.ConfigInformerFactory.Config().V1().ClusterVersions(),
161162
ctrlctx.FeatureGatesHandler,
162163
)
163164
go bootImageController.Run(ctrlctx.Stop)

pkg/controller/bootimage/boot_image_controller.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@ type Controller struct {
5353
cpmsLister machinelistersv1.ControlPlaneMachineSetLister
5454
infraLister configlistersv1.InfrastructureLister
5555
mcopLister mcoplistersv1.MachineConfigurationLister
56+
clusterVersionLister configlistersv1.ClusterVersionLister
5657

5758
mcoCmListerSynced cache.InformerSynced
5859
mapiMachineSetListerSynced cache.InformerSynced
5960
cpmsListerSynced cache.InformerSynced
6061
infraListerSynced cache.InformerSynced
6162
mcopListerSynced cache.InformerSynced
63+
clusterVersionListerSynced cache.InformerSynced
6264

6365
queue workqueue.TypedRateLimitingInterface[string]
6466

@@ -122,6 +124,7 @@ func New(
122124
infraInformer configinformersv1.InfrastructureInformer,
123125
mcopClient mcopclientset.Interface,
124126
mcopInformer mcopinformersv1.MachineConfigurationInformer,
127+
clusterVersionInformer configinformersv1.ClusterVersionInformer,
125128
fgHandler ctrlcommon.FeatureGatesHandler,
126129
) *Controller {
127130
eventBroadcaster := record.NewBroadcaster()
@@ -145,12 +148,14 @@ func New(
145148
ctrl.cpmsLister = cpmsInformer.Lister()
146149
ctrl.infraLister = infraInformer.Lister()
147150
ctrl.mcopLister = mcopInformer.Lister()
151+
ctrl.clusterVersionLister = clusterVersionInformer.Lister()
148152

149153
ctrl.mcoCmListerSynced = mcoCmInfomer.Informer().HasSynced
150154
ctrl.mapiMachineSetListerSynced = mapiMachineSetInformer.Informer().HasSynced
151155
ctrl.cpmsListerSynced = cpmsInformer.Informer().HasSynced
152156
ctrl.infraListerSynced = infraInformer.Informer().HasSynced
153157
ctrl.mcopListerSynced = mcopInformer.Informer().HasSynced
158+
ctrl.clusterVersionListerSynced = clusterVersionInformer.Informer().HasSynced
154159

155160
mapiMachineSetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
156161
AddFunc: ctrl.addMAPIMachineSet,
@@ -192,7 +197,7 @@ func (ctrl *Controller) Run(stopCh <-chan struct{}) {
192197
defer utilruntime.HandleCrash()
193198
defer ctrl.queue.ShutDown()
194199

195-
if !cache.WaitForCacheSync(stopCh, ctrl.mcoCmListerSynced, ctrl.mapiMachineSetListerSynced, ctrl.infraListerSynced, ctrl.mcopListerSynced) {
200+
if !cache.WaitForCacheSync(stopCh, ctrl.mcoCmListerSynced, ctrl.mapiMachineSetListerSynced, ctrl.infraListerSynced, ctrl.mcopListerSynced, ctrl.clusterVersionListerSynced) {
196201
return
197202
}
198203

pkg/controller/bootimage/boot_image_controller_test.go

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,71 +97,144 @@ func setMachineSetBootImage(machineset *machinev1beta1.MachineSet, generateBootI
9797
}
9898

9999
func TestGetArchFromMachineSet(t *testing.T) {
100+
// Helper to create a single-arch cluster version
101+
singleArchCV := &osconfigv1.ClusterVersion{
102+
Status: osconfigv1.ClusterVersionStatus{
103+
Desired: osconfigv1.Release{
104+
Architecture: "", // Empty means single-arch
105+
},
106+
},
107+
}
108+
// Helper to create a multi-arch cluster version
109+
multiArchCV := &osconfigv1.ClusterVersion{
110+
Status: osconfigv1.ClusterVersionStatus{
111+
Desired: osconfigv1.Release{
112+
Architecture: osconfigv1.ClusterVersionArchitectureMulti,
113+
},
114+
},
115+
}
116+
100117
cases := []struct {
101-
name string
102-
annotations map[string]string
103-
expectedArch string
104-
expectError bool
118+
name string
119+
annotations map[string]string
120+
clusterVersion *osconfigv1.ClusterVersion
121+
expectedArch string
122+
expectError bool
105123
}{
106124
{
107-
name: "Single architecture label",
125+
name: "Single architecture label in single-arch cluster",
108126
annotations: map[string]string{
109127
MachineSetArchAnnotationKey: "kubernetes.io/arch=amd64",
110128
},
111-
expectedArch: "x86_64",
112-
expectError: false,
129+
clusterVersion: singleArchCV,
130+
expectedArch: "x86_64",
131+
expectError: false,
132+
},
133+
{
134+
name: "Single architecture label in multi-arch cluster",
135+
annotations: map[string]string{
136+
MachineSetArchAnnotationKey: "kubernetes.io/arch=amd64",
137+
},
138+
clusterVersion: multiArchCV,
139+
expectedArch: "x86_64",
140+
expectError: false,
113141
},
114142
{
115143
name: "Multiple labels with architecture first",
116144
annotations: map[string]string{
117145
MachineSetArchAnnotationKey: "kubernetes.io/arch=amd64,topology.ebs.csi.aws.com/zone=eu-central-1a",
118146
},
119-
expectedArch: "x86_64",
120-
expectError: false,
147+
clusterVersion: singleArchCV,
148+
expectedArch: "x86_64",
149+
expectError: false,
121150
},
122151
{
123152
name: "Multiple labels with architecture last",
124153
annotations: map[string]string{
125154
MachineSetArchAnnotationKey: "topology.ebs.csi.aws.com/zone=eu-central-1a,kubernetes.io/arch=arm64",
126155
},
127-
expectedArch: "aarch64",
128-
expectError: false,
156+
clusterVersion: singleArchCV,
157+
expectedArch: "aarch64",
158+
expectError: false,
129159
},
130160
{
131161
name: "Multiple labels with architecture in middle",
132162
annotations: map[string]string{
133163
MachineSetArchAnnotationKey: "topology.ebs.csi.aws.com/zone=eu-central-1a,kubernetes.io/arch=s390x,node.kubernetes.io/instance-type=m5.large",
134164
},
135-
expectedArch: "s390x",
136-
expectError: false,
165+
clusterVersion: singleArchCV,
166+
expectedArch: "s390x",
167+
expectError: false,
137168
},
138169
{
139170
name: "Multiple labels with spaces",
140171
annotations: map[string]string{
141172
MachineSetArchAnnotationKey: " topology.ebs.csi.aws.com/zone=eu-central-1a , kubernetes.io/arch=ppc64le , node.kubernetes.io/instance-type=m5.large ",
142173
},
143-
expectedArch: "ppc64le",
144-
expectError: false,
174+
clusterVersion: singleArchCV,
175+
expectedArch: "ppc64le",
176+
expectError: false,
145177
},
146178
{
147179
name: "Invalid architecture",
148180
annotations: map[string]string{
149181
MachineSetArchAnnotationKey: "kubernetes.io/arch=invalid-arch",
150182
},
151-
expectError: true,
183+
clusterVersion: singleArchCV,
184+
expectError: true,
152185
},
153186
{
154-
name: "No architecture label",
187+
name: "No architecture label in annotation",
155188
annotations: map[string]string{
156189
MachineSetArchAnnotationKey: "topology.ebs.csi.aws.com/zone=eu-central-1a,node.kubernetes.io/instance-type=m5.large",
157190
},
191+
clusterVersion: singleArchCV,
192+
expectError: true,
193+
},
194+
{
195+
name: "No annotation in single-arch cluster defaults to control plane arch",
196+
annotations: map[string]string{},
197+
clusterVersion: singleArchCV,
198+
expectError: false, // Should default to control plane arch
199+
},
200+
{
201+
name: "No annotation in multi-arch cluster returns error",
202+
annotations: map[string]string{},
203+
clusterVersion: &osconfigv1.ClusterVersion{
204+
Status: osconfigv1.ClusterVersionStatus{
205+
Desired: osconfigv1.Release{
206+
Architecture: osconfigv1.ClusterVersionArchitectureMulti,
207+
},
208+
},
209+
},
158210
expectError: true,
159211
},
160212
{
161-
name: "No annotation",
162-
annotations: map[string]string{},
163-
expectedArch: "", // Will default to control plane arch, but we can't test that easily
164-
expectError: false,
213+
name: "ARM64 architecture in multi-arch cluster",
214+
annotations: map[string]string{
215+
MachineSetArchAnnotationKey: "kubernetes.io/arch=arm64",
216+
},
217+
clusterVersion: multiArchCV,
218+
expectedArch: "aarch64",
219+
expectError: false,
220+
},
221+
{
222+
name: "PPC64LE architecture in single-arch cluster",
223+
annotations: map[string]string{
224+
MachineSetArchAnnotationKey: "kubernetes.io/arch=ppc64le",
225+
},
226+
clusterVersion: singleArchCV,
227+
expectedArch: "ppc64le",
228+
expectError: false,
229+
},
230+
{
231+
name: "S390X architecture in multi-arch cluster",
232+
annotations: map[string]string{
233+
MachineSetArchAnnotationKey: "kubernetes.io/arch=s390x",
234+
},
235+
clusterVersion: multiArchCV,
236+
expectedArch: "s390x",
237+
expectError: false,
165238
},
166239
}
167240

@@ -174,7 +247,7 @@ func TestGetArchFromMachineSet(t *testing.T) {
174247
},
175248
}
176249

177-
arch, err := getArchFromMachineSet(machineSet)
250+
arch, err := getArchFromMachineSet(machineSet, tc.clusterVersion)
178251

179252
if tc.expectError {
180253
assert.Error(t, err, "Expected error for test case: %s", tc.name)

pkg/controller/bootimage/ms_helpers.go

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
osconfigv1 "github.com/openshift/api/config/v1"
1112
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
1213
opv1 "github.com/openshift/api/operator/v1"
1314
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
@@ -113,9 +114,20 @@ func (ctrl *Controller) syncMAPIMachineSet(machineSet *machinev1beta1.MachineSet
113114
}
114115
}
115116

117+
// Fetch the ClusterVersion to determine if this is a multi-arch cluster
118+
clusterVersion, err := ctrl.clusterVersionLister.Get("version")
119+
if err != nil {
120+
return fmt.Errorf("failed to fetch clusterversion during machineset sync: %v, defaulting to single-arch behavior", err)
121+
}
122+
116123
// Fetch the architecture type of this machineset
117-
arch, err := getArchFromMachineSet(machineSet)
124+
arch, err := getArchFromMachineSet(machineSet, clusterVersion)
118125
if err != nil {
126+
// If no architecture annotation was found, skip this machineset without erroring
127+
// A later sync loop will pick it up once the annotation is added
128+
if strings.Contains(err.Error(), "no architecture annotation found") {
129+
return nil
130+
}
119131
return fmt.Errorf("failed to fetch arch during machineset sync: %w", err)
120132
}
121133

@@ -222,29 +234,37 @@ func (ctrl *Controller) patchMachineSet(oldMachineSet, newMachineSet *machinev1b
222234
}
223235

224236
// Returns architecture type for a given machineset
225-
func getArchFromMachineSet(machineset *machinev1beta1.MachineSet) (arch string, err error) {
237+
func getArchFromMachineSet(machineset *machinev1beta1.MachineSet, clusterVersion *osconfigv1.ClusterVersion) (arch string, err error) {
226238

227239
// Valid set of machineset/node architectures
228240
validArchSet := sets.New("arm64", "s390x", "amd64", "ppc64le")
229241
// Check if the annotation enclosing arch label is present on this machineset
230242
archLabel, archLabelMatch := machineset.Annotations[MachineSetArchAnnotationKey]
231-
if archLabelMatch {
232-
// Parse the annotation value which may contain multiple comma-separated labels
233-
// Example: kubernetes.io/arch=amd64,topology.ebs.csi.aws.com/zone=eu-central-1a
234-
for label := range strings.SplitSeq(archLabel, ",") {
235-
label = strings.TrimSpace(label)
236-
if archLabelValue, found := strings.CutPrefix(label, ArchLabelKey); found {
237-
// Extract just the architecture value after "kubernetes.io/arch="
238-
if validArchSet.Has(archLabelValue) {
239-
return archtranslater.RpmArch(archLabelValue), nil
240-
}
241-
return "", fmt.Errorf("invalid architecture value found in annotation: %s", archLabelValue)
243+
244+
if !archLabelMatch {
245+
// Check if this is a multi-arch cluster
246+
// clusterVersion should never be nil as it's validated by the caller
247+
if clusterVersion.Status.Desired.Architecture == osconfigv1.ClusterVersionArchitectureMulti {
248+
// For multi-arch clusters, we require the architecture annotation
249+
klog.Errorf("No architecture annotation found on machineset %s in multi-arch cluster, skipping boot image update", machineset.Name)
250+
return "", fmt.Errorf("no architecture annotation found on machineset %s", machineset.Name)
251+
}
252+
// For single-arch clusters, default to control plane architecture
253+
klog.Infof("No architecture annotation found on machineset %s, defaulting to control plane architecture", machineset.Name)
254+
return archtranslater.CurrentRpmArch(), nil
255+
}
256+
257+
// Parse the annotation value which may contain multiple comma-separated labels
258+
// Example: kubernetes.io/arch=amd64,topology.ebs.csi.aws.com/zone=eu-central-1a
259+
for label := range strings.SplitSeq(archLabel, ",") {
260+
label = strings.TrimSpace(label)
261+
if archLabelValue, found := strings.CutPrefix(label, ArchLabelKey); found {
262+
// Extract just the architecture value after "kubernetes.io/arch="
263+
if validArchSet.Has(archLabelValue) {
264+
return archtranslater.RpmArch(archLabelValue), nil
242265
}
266+
return "", fmt.Errorf("invalid architecture value found in annotation: %s", archLabelValue)
243267
}
244-
return "", fmt.Errorf("kubernetes.io/arch label not found in annotation: %s", archLabel)
245268
}
246-
// If no arch annotation was found on the machineset, default to the control plane arch.
247-
// return the architecture of the node running this pod, which will always be a control plane node.
248-
klog.Infof("Defaulting to control plane architecture")
249-
return archtranslater.CurrentRpmArch(), nil
269+
return "", fmt.Errorf("kubernetes.io/arch label not found in annotation: %s", archLabel)
250270
}

0 commit comments

Comments
 (0)