Skip to content

Commit 7a8a698

Browse files
Merge pull request #5697 from umohnani8/osimgstream-spec
MCO-2136: Implement osImageStream inheritance for custom MCPs
2 parents d5dfdcf + f02ab03 commit 7a8a698

File tree

5 files changed

+521
-3
lines changed

5 files changed

+521
-3
lines changed

pkg/controller/common/helpers.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
opv1 "github.com/openshift/api/operator/v1"
5050
mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
5151
"github.com/openshift/client-go/machineconfiguration/clientset/versioned/scheme"
52+
mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1"
5253
"github.com/openshift/library-go/pkg/crypto"
5354
buildconstants "github.com/openshift/machine-config-operator/pkg/controller/build/constants"
5455
)
@@ -1430,3 +1431,43 @@ func GetPreBuiltImageMachineConfigsFromList(mcs []*mcfgv1.MachineConfig) map[str
14301431

14311432
return preBuiltMCs
14321433
}
1434+
1435+
// IsCustomPool returns true if the pool is a custom pool (not master, worker, or arbiter)
1436+
func IsCustomPool(pool *mcfgv1.MachineConfigPool) bool {
1437+
return pool.Name != MachineConfigPoolMaster &&
1438+
pool.Name != MachineConfigPoolWorker &&
1439+
pool.Name != MachineConfigPoolArbiter
1440+
}
1441+
1442+
// GetEffectiveOSImageStreamName returns the effective osImageStream name for a pool,
1443+
// taking inheritance from the worker pool into account for custom pools.
1444+
// Returns:
1445+
// - The pool's explicit osImageStream.Name if set
1446+
// - For custom pools: the worker pool's osImageStream.Name if the custom pool has none
1447+
// - Empty string (which will resolve to the default stream)
1448+
//
1449+
// This function can return an error if the worker pool cannot be fetched.
1450+
func GetEffectiveOSImageStreamName(pool *mcfgv1.MachineConfigPool, mcpLister mcfglistersv1.MachineConfigPoolLister) (string, error) {
1451+
// If the pool explicitly sets an osImageStream, use it
1452+
if pool.Spec.OSImageStream.Name != "" {
1453+
return pool.Spec.OSImageStream.Name, nil
1454+
}
1455+
1456+
// Only custom pools inherit from worker; standard pools use default directly
1457+
if !IsCustomPool(pool) {
1458+
return "", nil
1459+
}
1460+
1461+
// For custom pools with no explicit stream, try to inherit from worker
1462+
workerPool, err := mcpLister.Get(MachineConfigPoolWorker)
1463+
if err != nil {
1464+
// If worker pool doesn't exist or can't be fetched, use default stream
1465+
if !kerr.IsNotFound(err) {
1466+
return "", fmt.Errorf("failed to get worker pool for osImageStream inheritance: %w", err)
1467+
}
1468+
return "", nil
1469+
}
1470+
1471+
// Return worker pool's stream (which may be empty, indicating default)
1472+
return workerPool.Spec.OSImageStream.Name, nil
1473+
}

pkg/controller/common/helpers_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"k8s.io/apimachinery/pkg/runtime"
2121

2222
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
23+
"github.com/openshift/client-go/machineconfiguration/clientset/versioned/fake"
24+
informers "github.com/openshift/client-go/machineconfiguration/informers/externalversions"
2325
"github.com/openshift/machine-config-operator/pkg/controller/common/fixtures"
2426
"github.com/openshift/machine-config-operator/test/helpers"
2527
)
@@ -1830,3 +1832,92 @@ func assertExpectedNodes(t *testing.T, expected []string, actual []*corev1.Node)
18301832
t.Helper()
18311833
assert.Equal(t, expected, helpers.GetNamesFromNodes(actual))
18321834
}
1835+
1836+
func TestGetEffectiveOSImageStreamName(t *testing.T) {
1837+
tests := []struct {
1838+
name string
1839+
pool *mcfgv1.MachineConfigPool
1840+
workerPool *mcfgv1.MachineConfigPool
1841+
expectedStream string
1842+
expectError bool
1843+
}{
1844+
{
1845+
name: "custom pool with explicit stream",
1846+
pool: func() *mcfgv1.MachineConfigPool {
1847+
p := helpers.NewMachineConfigPool("infra", helpers.InfraSelector, nil, "")
1848+
p.Spec.OSImageStream.Name = "rhel-10"
1849+
return p
1850+
}(),
1851+
workerPool: func() *mcfgv1.MachineConfigPool {
1852+
p := helpers.NewMachineConfigPool("worker", helpers.WorkerSelector, nil, "")
1853+
p.Spec.OSImageStream.Name = "rhel-9"
1854+
return p
1855+
}(),
1856+
expectedStream: "rhel-10",
1857+
expectError: false,
1858+
},
1859+
{
1860+
name: "custom pool inherits from worker",
1861+
pool: helpers.NewMachineConfigPool("infra", helpers.InfraSelector, nil, ""),
1862+
workerPool: func() *mcfgv1.MachineConfigPool {
1863+
p := helpers.NewMachineConfigPool("worker", helpers.WorkerSelector, nil, "")
1864+
p.Spec.OSImageStream.Name = "rhel-9"
1865+
return p
1866+
}(),
1867+
expectedStream: "rhel-9",
1868+
expectError: false,
1869+
},
1870+
{
1871+
name: "custom pool without worker pool uses default",
1872+
pool: helpers.NewMachineConfigPool("infra", helpers.InfraSelector, nil, ""),
1873+
workerPool: nil,
1874+
expectedStream: "",
1875+
expectError: false,
1876+
},
1877+
{
1878+
name: "standard pool (worker) uses default",
1879+
pool: helpers.NewMachineConfigPool("worker", helpers.WorkerSelector, nil, ""),
1880+
workerPool: nil,
1881+
expectedStream: "",
1882+
expectError: false,
1883+
},
1884+
{
1885+
name: "standard pool (master) uses default",
1886+
pool: helpers.NewMachineConfigPool("master", helpers.MasterSelector, nil, ""),
1887+
workerPool: nil,
1888+
expectedStream: "",
1889+
expectError: false,
1890+
},
1891+
}
1892+
1893+
for _, tt := range tests {
1894+
t.Run(tt.name, func(t *testing.T) {
1895+
// Create fake client and informer
1896+
objects := []runtime.Object{}
1897+
if tt.workerPool != nil {
1898+
objects = append(objects, tt.workerPool)
1899+
}
1900+
1901+
fakeClient := fake.NewSimpleClientset(objects...)
1902+
informerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
1903+
mcpLister := informerFactory.Machineconfiguration().V1().MachineConfigPools().Lister()
1904+
1905+
// Start informer and wait for cache sync
1906+
stopCh := make(chan struct{})
1907+
defer close(stopCh)
1908+
informerFactory.Start(stopCh)
1909+
informerFactory.WaitForCacheSync(stopCh)
1910+
1911+
// Call the function
1912+
streamName, err := GetEffectiveOSImageStreamName(tt.pool, mcpLister)
1913+
1914+
// Check error
1915+
if tt.expectError {
1916+
assert.Error(t, err)
1917+
} else {
1918+
assert.NoError(t, err)
1919+
assert.Equal(t, tt.expectedStream, streamName)
1920+
}
1921+
})
1922+
}
1923+
}

pkg/controller/render/render_controller.go

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ func (ctrl *Controller) updateMachineConfigPool(old, cur interface{}) {
192192

193193
klog.V(4).Infof("Updating MachineConfigPool %s", oldPool.Name)
194194
ctrl.enqueueMachineConfigPool(curPool)
195+
196+
// If the worker pool's osImageStream changed, enqueue all custom pools that might inherit from it
197+
if curPool.Name == ctrlcommon.MachineConfigPoolWorker &&
198+
oldPool.Spec.OSImageStream.Name != curPool.Spec.OSImageStream.Name {
199+
klog.V(4).Infof("Worker pool osImageStream changed from %q to %q, enqueuing custom pools",
200+
oldPool.Spec.OSImageStream.Name, curPool.Spec.OSImageStream.Name)
201+
ctrl.enqueueCustomPools()
202+
}
195203
}
196204

197205
func (ctrl *Controller) deleteMachineConfigPool(obj interface{}) {
@@ -398,6 +406,23 @@ func (ctrl *Controller) enqueueDefault(pool *mcfgv1.MachineConfigPool) {
398406
ctrl.enqueueAfter(pool, renderDelay)
399407
}
400408

409+
// enqueueCustomPools enqueues all custom pools (pools that are not master, worker, or arbiter).
410+
// This is used when the worker pool changes in a way that might affect custom pools that inherit from it.
411+
func (ctrl *Controller) enqueueCustomPools() {
412+
pools, err := ctrl.mcpLister.List(labels.Everything())
413+
if err != nil {
414+
klog.Warningf("Failed to list pools when enqueuing custom pools: %v", err)
415+
return
416+
}
417+
418+
for _, pool := range pools {
419+
if ctrlcommon.IsCustomPool(pool) {
420+
klog.V(4).Infof("Enqueuing custom pool %s due to worker pool change", pool.Name)
421+
ctrl.enqueueMachineConfigPool(pool)
422+
}
423+
}
424+
}
425+
401426
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
402427
// It enforces that the syncHandler is never invoked concurrently with the same key.
403428
func (ctrl *Controller) worker() {
@@ -565,17 +590,51 @@ func (ctrl *Controller) getRenderedMachineConfig(pool *mcfgv1.MachineConfigPool,
565590
return generateAndValidateRenderedMachineConfig(currentMC, pool, configs, cc, &mcop.Spec.IrreconcilableValidationOverrides, osImageStreamSet)
566591
}
567592

593+
// getOSImageStreamNameForPool determines the OS image stream name for a pool,
594+
// implementing inheritance from the worker pool for custom pools.
595+
// Returns:
596+
// - The pool's explicit osImageStream.Name if set
597+
// - For custom pools: the worker pool's osImageStream.Name if the custom pool has none
598+
// - Empty string (which will resolve to the default stream)
599+
func (ctrl *Controller) getOSImageStreamNameForPool(pool *mcfgv1.MachineConfigPool) string {
600+
streamName, err := ctrlcommon.GetEffectiveOSImageStreamName(pool, ctrl.mcpLister)
601+
if err != nil {
602+
klog.V(2).Infof("Failed to get effective osImageStream for pool %s: %v; using default stream", pool.Name, err)
603+
return ""
604+
}
605+
606+
// Log what stream is being used
607+
if streamName != "" {
608+
if pool.Spec.OSImageStream.Name != "" {
609+
klog.V(4).Infof("Pool %s using explicit osImageStream: %s", pool.Name, streamName)
610+
} else {
611+
klog.V(4).Infof("Custom pool %s inheriting osImageStream from worker: %s", pool.Name, streamName)
612+
}
613+
} else {
614+
if ctrlcommon.IsCustomPool(pool) {
615+
klog.V(4).Infof("Custom pool %s using default osImageStream (worker also uses default)", pool.Name)
616+
} else {
617+
klog.V(4).Infof("Standard pool %s using default osImageStream", pool.Name)
618+
}
619+
}
620+
621+
return streamName
622+
}
623+
568624
func (ctrl *Controller) getOSImageStreamForPool(pool *mcfgv1.MachineConfigPool) (*v1alpha1.OSImageStreamSet, error) {
569625
if !osimagestream.IsFeatureEnabled(ctrl.fgHandler) || ctrl.osImageStreamLister == nil {
570626
return nil, nil
571627
}
572628

629+
// Determine which stream name to use based on inheritance logic
630+
streamName := ctrl.getOSImageStreamNameForPool(pool)
631+
573632
imageStream, err := ctrl.osImageStreamLister.Get(ctrlcommon.ClusterInstanceNameOSImageStream)
574633
if err != nil {
575634
return nil, fmt.Errorf("could not get OSImageStream for pool %s: %w", pool.Name, err)
576635
}
577636

578-
imageStreamSet, err := osimagestream.GetOSImageStreamSetByName(imageStream, pool.Spec.OSImageStream.Name)
637+
imageStreamSet, err := osimagestream.GetOSImageStreamSetByName(imageStream, streamName)
579638
if err != nil {
580639
return nil, fmt.Errorf("could not get OSImageStreamSet for pool %s: %w", pool.Name, err)
581640
}
@@ -778,6 +837,31 @@ func generateAndValidateRenderedMachineConfig(
778837
return generated, nil
779838
}
780839

840+
// getOSImageStreamNameForPoolBootstrap implements the same inheritance logic as
841+
// getOSImageStreamNameForPool but for the bootstrap scenario where we only have
842+
// a slice of pools, not a lister.
843+
func getOSImageStreamNameForPoolBootstrap(pool *mcfgv1.MachineConfigPool, pools []*mcfgv1.MachineConfigPool) string {
844+
// If the pool explicitly sets an osImageStream, use it
845+
if pool.Spec.OSImageStream.Name != "" {
846+
return pool.Spec.OSImageStream.Name
847+
}
848+
849+
// Only custom pools inherit from worker
850+
if !ctrlcommon.IsCustomPool(pool) {
851+
return ""
852+
}
853+
854+
// Find worker pool in the provided slice
855+
for _, p := range pools {
856+
if p.Name == ctrlcommon.MachineConfigPoolWorker {
857+
return p.Spec.OSImageStream.Name
858+
}
859+
}
860+
861+
// Worker not found, use default
862+
return ""
863+
}
864+
781865
// RunBootstrap runs the render controller in bootstrap mode.
782866
// For each pool, it matches the machineconfigs based on label selector and
783867
// returns the generated machineconfigs and pool with CurrentMachineConfig status field set.
@@ -793,7 +877,9 @@ func RunBootstrap(pools []*mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineCo
793877
}
794878
var osImageStreamSet *v1alpha1.OSImageStreamSet
795879
if osImageStream != nil {
796-
osImageStreamSet, err = osimagestream.GetOSImageStreamSetByName(osImageStream, pool.Spec.OSImageStream.Name)
880+
// Determine which stream name to use based on inheritance logic
881+
streamName := getOSImageStreamNameForPoolBootstrap(pool, pools)
882+
osImageStreamSet, err = osimagestream.GetOSImageStreamSetByName(osImageStream, streamName)
797883
if err != nil {
798884
return nil, nil, fmt.Errorf("couldn't get the OSImageStream for pool %s %w", pool.Name, err)
799885
}

0 commit comments

Comments
 (0)