Skip to content

Commit 5281786

Browse files
cheesesashimidkhater-redhat
authored andcommitted
unify update paths for non-OCL and OCL and avoid reboots by recreating and reusing MOSB's with updated rendered spec
1 parent cb7cd68 commit 5281786

File tree

10 files changed

+619
-289
lines changed

10 files changed

+619
-289
lines changed

pkg/controller/build/clients.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
type informers struct {
2626
controllerConfigInformer mcfginformersv1.ControllerConfigInformer
2727
machineConfigPoolInformer mcfginformersv1.MachineConfigPoolInformer
28+
machineConfigInformer mcfginformersv1.MachineConfigInformer
2829
jobInformer batchinformersv1.JobInformer
2930
machineOSBuildInformer mcfginformersv1.MachineOSBuildInformer
3031
machineOSConfigInformer mcfginformersv1.MachineOSConfigInformer
@@ -46,6 +47,7 @@ func (i *informers) listers() *listers {
4647
machineOSBuildLister: i.machineOSBuildInformer.Lister(),
4748
machineOSConfigLister: i.machineOSConfigInformer.Lister(),
4849
machineConfigPoolLister: i.machineConfigPoolInformer.Lister(),
50+
machineConfigLister: i.machineConfigInformer.Lister(),
4951
jobLister: i.jobInformer.Lister(),
5052
controllerConfigLister: i.controllerConfigInformer.Lister(),
5153
nodeLister: i.nodeInformer.Lister(),
@@ -57,6 +59,7 @@ type listers struct {
5759
machineOSBuildLister mcfglistersv1.MachineOSBuildLister
5860
machineOSConfigLister mcfglistersv1.MachineOSConfigLister
5961
machineConfigPoolLister mcfglistersv1.MachineConfigPoolLister
62+
machineConfigLister mcfglistersv1.MachineConfigLister
6063
jobLister batchlisterv1.JobLister
6164
controllerConfigLister mcfglistersv1.ControllerConfigLister
6265
nodeLister corelistersv1.NodeLister
@@ -91,6 +94,7 @@ func newInformers(mcfgclient mcfgclientset.Interface, kubeclient clientset.Inter
9194
controllerConfigInformer := mcoInformerFactory.Machineconfiguration().V1().ControllerConfigs()
9295
machineConfigPoolInformer := mcoInformerFactory.Machineconfiguration().V1().MachineConfigPools()
9396
machineOSBuildInformer := mcoInformerFactory.Machineconfiguration().V1().MachineOSBuilds()
97+
machineConfigInformer := mcoInformerFactory.Machineconfiguration().V1().MachineConfigs()
9498
machineOSConfigInformer := mcoInformerFactory.Machineconfiguration().V1().MachineOSConfigs()
9599
jobInformer := coreInformerFactory.Batch().V1().Jobs()
96100
nodeInformer := coreInformerFactoryNodes.Core().V1().Nodes()
@@ -100,6 +104,7 @@ func newInformers(mcfgclient mcfgclientset.Interface, kubeclient clientset.Inter
100104
machineConfigPoolInformer: machineConfigPoolInformer,
101105
machineOSBuildInformer: machineOSBuildInformer,
102106
machineOSConfigInformer: machineOSConfigInformer,
107+
machineConfigInformer: machineConfigInformer,
103108
jobInformer: jobInformer,
104109
nodeInformer: nodeInformer,
105110
toStart: []interface{ Start(<-chan struct{}) }{

pkg/controller/build/osbuildcontroller_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,3 +756,36 @@ func assertMachineOSConfigGetsCurrentBuildAnnotation(ctx context.Context, t *tes
756756

757757
require.NoError(t, err)
758758
}
759+
760+
// Test that when the MCP’s rendered-MC name changes but the two MCs only differ
761+
// by on-cluster layering, no Build Job is created.
762+
func TestOSBuildControllerSkipsBuildForLayerOnlyChanges(t *testing.T) {
763+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
764+
t.Cleanup(cancel)
765+
poolName := "worker"
766+
767+
_, mcfgclient, _, _, mosc, firstMosb, mcp, kubeassert := setupOSBuildControllerForTestWithSuccessfulBuild(ctx, t, poolName)
768+
isMachineOSBuildReachedExpectedCount(ctx, t, mcfgclient, mosc, 1)
769+
770+
assertMachineOSConfigGetsCurrentBuildAnnotation(ctx, t, mcfgclient, mosc, firstMosb)
771+
772+
isMachineOSBuildReachedExpectedCount(ctx, t, mcfgclient, mosc, 1)
773+
774+
insertNewRenderedMachineConfigAndUpdatePool(ctx, t, mcfgclient, mcp.Name, "rendered-worker-layer-only")
775+
776+
// Give the controller a moment to (not) kick off a build
777+
time.Sleep(200 * time.Millisecond)
778+
779+
mosbList, err := mcfgclient.MachineconfigurationV1().
780+
MachineOSBuilds().
781+
List(ctx, metav1.ListOptions{LabelSelector: utils.MachineOSBuildForPoolSelector(mosc).String()})
782+
require.NoError(t, err)
783+
require.Len(t, mosbList.Items, 2, "expected a new MOSB to be created for layering-only change")
784+
assert.Equal(t, firstMosb.Name, mosbList.Items[0].Name, "first MOSB should remain unchanged")
785+
786+
layerOnlyMosb := mosbList.Items[1]
787+
788+
jobName := utils.GetBuildJobName(&layerOnlyMosb)
789+
790+
kubeassert.JobDoesNotExist(jobName, "layering-only MOSB should not spawn a build Job")
791+
}

pkg/controller/build/reconciler.go

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
imagev1clientset "github.com/openshift/client-go/image/clientset/versioned"
1414
mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
1515
routeclientset "github.com/openshift/client-go/route/clientset/versioned"
16+
"github.com/openshift/machine-config-operator/pkg/apihelpers"
1617
"github.com/openshift/machine-config-operator/pkg/controller/build/buildrequest"
1718
"github.com/openshift/machine-config-operator/pkg/controller/build/constants"
1819
"github.com/openshift/machine-config-operator/pkg/controller/build/imagebuilder"
@@ -117,7 +118,7 @@ func (b *buildReconciler) updateMachineOSConfig(ctx context.Context, old, cur *m
117118

118119
// Whenever the MachineOSConfig spec has changed, create a new MachineOSBuild.
119120
if !equality.Semantic.DeepEqual(old.Spec, cur.Spec) {
120-
klog.Infof("Detected MachineOSConfig change for %s", cur.Name)
121+
klog.Infof("[updateMachineOSConfig] Detected MachineOSConfig change and kicking off createNewMachineOSBuildOrReuseExisting for %s", cur.Name)
121122
return b.createNewMachineOSBuildOrReuseExisting(ctx, cur, false)
122123
}
123124

@@ -331,7 +332,7 @@ func (b *buildReconciler) updateMachineOSConfigStatus(ctx context.Context, mosc
331332

332333
// skip the status update if the current image pullspec equals the digest image pushspec.
333334
if mosc.Status.CurrentImagePullSpec == mosb.Status.DigestedImagePushSpec {
334-
klog.Infof("MachineOSConfig %q already has final image pushspec for MachineOSBuild %q", mosc.Name, mosb.Name)
335+
klog.Infof("[updateMachineOSConfig] MachineOSConfig %q already has final image pushspec for MachineOSBuild %q", mosc.Name, mosb.Name)
335336
return nil
336337
}
337338

@@ -373,13 +374,11 @@ func (b *buildReconciler) UpdateMachineConfigPool(ctx context.Context, oldMCP, c
373374
func (b *buildReconciler) updateMachineConfigPool(ctx context.Context, oldMCP, curMCP *mcfgv1.MachineConfigPool) error {
374375
if oldMCP.Spec.Configuration.Name != curMCP.Spec.Configuration.Name {
375376
klog.Infof("Rendered config for pool %s changed from %s to %s", curMCP.Name, oldMCP.Spec.Configuration.Name, curMCP.Spec.Configuration.Name)
376-
if err := b.createNewMachineOSBuildOrReuseExistingForPoolChange(ctx, curMCP); err != nil {
377+
if err := b.reconcilePoolChange(ctx, curMCP); err != nil {
377378
return fmt.Errorf("could not create or reuse existing MachineOSBuild for MachineConfigPool %q change: %w", curMCP.Name, err)
378379
}
379380
}
380381

381-
// Not sure if we need to do this here yet or not.
382-
// TODO: Determine if we should call b.syncMachineConfigPools() here or not.
383382
return b.syncAll(ctx)
384383
}
385384

@@ -556,8 +555,8 @@ func (b *buildReconciler) createNewMachineOSBuildOrReuseExisting(ctx context.Con
556555
// In this situation, we've determined that the MachineOSBuild does not
557556
// exist, so we need to create it.
558557
if k8serrors.IsNotFound(err) {
559-
_, err := b.mcfgclient.MachineconfigurationV1().MachineOSBuilds().Create(ctx, mosb, metav1.CreateOptions{})
560-
if err != nil && !k8serrors.IsAlreadyExists(err) {
558+
mosb, err := b.mcfgclient.MachineconfigurationV1().MachineOSBuilds().Create(ctx, mosb, metav1.CreateOptions{})
559+
if err != nil {
561560
return fmt.Errorf("could not create new MachineOSBuild %q: %w", mosb.Name, err)
562561
}
563562
klog.Infof("New MachineOSBuild created: %s", mosb.Name)
@@ -1188,12 +1187,35 @@ func (b *buildReconciler) syncMachineOSBuild(ctx context.Context, mosb *mcfgv1.M
11881187
return fmt.Errorf("could not get MachineConfigPool from MachineOSBuild %q: %w", mosb.Name, err)
11891188
}
11901189

1191-
// An mosb which had previously been forgotten by the queue and is no longer desired by the mcp should not build
11921190
if mosb.ObjectMeta.Labels[constants.RenderedMachineConfigLabelKey] != mcp.Spec.Configuration.Name {
11931191
klog.Infof("The MachineOSBuild %q which builds the rendered Machine Config %q is no longer desired by the MCP %q", mosb.Name, mosb.ObjectMeta.Labels[constants.RenderedMachineConfigLabelKey], mosb.ObjectMeta.Labels[constants.TargetMachineConfigPoolLabelKey])
11941192
return nil
11951193
}
11961194

1195+
oldMCName := mcp.Status.Configuration.Name
1196+
newMCName := mcp.Spec.Configuration.Name
1197+
1198+
oldRendered, err := b.machineConfigLister.Get(oldMCName)
1199+
if err != nil {
1200+
return err
1201+
}
1202+
newRendered, err := b.machineConfigLister.Get(newMCName)
1203+
if err != nil {
1204+
return err
1205+
}
1206+
1207+
old := mcp.DeepCopy()
1208+
old.Spec.Configuration.Name = mcp.Status.Configuration.Name
1209+
1210+
needsImageRebuild, err := b.reconcileImageRebuild(old, mcp)
1211+
if err != nil {
1212+
return err
1213+
}
1214+
if oldRendered != newRendered && !needsImageRebuild {
1215+
klog.Infof("[syncMachineOSBuild] MachineOSBuild %q: only layering changes → skipping builder", mosb.Name)
1216+
return nil
1217+
}
1218+
11971219
mosbState := ctrlcommon.NewMachineOSBuildState(mosb)
11981220

11991221
if mosbState.IsInTerminalState() {
@@ -1290,7 +1312,7 @@ func (b *buildReconciler) syncMachineOSConfig(ctx context.Context, mosc *mcfgv1.
12901312
}
12911313
}
12921314

1293-
klog.Infof("No matching MachineOSBuild found for MachineOSConfig %q, will create one", mosc.Name)
1315+
klog.Infof("syncMachineOSConfig: No matching MachineOSBuild found for MachineOSConfig %q, will create one by kicking off createNewMachineOSBuildOrReuseExisting", mosc.Name)
12941316
if err := b.createNewMachineOSBuildOrReuseExisting(ctx, mosc, false); err != nil {
12951317
return fmt.Errorf("could not create new or reuse existing MachineOSBuild for MachineOSConfig %q: %w", mosc.Name, err)
12961318
}
@@ -1328,6 +1350,130 @@ func (b *buildReconciler) syncMachineConfigPools(ctx context.Context) error {
13281350
// if needed.
13291351
func (b *buildReconciler) syncMachineConfigPool(ctx context.Context, mcp *mcfgv1.MachineConfigPool) error {
13301352
return b.timeObjectOperation(mcp, syncingVerb, func() error {
1331-
return b.createNewMachineOSBuildOrReuseExistingForPoolChange(ctx, mcp)
1353+
return b.reconcilePoolChange(ctx, mcp)
1354+
})
1355+
}
1356+
1357+
func (b *buildReconciler) reconcilePoolChange(ctx context.Context, mcp *mcfgv1.MachineConfigPool) error {
1358+
mosc, err := utils.GetMachineOSConfigForMachineConfigPool(mcp, b.utilListers())
1359+
if err != nil {
1360+
// Not opted in yet, so nothing for us to do.
1361+
klog.Infof("buildReconciler: no MachineOSConfig for pool %q, skipping", mcp.Name)
1362+
return nil
1363+
}
1364+
1365+
oldRendered := mcp.Status.Configuration.Name
1366+
newRendered := mcp.Spec.Configuration.Name
1367+
1368+
// old pool
1369+
old := mcp.DeepCopy()
1370+
old.Spec.Configuration.Name = mcp.Status.Configuration.Name
1371+
firstOptIn := mosc.Annotations[constants.CurrentMachineOSBuildAnnotationKey]
1372+
if firstOptIn == "" {
1373+
return fmt.Errorf("no current build annotation on MachineOSConfig %q", mosc.Name)
1374+
}
1375+
1376+
needsImageRebuild, err := b.reconcileImageRebuild(old, mcp)
1377+
if err != nil {
1378+
return err
1379+
}
1380+
1381+
if (oldRendered != newRendered && needsImageRebuild) || firstOptIn == "" {
1382+
klog.Infof("[reconcilePoolChange] pool %q: detected extension/kernel/OSImageURL change → will rebuild image", mcp.Name)
1383+
} else if oldRendered != newRendered && !needsImageRebuild {
1384+
klog.Infof("[reconcilePoolChange] pool %q: only layering changes → reusing existing image", mcp.Name)
1385+
prevPullSpec := mosc.Status.CurrentImagePullSpec
1386+
oldMOSB, err := utils.GetMachineOSBuildForImagePullspec(string(prevPullSpec), b.utilListers())
1387+
if err != nil {
1388+
klog.Infof("[reconcilePoolChange] mosb %s not found", oldMOSB.Name)
1389+
return err
1390+
}
1391+
return b.reuseImageForNewMOSB(ctx, mosc, oldMOSB)
1392+
}
1393+
1394+
return b.createNewMachineOSBuildOrReuseExisting(ctx, mosc, needsImageRebuild)
1395+
1396+
}
1397+
1398+
// reuseImageForNewMOSB creates a new MOSB (for the new rendered-MC name)
1399+
// but populates its status from oldMosb so that no build actually runs.
1400+
func (b *buildReconciler) reuseImageForNewMOSB(ctx context.Context, mosc *mcfgv1.MachineOSConfig, oldMosb *mcfgv1.MachineOSBuild,
1401+
) error {
1402+
// Look up the MCP
1403+
mcp, err := b.machineConfigPoolLister.Get(mosc.Spec.MachineConfigPool.Name)
1404+
if err != nil {
1405+
return err
1406+
}
1407+
1408+
// Build the new MOSB object
1409+
osImageURLs, err := ctrlcommon.GetOSImageURLConfig(ctx, b.kubeclient)
1410+
if err != nil {
1411+
return err
1412+
}
1413+
newMosb, err := buildrequest.NewMachineOSBuild(
1414+
buildrequest.MachineOSBuildOpts{
1415+
MachineOSConfig: mosc,
1416+
MachineConfigPool: mcp,
1417+
OSImageURLConfig: osImageURLs,
1418+
})
1419+
if err != nil {
1420+
return err
1421+
}
1422+
newMosb.SetOwnerReferences([]metav1.OwnerReference{
1423+
*metav1.NewControllerRef(mosc, mcfgv1.SchemeGroupVersion.WithKind("MachineOSConfig")),
13321424
})
1425+
1426+
// Create it if not already there
1427+
_, err = b.machineOSBuildLister.Get(newMosb.Name)
1428+
if k8serrors.IsNotFound(err) {
1429+
if newMosb, err = b.mcfgclient.
1430+
MachineconfigurationV1().
1431+
MachineOSBuilds().
1432+
Create(ctx, newMosb, metav1.CreateOptions{}); err != nil {
1433+
return err
1434+
}
1435+
} else if err != nil {
1436+
return err
1437+
}
1438+
1439+
// Change its status so it’s “Succeeded” with the old image
1440+
toUpdate, err := b.getMachineOSBuildForUpdate(newMosb)
1441+
if err != nil {
1442+
return err
1443+
}
1444+
oldStatus := toUpdate.Status
1445+
1446+
toUpdate.Status.DigestedImagePushSpec = oldMosb.Status.DigestedImagePushSpec
1447+
1448+
for _, c := range apihelpers.MachineOSBuildSucceededConditions() {
1449+
apihelpers.SetMachineOSBuildCondition(&toUpdate.Status, c)
1450+
}
1451+
1452+
if err := b.setStatusOnMachineOSBuildIfNeeded(ctx, toUpdate, oldStatus, toUpdate.Status); err != nil {
1453+
return err
1454+
}
1455+
1456+
// re-point the MC to the NEW build
1457+
return b.updateMachineOSConfigStatus(ctx, mosc, toUpdate)
1458+
}
1459+
1460+
// reconcilePoolChange returns (annotateRebuild bool, reuseOnly bool, err error)
1461+
func (b *buildReconciler) reconcileImageRebuild(oldMCP, curMCP *mcfgv1.MachineConfigPool) (bool, error) {
1462+
1463+
curr, err := b.machineConfigLister.Get(oldMCP.Spec.Configuration.Name)
1464+
if err != nil {
1465+
return false, err
1466+
}
1467+
des, err := b.machineConfigLister.Get(curMCP.Spec.Configuration.Name)
1468+
if err != nil {
1469+
return false, err
1470+
}
1471+
1472+
needsImageRebuild := ctrlcommon.RequiresRebuild(curr, des)
1473+
if needsImageRebuild {
1474+
klog.Info("[reconcileImageRebuild] needsimage rebuild is true, signalling a rebuild")
1475+
return true, nil
1476+
}
1477+
1478+
return false, nil
13331479
}

pkg/controller/common/helpers.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,3 +1462,11 @@ func GetCAsFromConfigMap(cm *corev1.ConfigMap, key string) ([]byte, error) {
14621462
}
14631463
return nil, fmt.Errorf("%s not found in %s/%s", key, cm.Namespace, cm.Name)
14641464
}
1465+
1466+
// Determines if an on-cluster layering image rollout and rebuild is required for the changes applied on the new MC
1467+
func RequiresRebuild(oldMC, newMC *mcfgv1.MachineConfig) bool {
1468+
return oldMC.Spec.OSImageURL != newMC.Spec.OSImageURL ||
1469+
oldMC.Spec.KernelType != newMC.Spec.KernelType ||
1470+
!reflect.DeepEqual(oldMC.Spec.Extensions, newMC.Spec.Extensions) ||
1471+
!reflect.DeepEqual(oldMC.Spec.KernelArguments, newMC.Spec.KernelArguments)
1472+
}

0 commit comments

Comments
 (0)