11package server
22
33import (
4+ "context"
45 "encoding/json"
56 "errors"
67 "fmt"
@@ -9,14 +10,19 @@ import (
910 "path/filepath"
1011 "time"
1112
13+ ign3types "github.com/coreos/ignition/v2/config/v3_5/types"
1214 yaml "github.com/ghodss/yaml"
15+ mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
1316 mcfginformers "github.com/openshift/client-go/machineconfiguration/informers/externalversions"
1417
18+ routeclientset "github.com/openshift/client-go/route/clientset/versioned"
1519 "github.com/openshift/machine-config-operator/internal/clients"
1620 ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
1721 corev1 "k8s.io/api/core/v1"
22+ "k8s.io/apimachinery/pkg/labels"
1823 "k8s.io/apimachinery/pkg/runtime"
1924 "k8s.io/client-go/informers"
25+ clientset "k8s.io/client-go/kubernetes"
2026 corelisterv1 "k8s.io/client-go/listers/core/v1"
2127 "k8s.io/client-go/tools/cache"
2228 clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
@@ -42,6 +48,11 @@ type clusterServer struct {
4248 machineConfigLister v1.MachineConfigLister
4349 controllerConfigLister v1.ControllerConfigLister
4450 configMapLister corelisterv1.ConfigMapLister
51+ machineOSConfigLister v1.MachineOSConfigLister
52+ machineOSBuildLister v1.MachineOSBuildLister
53+
54+ kubeclient clientset.Interface
55+ routeclient routeclientset.Interface
4556
4657 kubeconfigFunc kubeconfigFunc
4758 apiserverURL string
@@ -73,26 +84,31 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
7384
7485 machineConfigClient := clientsBuilder .MachineConfigClientOrDie ("machine-config-shared-informer" )
7586 kubeClient := clientsBuilder .KubeClientOrDie ("kube-client-shared-informer" )
87+ routeClient := clientsBuilder .RouteClientOrDie ("route-client" )
7688 sharedInformerFactory := mcfginformers .NewSharedInformerFactory (machineConfigClient , resyncPeriod ()())
7789 kubeNamespacedSharedInformer := informers .NewFilteredSharedInformerFactory (kubeClient , resyncPeriod ()(), "openshift-machine-config-operator" , nil )
7890
79- mcpInformer , mcInformer , ccInformer , cmInformer :=
91+ mcpInformer , mcInformer , ccInformer , cmInformer , moscInformer , mosbInformer :=
8092 sharedInformerFactory .Machineconfiguration ().V1 ().MachineConfigPools (),
8193 sharedInformerFactory .Machineconfiguration ().V1 ().MachineConfigs (),
8294 sharedInformerFactory .Machineconfiguration ().V1 ().ControllerConfigs (),
83- kubeNamespacedSharedInformer .Core ().V1 ().ConfigMaps ()
84- mcpLister , mcLister , ccLister , cmLister := mcpInformer .Lister (), mcInformer .Lister (), ccInformer .Lister (), cmInformer .Lister ()
85- mcpListerHasSynced , mcListerHasSynced , ccListerHasSynced , cmListerHasSynced :=
95+ kubeNamespacedSharedInformer .Core ().V1 ().ConfigMaps (),
96+ sharedInformerFactory .Machineconfiguration ().V1 ().MachineOSConfigs (),
97+ sharedInformerFactory .Machineconfiguration ().V1 ().MachineOSBuilds ()
98+ mcpLister , mcLister , ccLister , cmLister , moscLister , mosbLister := mcpInformer .Lister (), mcInformer .Lister (), ccInformer .Lister (), cmInformer .Lister (), moscInformer .Lister (), mosbInformer .Lister ()
99+ mcpListerHasSynced , mcListerHasSynced , ccListerHasSynced , cmListerHasSynced , moscListerHasSynced , mosbListerHasSynced :=
86100 mcpInformer .Informer ().HasSynced ,
87101 mcInformer .Informer ().HasSynced ,
88102 ccInformer .Informer ().HasSynced ,
89- cmInformer .Informer ().HasSynced
103+ cmInformer .Informer ().HasSynced ,
104+ moscInformer .Informer ().HasSynced ,
105+ mosbInformer .Informer ().HasSynced
90106
91107 var informerStopCh chan struct {}
92108 go sharedInformerFactory .Start (informerStopCh )
93109 go kubeNamespacedSharedInformer .Start (informerStopCh )
94110
95- if ! cache .WaitForCacheSync (informerStopCh , mcpListerHasSynced , mcListerHasSynced , ccListerHasSynced , cmListerHasSynced ) {
111+ if ! cache .WaitForCacheSync (informerStopCh , mcpListerHasSynced , mcListerHasSynced , ccListerHasSynced , cmListerHasSynced , moscListerHasSynced , mosbListerHasSynced ) {
96112 return nil , errors .New ("failed to wait for cache sync" )
97113 }
98114
@@ -101,6 +117,10 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
101117 machineConfigLister : mcLister ,
102118 controllerConfigLister : ccLister ,
103119 configMapLister : cmLister ,
120+ machineOSConfigLister : moscLister ,
121+ machineOSBuildLister : mosbLister ,
122+ kubeclient : kubeClient ,
123+ routeclient : routeClient ,
104124 kubeconfigFunc : func () ([]byte , []byte , error ) { return kubeconfigFromSecret (bootstrapTokenDir , apiserverURL , nil ) },
105125 apiserverURL : apiserverURL ,
106126 }, nil
@@ -163,7 +183,27 @@ func (cs *clusterServer) GetConfig(cr poolRequest) (*runtime.RawExtension, error
163183
164184 addDataAndMaybeAppendToIgnition (caBundleFilePath , cc .Spec .KubeAPIServerServingCAData , & ignConf )
165185 addDataAndMaybeAppendToIgnition (cloudProviderCAPath , cc .Spec .CloudProviderCAData , & ignConf )
166- appenders := getAppenders (currConf , cr .version , cs .kubeconfigFunc , []string {}, "" )
186+
187+ desiredImage := cs .resolveDesiredImageForPool (mp )
188+ klog .Infof ("desiredImage is found to be %s" , desiredImage )
189+
190+ appenders := []appenderFunc {
191+ func (cfg * ign3types.Config , _ * mcfgv1.MachineConfig ) error {
192+ return appendNodeAnnotations (cfg , currConf , desiredImage )
193+ },
194+ func (cfg * ign3types.Config , _ * mcfgv1.MachineConfig ) error {
195+ return appendKubeConfig (cfg , cs .kubeconfigFunc )
196+ },
197+ appendInitialMachineConfig ,
198+ func (cfg * ign3types.Config , _ * mcfgv1.MachineConfig ) error { return appendCerts (cfg , []string {}, "" ) },
199+ // Inject desired image into the MC
200+ appendDesiredOSImage (desiredImage ),
201+ // Must be last
202+ func (cfg * ign3types.Config , mc * mcfgv1.MachineConfig ) error {
203+ return appendEncapsulated (cfg , mc , cr .version )
204+ },
205+ }
206+
167207 for _ , a := range appenders {
168208 if err := a (& ignConf , mc ); err != nil {
169209 return nil , err
@@ -224,3 +264,116 @@ func kubeconfigFromSecret(secretDir, apiserverURL string, caData []byte) ([]byte
224264 }
225265 return kcData , caData , nil
226266}
267+
268+ // Finds the MOSC that targets this MCO and verfies that the MOSC has an image in status
269+ // locates the matching MOSB for the MCP's current or next rendered MC and confirms build succeeded
270+ // and returns the image pullspec when its ready
271+ func (cs * clusterServer ) resolveDesiredImageForPool (pool * mcfgv1.MachineConfigPool ) string {
272+ // If listers are not initialized (e.g., in tests or clusters without layering), return empty
273+ if cs .machineOSConfigLister == nil || cs .machineOSBuildLister == nil {
274+ return ""
275+ }
276+
277+ // Find MachineOSConfig for this pool
278+ moscList , err := cs .machineOSConfigLister .List (labels .Everything ())
279+ if err != nil {
280+ // MOSC resources not available
281+ klog .Infof ("Could not list MachineOSConfigs for pool %s: %v" , pool .Name , err )
282+ return ""
283+ }
284+
285+ // TODO(dkhater): Simplify to directly get MOSC using pool name with admin_ack enforcing MOSC name == pool name.
286+ // Versions 4.18.21-4.18.23 lack OCPBUGS-60904 validation (MOSCs could have non-matching names).
287+ // Can use admin_ack (e.g., at 4.23) to block upgrades until MOSCs are corrected, then replace List+filter with: mosc, err := cs.machineOSConfigLister.Get(pool.Name)
288+ // See: https://issues.redhat.com/browse/OCPBUGS-60904 for validation bug.
289+ // See: https://issues.redhat.com/browse/MCO-1935 for cleanup story.
290+
291+ var mosc * mcfgv1.MachineOSConfig
292+ for _ , config := range moscList {
293+ if config .Spec .MachineConfigPool .Name == pool .Name {
294+ mosc = config
295+ break
296+ }
297+ }
298+
299+ // No MOSC for this pool - not layered
300+ if mosc == nil {
301+ return ""
302+ }
303+
304+ // Check if image is ready in MOSC status
305+ moscState := ctrlcommon .NewMachineOSConfigState (mosc )
306+ if ! moscState .HasOSImage () {
307+ klog .Infof ("Pool %s has MachineOSConfig but image not ready yet" , pool .Name )
308+ return ""
309+ }
310+
311+ // Also verify the corresponding MOSB is successful to ensure we don't serve an image that hasn't been built yet
312+ mosbList , err := cs .machineOSBuildLister .List (labels .Everything ())
313+ if err != nil {
314+ klog .Infof ("Could not list MachineOSBuilds for pool %s: %v" , pool .Name , err )
315+ return ""
316+ }
317+
318+ var currentConf string
319+ if pool .Status .UpdatedMachineCount > 0 {
320+ currentConf = pool .Spec .Configuration .Name
321+ } else {
322+ currentConf = pool .Status .Configuration .Name
323+ }
324+
325+ var mosb * mcfgv1.MachineOSBuild
326+ for _ , build := range mosbList {
327+ if build .Spec .MachineOSConfig .Name == mosc .Name &&
328+ build .Spec .MachineConfig .Name == currentConf {
329+ mosb = build
330+ break
331+ }
332+ }
333+
334+ if mosb == nil {
335+ klog .Infof ("Pool %s has MachineOSConfig but no matching MachineOSBuild for MC %s" , pool .Name , currentConf )
336+ return ""
337+ }
338+
339+ // Check build is successful
340+ mosbState := ctrlcommon .NewMachineOSBuildState (mosb )
341+ if ! mosbState .IsBuildSuccess () {
342+ klog .Infof ("Pool %s has MachineOSBuild but build not successful yet" , pool .Name )
343+ return ""
344+ }
345+
346+ imageSpec := moscState .GetOSImage ()
347+
348+ // Don't serve internal registry images during bootstrap because cluster DNS is not available yet
349+ // New nodes will bootstrap with base image, then update to layered image after joining cluster (2 reboots)
350+ // External registries (Quay, etc.) will still get the 1-reboot optimization
351+ isInternal , err := ctrlcommon .IsOpenShiftRegistry (context .TODO (), imageSpec , cs .kubeclient , cs .routeclient )
352+ if err != nil {
353+ klog .Errorf ("Failed to check if image is internal registry for pool %s: %v" , pool .Name , err )
354+ return ""
355+ }
356+
357+ if isInternal {
358+ klog .V (4 ).Infof ("New nodes will bootstrap with base image, then update to layered image after joining cluster" )
359+ return ""
360+ }
361+
362+ klog .Infof ("Resolved layered image for pool %s: %s" , pool .Name , imageSpec )
363+ return imageSpec
364+ }
365+
366+ // appendDesiredOSImage mutates the MC to include the desired OS image.
367+ // This runs appendEncapsulated so mcd-firstboot sees the image on first boot.
368+ func appendDesiredOSImage (desired string ) appenderFunc {
369+ return func (_ * ign3types.Config , mc * mcfgv1.MachineConfig ) error {
370+ if desired == "" {
371+ return nil
372+ }
373+ if mc .Spec .OSImageURL != desired {
374+ klog .Infof ("overriding MC OSImageURL: %q -> %q" , mc .Spec .OSImageURL , desired )
375+ mc .Spec .OSImageURL = desired
376+ }
377+ return nil
378+ }
379+ }
0 commit comments