Skip to content

Commit a4fbb2c

Browse files
minimizing reboot for scaling image mode enabled node
1 parent c6faace commit a4fbb2c

File tree

5 files changed

+154
-14
lines changed

5 files changed

+154
-14
lines changed

manifests/machineconfigserver/clusterrole.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ rules:
99
- apiGroups: ["machineconfiguration.openshift.io"]
1010
resources: ["controllerconfigs"]
1111
verbs: ["get", "watch", "list"]
12+
- apiGroups: ["machineconfiguration.openshift.io"]
13+
resources: ["machineosconfigs", "machineosbuilds"]
14+
verbs: ["get", "list", "watch"]
1215
- apiGroups: ["security.openshift.io"]
1316
resourceNames: ["hostnetwork"]
1417
resources: ["securitycontextconstraints"]

pkg/daemon/daemon.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,7 @@ func (dn *Daemon) getStateAndConfigs() (*stateAndConfigs, error) {
17121712
}
17131713
return nil, err
17141714
}
1715+
klog.Infof("[daemon.go] getStateAndConfigs: bootstrapping=%v, currentConfigName=%s, currentConfig.Spec.OSImageURL=%s (source=cluster-lister)", bootstrapping, currentConfigName, currentConfig.Spec.OSImageURL)
17151716
state, err := getNodeAnnotationExt(dn.node, constants.MachineConfigDaemonStateAnnotationKey, true)
17161717
if err != nil {
17171718
return nil, err
@@ -1721,11 +1722,13 @@ func (dn *Daemon) getStateAndConfigs() (*stateAndConfigs, error) {
17211722
klog.Infof("%s is not set. any errors? %s", constants.CurrentImageAnnotationKey, err)
17221723
return nil, err
17231724
}
1725+
klog.Infof("[daemon.go] getStateAndConfigs: currentImage=%s (from node annotations)", currentImage)
17241726
desiredImage, err := getNodeAnnotationExt(dn.node, constants.DesiredImageAnnotationKey, true)
17251727
if err != nil {
17261728
klog.Infof("%s is not set. any errors? %s", constants.DesiredImageAnnotationKey, err)
17271729
return nil, err
17281730
}
1731+
klog.Infof("[daemon.go] getStateAndConfigs: desiredImage=%s (from node annotations)", desiredImage)
17291732

17301733
// Temporary hack: the MCS used to not write the state=done annotation
17311734
// key. If it's unset, let's write it now.
@@ -2174,8 +2177,19 @@ func (dn *Daemon) checkStateOnFirstRun() error {
21742177

21752178
// Bootstrapping state is when we have the node annotations file
21762179
if state.bootstrapping {
2177-
targetOSImageURL := state.currentConfig.Spec.OSImageURL
2180+
// targetOSImageURL := state.currentConfig.Spec.OSImageURL
2181+
// klog.Infof("[daemon.go] checkStateOnFirstRun: in bootstrap mode, bootedOSImageURL=%s, targetOSImageURL=%s (from state.currentConfig.Spec.OSImageURL)", dn.bootedOSImageURL, targetOSImageURL)
2182+
2183+
// During bootstrap, prefer the image from node annotations (if set) over the MC from cluster lister
2184+
targetOSImageURL := state.currentImage
2185+
if targetOSImageURL == "" {
2186+
// Fall back to MC's OSImageURL if no image annotation was provided
2187+
targetOSImageURL = state.currentConfig.Spec.OSImageURL
2188+
}
2189+
klog.Infof("[daemon.go] checkStateOnFirstRun: in bootstrap mode, bootedOSImageURL=%s, targetOSImageURL=%s (from state.currentImage annotation or state.currentConfig.Spec.OSImageURL)", dn.bootedOSImageURL,
2190+
targetOSImageURL)
21782191
osMatch := dn.checkOS(targetOSImageURL)
2192+
klog.Infof("[daemon.go] checkStateOnFirstRun: osMatch=%v (bootedOSImageURL == targetOSImageURL)", osMatch)
21792193
if !osMatch {
21802194
logSystem("Bootstrap pivot required to: %s", targetOSImageURL)
21812195

pkg/server/cluster_server.go

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import (
99
"path/filepath"
1010
"time"
1111

12+
ign3types "github.com/coreos/ignition/v2/config/v3_5/types"
1213
yaml "github.com/ghodss/yaml"
14+
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
1315
mcfginformers "github.com/openshift/client-go/machineconfiguration/informers/externalversions"
1416

1517
"github.com/openshift/machine-config-operator/internal/clients"
1618
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
1719
corev1 "k8s.io/api/core/v1"
20+
"k8s.io/apimachinery/pkg/labels"
1821
"k8s.io/apimachinery/pkg/runtime"
1922
"k8s.io/client-go/informers"
2023
corelisterv1 "k8s.io/client-go/listers/core/v1"
@@ -42,6 +45,8 @@ type clusterServer struct {
4245
machineConfigLister v1.MachineConfigLister
4346
controllerConfigLister v1.ControllerConfigLister
4447
configMapLister corelisterv1.ConfigMapLister
48+
machineOSConfigLister v1.MachineOSConfigLister
49+
machineOSBuildLister v1.MachineOSBuildLister
4550

4651
kubeconfigFunc kubeconfigFunc
4752
apiserverURL string
@@ -76,23 +81,27 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
7681
sharedInformerFactory := mcfginformers.NewSharedInformerFactory(machineConfigClient, resyncPeriod()())
7782
kubeNamespacedSharedInformer := informers.NewFilteredSharedInformerFactory(kubeClient, resyncPeriod()(), "openshift-machine-config-operator", nil)
7883

79-
mcpInformer, mcInformer, ccInformer, cmInformer :=
84+
mcpInformer, mcInformer, ccInformer, cmInformer, moscInformer, mosbInformer :=
8085
sharedInformerFactory.Machineconfiguration().V1().MachineConfigPools(),
8186
sharedInformerFactory.Machineconfiguration().V1().MachineConfigs(),
8287
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 :=
88+
kubeNamespacedSharedInformer.Core().V1().ConfigMaps(),
89+
sharedInformerFactory.Machineconfiguration().V1().MachineOSConfigs(),
90+
sharedInformerFactory.Machineconfiguration().V1().MachineOSBuilds()
91+
mcpLister, mcLister, ccLister, cmLister, moscLister, mosbLister := mcpInformer.Lister(), mcInformer.Lister(), ccInformer.Lister(), cmInformer.Lister(), moscInformer.Lister(), mosbInformer.Lister()
92+
mcpListerHasSynced, mcListerHasSynced, ccListerHasSynced, cmListerHasSynced, moscListerHasSynced, mosbListerHasSynced :=
8693
mcpInformer.Informer().HasSynced,
8794
mcInformer.Informer().HasSynced,
8895
ccInformer.Informer().HasSynced,
89-
cmInformer.Informer().HasSynced
96+
cmInformer.Informer().HasSynced,
97+
moscInformer.Informer().HasSynced,
98+
mosbInformer.Informer().HasSynced
9099

91100
var informerStopCh chan struct{}
92101
go sharedInformerFactory.Start(informerStopCh)
93102
go kubeNamespacedSharedInformer.Start(informerStopCh)
94103

95-
if !cache.WaitForCacheSync(informerStopCh, mcpListerHasSynced, mcListerHasSynced, ccListerHasSynced, cmListerHasSynced) {
104+
if !cache.WaitForCacheSync(informerStopCh, mcpListerHasSynced, mcListerHasSynced, ccListerHasSynced, cmListerHasSynced, moscListerHasSynced, mosbListerHasSynced) {
96105
return nil, errors.New("failed to wait for cache sync")
97106
}
98107

@@ -101,6 +110,8 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
101110
machineConfigLister: mcLister,
102111
controllerConfigLister: ccLister,
103112
configMapLister: cmLister,
113+
machineOSConfigLister: moscLister,
114+
machineOSBuildLister: mosbLister,
104115
kubeconfigFunc: func() ([]byte, []byte, error) { return kubeconfigFromSecret(bootstrapTokenDir, apiserverURL, nil) },
105116
apiserverURL: apiserverURL,
106117
}, nil
@@ -163,7 +174,27 @@ func (cs *clusterServer) GetConfig(cr poolRequest) (*runtime.RawExtension, error
163174

164175
addDataAndMaybeAppendToIgnition(caBundleFilePath, cc.Spec.KubeAPIServerServingCAData, &ignConf)
165176
addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf)
166-
appenders := getAppenders(currConf, cr.version, cs.kubeconfigFunc, []string{}, "")
177+
178+
desiredImage := cs.resolveDesiredImageForPool(mp)
179+
klog.Infof("desiredImage is found to be %s", desiredImage)
180+
181+
appenders := []appenderFunc{
182+
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
183+
return appendNodeAnnotations(cfg, currConf, desiredImage)
184+
},
185+
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
186+
return appendKubeConfig(cfg, cs.kubeconfigFunc)
187+
},
188+
appendInitialMachineConfig,
189+
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error { return appendCerts(cfg, []string{}, "") },
190+
// Inject desired image into the MC
191+
appendDesiredOSImage(desiredImage),
192+
// Must be last
193+
func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error {
194+
return appendEncapsulated(cfg, mc, cr.version)
195+
},
196+
}
197+
167198
for _, a := range appenders {
168199
if err := a(&ignConf, mc); err != nil {
169200
return nil, err
@@ -224,3 +255,90 @@ func kubeconfigFromSecret(secretDir, apiserverURL string, caData []byte) ([]byte
224255
}
225256
return kcData, caData, nil
226257
}
258+
259+
// Finds the MOSC that targets this MCO and verfies that the MOSC has an image in status
260+
// locates the matching MOSB for the MCP's current or next rendered MC and confirms build succeeded
261+
// and returns the image pullspec when its ready
262+
func (cs *clusterServer) resolveDesiredImageForPool(pool *mcfgv1.MachineConfigPool) string {
263+
// Find MachineOSConfig for this pool
264+
moscList, err := cs.machineOSConfigLister.List(labels.Everything())
265+
if err != nil {
266+
// MOSC resources not available
267+
klog.Infof("Could not list MachineOSConfigs for pool %s: %v", pool.Name, err)
268+
return ""
269+
}
270+
271+
var mosc *mcfgv1.MachineOSConfig
272+
for _, config := range moscList {
273+
if config.Spec.MachineConfigPool.Name == pool.Name {
274+
mosc = config
275+
break
276+
}
277+
}
278+
279+
// No MOSC for this pool - not layered
280+
if mosc == nil {
281+
return ""
282+
}
283+
284+
// Check if image is ready in MOSC status
285+
moscState := ctrlcommon.NewMachineOSConfigState(mosc)
286+
if !moscState.HasOSImage() {
287+
klog.Infof("Pool %s has MachineOSConfig but image not ready yet", pool.Name)
288+
return ""
289+
}
290+
291+
// Also verify the corresponding MOSB is successful to ensure we don't serve an image that hasn't been built yet
292+
mosbList, err := cs.machineOSBuildLister.List(labels.Everything())
293+
if err != nil {
294+
klog.Infof("Could not list MachineOSBuilds for pool %s: %v", pool.Name, err)
295+
return ""
296+
}
297+
298+
var currentConf string
299+
if pool.Status.UpdatedMachineCount > 0 {
300+
currentConf = pool.Spec.Configuration.Name
301+
} else {
302+
currentConf = pool.Status.Configuration.Name
303+
}
304+
305+
var mosb *mcfgv1.MachineOSBuild
306+
for _, build := range mosbList {
307+
if build.Spec.MachineOSConfig.Name == mosc.Name &&
308+
build.Spec.MachineConfig.Name == currentConf {
309+
mosb = build
310+
break
311+
}
312+
}
313+
314+
if mosb == nil {
315+
klog.Infof("Pool %s has MachineOSConfig but no matching MachineOSBuild for MC %s", pool.Name, currentConf)
316+
return ""
317+
}
318+
319+
// Check build is successful
320+
mosbState := ctrlcommon.NewMachineOSBuildState(mosb)
321+
if !mosbState.IsBuildSuccess() {
322+
klog.Infof("Pool %s has MachineOSBuild but build not successful yet", pool.Name)
323+
return ""
324+
}
325+
326+
imageSpec := moscState.GetOSImage()
327+
klog.Infof("Resolved layered image for pool %s: %s", pool.Name, imageSpec)
328+
return imageSpec
329+
}
330+
331+
// appendDesiredOSImage mutates the MC to include the desired OS image.
332+
// This runs appendEncapsulated so mcd-firstboot sees the image on first boot.
333+
func appendDesiredOSImage(desired string) appenderFunc {
334+
return func(_ *ign3types.Config, mc *mcfgv1.MachineConfig) error {
335+
if desired == "" {
336+
return nil
337+
}
338+
if mc.Spec.OSImageURL != desired {
339+
klog.Infof("overriding MC OSImageURL: %q -> %q", mc.Spec.OSImageURL, desired)
340+
mc.Spec.OSImageURL = desired
341+
}
342+
return nil
343+
}
344+
}

pkg/server/server.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfi
5050
appenders := []appenderFunc{
5151
// append machine annotations file.
5252
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
53-
return appendNodeAnnotations(cfg, currMachineConfig)
53+
return appendNodeAnnotations(cfg, currMachineConfig, "")
5454
},
5555
// append kubeconfig.
5656
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error { return appendKubeConfig(cfg, f) },
@@ -153,8 +153,8 @@ func appendKubeConfig(conf *ign3types.Config, f kubeconfigFunc) error {
153153
return nil
154154
}
155155

156-
func appendNodeAnnotations(conf *ign3types.Config, currConf string) error {
157-
anno, err := getNodeAnnotation(currConf)
156+
func appendNodeAnnotations(conf *ign3types.Config, currConf string, image string) error {
157+
anno, err := getNodeAnnotation(currConf, image)
158158
if err != nil {
159159
return err
160160
}
@@ -165,12 +165,17 @@ func appendNodeAnnotations(conf *ign3types.Config, currConf string) error {
165165
return nil
166166
}
167167

168-
func getNodeAnnotation(conf string) (string, error) {
168+
func getNodeAnnotation(conf string, image string) (string, error) {
169169
nodeAnnotations := map[string]string{
170170
daemonconsts.CurrentMachineConfigAnnotationKey: conf,
171171
daemonconsts.DesiredMachineConfigAnnotationKey: conf,
172172
daemonconsts.MachineConfigDaemonStateAnnotationKey: daemonconsts.MachineConfigDaemonStateDone,
173173
}
174+
// If image is provided, include image annotations
175+
if image != "" {
176+
nodeAnnotations[daemonconsts.CurrentImageAnnotationKey] = image
177+
nodeAnnotations[daemonconsts.DesiredImageAnnotationKey] = image
178+
}
174179
contents, err := json.Marshal(nodeAnnotations)
175180
if err != nil {
176181
return "", fmt.Errorf("could not marshal node annotations, err: %w", err)

pkg/server/server_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func TestBootstrapServer(t *testing.T) {
159159
if err != nil {
160160
t.Fatalf("unexpected error while appending file to ignition: %v", err)
161161
}
162-
anno, err := getNodeAnnotation(mp.Status.Configuration.Name)
162+
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "")
163163
if err != nil {
164164
t.Fatalf("unexpected error while creating annotations err: %v", err)
165165
}
@@ -354,7 +354,7 @@ func TestClusterServer(t *testing.T) {
354354
if err != nil {
355355
t.Fatalf("unexpected error while appending file to ignition: %v", err)
356356
}
357-
anno, err := getNodeAnnotation(mp.Status.Configuration.Name)
357+
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "")
358358
if err != nil {
359359
t.Fatalf("unexpected error while creating annotations err: %v", err)
360360
}

0 commit comments

Comments
 (0)