Skip to content

Commit 1bcbc37

Browse files
Merge pull request openshift#2668 from JoelSpeed/bootstrap-featuregate-sync
[OCPCLOUD-1209] Run KubeletConfig FeatureGate sync during bootstrap
2 parents 6fe9d7c + 46b6ace commit 1bcbc37

File tree

7 files changed

+243
-80
lines changed

7 files changed

+243
-80
lines changed

pkg/controller/bootstrap/bootstrap.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
apicfgv1 "github.com/openshift/api/config/v1"
2121
apioperatorsv1alpha1 "github.com/openshift/api/operator/v1alpha1"
2222
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
23+
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
2324
containerruntimeconfig "github.com/openshift/machine-config-operator/pkg/controller/container-runtime-config"
25+
kubeletconfig "github.com/openshift/machine-config-operator/pkg/controller/kubelet-config"
2426
"github.com/openshift/machine-config-operator/pkg/controller/render"
2527
"github.com/openshift/machine-config-operator/pkg/controller/template"
2628
)
@@ -46,6 +48,7 @@ func New(templatesDir, manifestDir, pullSecretFile string) *Bootstrap {
4648

4749
// Run runs boostrap for Machine Config Controller
4850
// It writes all the assets to destDir
51+
// nolint:gocyclo
4952
func (b *Bootstrap) Run(destDir string) error {
5053
infos, err := ioutil.ReadDir(b.manifestDir)
5154
if err != nil {
@@ -70,6 +73,7 @@ func (b *Bootstrap) Run(destDir string) error {
7073
decoder := codecFactory.UniversalDecoder(mcfgv1.GroupVersion, apioperatorsv1alpha1.GroupVersion, apicfgv1.GroupVersion)
7174

7275
var cconfig *mcfgv1.ControllerConfig
76+
var featureGate *apicfgv1.FeatureGate
7377
var pools []*mcfgv1.MachineConfigPool
7478
var configs []*mcfgv1.MachineConfig
7579
var icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy
@@ -112,6 +116,10 @@ func (b *Bootstrap) Run(destDir string) error {
112116
icspRules = append(icspRules, obj)
113117
case *apicfgv1.Image:
114118
imgCfg = obj
119+
case *apicfgv1.FeatureGate:
120+
if obj.GetName() == ctrlcommon.ClusterFeatureInstanceName {
121+
featureGate = obj
122+
}
115123
default:
116124
glog.Infof("skipping %q [%d] manifest because of unhandled %T", file.Name(), idx+1, obji)
117125
}
@@ -121,7 +129,7 @@ func (b *Bootstrap) Run(destDir string) error {
121129
if cconfig == nil {
122130
return fmt.Errorf("error: no controllerconfig found in dir: %q", destDir)
123131
}
124-
iconfigs, err := template.RunBootstrap(b.templatesDir, cconfig, psraw)
132+
iconfigs, err := template.RunBootstrap(b.templatesDir, cconfig, psraw, featureGate)
125133
if err != nil {
126134
return err
127135
}
@@ -133,6 +141,14 @@ func (b *Bootstrap) Run(destDir string) error {
133141
}
134142
configs = append(configs, rconfigs...)
135143

144+
if featureGate != nil {
145+
kConfigs, err := kubeletconfig.RunFeatureGateBootstrap(b.templatesDir, featureGate, cconfig, pools)
146+
if err != nil {
147+
return err
148+
}
149+
configs = append(configs, kConfigs...)
150+
}
151+
136152
fpools, gconfigs, err := render.RunBootstrap(pools, configs, cconfig)
137153
if err != nil {
138154
return err

pkg/controller/bootstrap/bootstrap_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ spec:
4444
want: []manifest{{
4545
Raw: []byte(`{"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"name":"test-ingress","namespace":"test-namespace"},"spec":{"rules":[{"http":{"paths":[{"backend":{"serviceName":"test","servicePort":80},"path":"/testpath"}]}}]}}`),
4646
}},
47+
}, {
48+
name: "feature gate",
49+
raw: `
50+
apiVersion: config.openshift.io/v1
51+
kind: FeatureGate
52+
metadata:
53+
name: cluster
54+
spec:
55+
featureSet: TechPreviewNoUpgrade
56+
`,
57+
want: []manifest{{
58+
Raw: []byte(`{"apiVersion":"config.openshift.io/v1","kind":"FeatureGate","metadata":{"name":"cluster"},"spec":{"featureSet":"TechPreviewNoUpgrade"}}`),
59+
}},
4760
}, {
4861
name: "two-resources",
4962
raw: `

pkg/controller/kubelet-config/helpers.go

+10
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,13 @@ func newKubeletconfigJSONEncoder(targetVersion schema.GroupVersion) (runtime.Enc
311311
}
312312
return codecs.EncoderForVersion(info.Serializer, targetVersion), nil
313313
}
314+
315+
// kubeletConfigToIgnFile converts a KubeletConfiguration to an Ignition File
316+
func kubeletConfigToIgnFile(cfg *kubeletconfigv1beta1.KubeletConfiguration) (*ign3types.File, error) {
317+
cfgJSON, err := EncodeKubeletConfig(cfg, kubeletconfigv1beta1.SchemeGroupVersion)
318+
if err != nil {
319+
return nil, fmt.Errorf("could not encode kubelet configuration: %v", err)
320+
}
321+
cfgIgn := createNewKubeletIgnition(cfgJSON)
322+
return cfgIgn, nil
323+
}

pkg/controller/kubelet-config/kubelet_config_controller.go

+48-39
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,43 @@ func (ctrl *Controller) handleFeatureErr(err error, key interface{}) {
330330
ctrl.featureQueue.AddAfter(key, 1*time.Minute)
331331
}
332332

333-
func (ctrl *Controller) generateOriginalKubeletConfig(role string, featureGate *configv1.FeatureGate) (*ign3types.File, error) {
334-
cc, err := ctrl.ccLister.Get(ctrlcommon.ControllerConfigName)
333+
// generateOriginalKubeletConfigWithFeatureGates generates a KubeletConfig and ensure the correct feature gates are set
334+
// based on the given FeatureGate.
335+
func generateOriginalKubeletConfigWithFeatureGates(cc *mcfgv1.ControllerConfig, templatesDir, role string, features *configv1.FeatureGate) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
336+
originalKubeletIgn, err := generateOriginalKubeletConfigIgn(cc, templatesDir, role, features)
335337
if err != nil {
336-
return nil, fmt.Errorf("could not get ControllerConfig %v", err)
338+
return nil, fmt.Errorf("could not generate the original Kubelet config ignition: %v", err)
337339
}
340+
if originalKubeletIgn.Contents.Source == nil {
341+
return nil, fmt.Errorf("the original Kubelet source string is empty: %v", err)
342+
}
343+
dataURL, err := dataurl.DecodeString(*originalKubeletIgn.Contents.Source)
344+
if err != nil {
345+
return nil, fmt.Errorf("could not decode the original Kubelet source string: %v", err)
346+
}
347+
originalKubeConfig, err := decodeKubeletConfig(dataURL.Data)
348+
if err != nil {
349+
return nil, fmt.Errorf("could not deserialize the Kubelet source: %v", err)
350+
}
351+
352+
featureGates, err := generateFeatureMap(features, openshiftOnlyFeatureGates...)
353+
if err != nil {
354+
return nil, fmt.Errorf("could not generate features map: %v", err)
355+
}
356+
357+
// Merge in Feature Gates.
358+
// If they are the same, this will be a no-op
359+
if err := mergo.Merge(&originalKubeConfig.FeatureGates, featureGates, mergo.WithOverride); err != nil {
360+
return nil, fmt.Errorf("could not merge feature gates: %v", err)
361+
}
362+
363+
return originalKubeConfig, nil
364+
}
365+
366+
func generateOriginalKubeletConfigIgn(cc *mcfgv1.ControllerConfig, templatesDir, role string, featureGate *configv1.FeatureGate) (*ign3types.File, error) {
338367
// Render the default templates
339368
rc := &mtmpl.RenderConfig{ControllerConfigSpec: &cc.Spec, FeatureGate: featureGate}
340-
generatedConfigs, err := mtmpl.GenerateMachineConfigsForRole(rc, role, ctrl.templatesDir)
369+
generatedConfigs, err := mtmpl.GenerateMachineConfigsForRole(rc, role, templatesDir)
341370
if err != nil {
342371
return nil, fmt.Errorf("GenerateMachineConfigsforRole failed with error %s", err)
343372
}
@@ -478,12 +507,6 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
478507
err := fmt.Errorf("could not fetch FeatureGates: %v", err)
479508
return ctrl.syncStatusOnly(cfg, err)
480509
}
481-
featureGates, err := generateFeatureMap(features)
482-
if err != nil {
483-
err := fmt.Errorf("could not generate FeatureMap: %v", err)
484-
glog.V(2).Infof("%v", err)
485-
return ctrl.syncStatusOnly(cfg, err)
486-
}
487510

488511
for _, pool := range mcpPools {
489512
if pool.Spec.Configuration.Name == "" {
@@ -512,20 +535,14 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
512535
userDefinedSystemReserved := make(map[string]string, 2)
513536

514537
// Generate the original KubeletConfig
515-
originalKubeletIgn, err := ctrl.generateOriginalKubeletConfig(role, features)
516-
if err != nil {
517-
return ctrl.syncStatusOnly(cfg, err, "could not generate the original Kubelet config: %v", err)
518-
}
519-
if originalKubeletIgn.Contents.Source == nil {
520-
return ctrl.syncStatusOnly(cfg, err, "the original Kubelet source string is empty: %v", err)
521-
}
522-
dataURL, err := dataurl.DecodeString(*originalKubeletIgn.Contents.Source)
538+
cc, err := ctrl.ccLister.Get(ctrlcommon.ControllerConfigName)
523539
if err != nil {
524-
return ctrl.syncStatusOnly(cfg, err, "could not decode the original Kubelet source string: %v", err)
540+
return fmt.Errorf("could not get ControllerConfig %v", err)
525541
}
526-
originalKubeConfig, err := decodeKubeletConfig(dataURL.Data)
542+
543+
originalKubeConfig, err := generateOriginalKubeletConfigWithFeatureGates(cc, ctrl.templatesDir, role, features)
527544
if err != nil {
528-
return ctrl.syncStatusOnly(cfg, err, "could not deserialize the Kubelet source: %v", err)
545+
return ctrl.syncStatusOnly(cfg, err, "could not get original kubelet config: %v", err)
529546
}
530547

531548
// Get the default API Server Security Profile
@@ -561,29 +578,21 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
561578
delete(specKubeletConfig.SystemReserved, "cpu")
562579
}
563580

581+
// FeatureGates must be set from the FeatureGate.
582+
// Remove them here to prevent the specKubeletConfig merge overwriting them.
583+
specKubeletConfig.FeatureGates = nil
584+
564585
// Merge the Old and New
565586
err = mergo.Merge(originalKubeConfig, specKubeletConfig, mergo.WithOverride)
566587
if err != nil {
567588
return ctrl.syncStatusOnly(cfg, err, "could not merge original config and new config: %v", err)
568589
}
569-
// Merge in Feature Gates
570-
err = mergo.Merge(&originalKubeConfig.FeatureGates, featureGates, mergo.WithOverride)
571-
if err != nil {
572-
return ctrl.syncStatusOnly(cfg, err, "could not merge FeatureGates: %v", err)
573-
}
574-
// Encode the new config into raw JSON
575-
cfgJSON, err := EncodeKubeletConfig(originalKubeConfig, kubeletconfigv1beta1.SchemeGroupVersion)
576-
if err != nil {
577-
return ctrl.syncStatusOnly(cfg, err, "could not encode JSON: %v", err)
578-
}
579-
kubeletIgnition = createNewKubeletIgnition(cfgJSON)
580-
} else {
581-
// Encode the new config into raw JSON
582-
cfgJSON, err := EncodeKubeletConfig(originalKubeConfig, kubeletconfigv1beta1.SchemeGroupVersion)
583-
if err != nil {
584-
return ctrl.syncStatusOnly(cfg, err, "could not encode JSON: %v", err)
585-
}
586-
kubeletIgnition = createNewKubeletIgnition(cfgJSON)
590+
}
591+
592+
// Encode the new config into an Ignition File
593+
kubeletIgnition, err = kubeletConfigToIgnFile(originalKubeConfig)
594+
if err != nil {
595+
return ctrl.syncStatusOnly(cfg, err, "could not encode JSON: %v", err)
587596
}
588597

589598
if isNotFound {

pkg/controller/kubelet-config/kubelet_config_features.go

+75-37
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@ import (
88

99
"github.com/clarketm/json"
1010
"github.com/golang/glog"
11-
"github.com/imdario/mergo"
1211
osev1 "github.com/openshift/api/config/v1"
1312
"github.com/openshift/library-go/pkg/cloudprovider"
14-
"github.com/vincent-petithory/dataurl"
1513
"k8s.io/apimachinery/pkg/api/errors"
1614
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1715
"k8s.io/apimachinery/pkg/labels"
1816
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1917
"k8s.io/client-go/tools/cache"
2018
"k8s.io/client-go/util/retry"
21-
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
2219

20+
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
2321
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
2422
"github.com/openshift/machine-config-operator/pkg/version"
2523
)
@@ -69,9 +67,10 @@ func (ctrl *Controller) syncFeatureHandler(key string) error {
6967
} else if err != nil {
7068
return err
7169
}
72-
featureGates, err := generateFeatureMap(features, openshiftOnlyFeatureGates...)
70+
71+
cc, err := ctrl.ccLister.Get(ctrlcommon.ControllerConfigName)
7372
if err != nil {
74-
return err
73+
return fmt.Errorf("could not get ControllerConfig %v", err)
7574
}
7675

7776
// Find all MachineConfigPools
@@ -100,43 +99,15 @@ func (ctrl *Controller) syncFeatureHandler(key string) error {
10099
return err
101100
}
102101
}
103-
// Generate the original KubeletConfig
104-
originalKubeletIgn, err := ctrl.generateOriginalKubeletConfig(role, nil)
105-
if err != nil {
106-
return err
107-
}
108-
if originalKubeletIgn.Contents.Source == nil {
109-
return fmt.Errorf("could not find original Kubelet config to decode")
110-
}
111-
dataURL, err := dataurl.DecodeString(*originalKubeletIgn.Contents.Source)
112-
if err != nil {
113-
return err
114-
}
115-
originalKubeConfig, err := decodeKubeletConfig(dataURL.Data)
102+
103+
rawCfgIgn, err := generateKubeConfigIgnFromFeatures(cc, ctrl.templatesDir, role, features)
116104
if err != nil {
117105
return err
118106
}
119-
// Check to see if FeatureGates are equal
120-
if reflect.DeepEqual(originalKubeConfig.FeatureGates, *featureGates) {
107+
if rawCfgIgn == nil {
121108
continue
122109
}
123-
// Merge in Feature Gates
124-
err = mergo.Merge(&originalKubeConfig.FeatureGates, featureGates, mergo.WithOverride)
125-
if err != nil {
126-
return err
127-
}
128-
// Encode the new config into raw JSON
129-
cfgJSON, err := EncodeKubeletConfig(originalKubeConfig, kubeletconfigv1beta1.SchemeGroupVersion)
130-
if err != nil {
131-
return err
132-
}
133-
tempIgnConfig := ctrlcommon.NewIgnConfig()
134-
cfgIgn := createNewKubeletIgnition(cfgJSON)
135-
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *cfgIgn)
136-
rawCfgIgn, err := json.Marshal(tempIgnConfig)
137-
if err != nil {
138-
return err
139-
}
110+
140111
mc.Spec.Config.Raw = rawCfgIgn
141112
mc.ObjectMeta.Annotations = map[string]string{
142113
ctrlcommon.GeneratedByControllerVersionAnnotationKey: version.Hash,
@@ -231,3 +202,70 @@ func generateFeatureMap(features *osev1.FeatureGate, exclusions ...string) (*map
231202
}
232203
return &rv, nil
233204
}
205+
206+
func generateKubeConfigIgnFromFeatures(cc *mcfgv1.ControllerConfig, templatesDir, role string, features *osev1.FeatureGate) ([]byte, error) {
207+
originalKubeConfig, err := generateOriginalKubeletConfigWithFeatureGates(cc, templatesDir, role, features)
208+
if err != nil {
209+
return nil, err
210+
}
211+
defaultFeatures, err := generateFeatureMap(createNewDefaultFeatureGate(), openshiftOnlyFeatureGates...)
212+
if err != nil {
213+
return nil, err
214+
}
215+
216+
// Check to see if configured FeatureGates are equivalent to the Default FeatureSet.
217+
if reflect.DeepEqual(originalKubeConfig.FeatureGates, *defaultFeatures) {
218+
// When there is no difference, this isn't an error, but no machine config should be created
219+
return nil, nil
220+
}
221+
222+
// Encode the new config into raw JSON
223+
cfgIgn, err := kubeletConfigToIgnFile(originalKubeConfig)
224+
if err != nil {
225+
return nil, err
226+
}
227+
228+
tempIgnConfig := ctrlcommon.NewIgnConfig()
229+
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *cfgIgn)
230+
rawCfgIgn, err := json.Marshal(tempIgnConfig)
231+
if err != nil {
232+
return nil, err
233+
}
234+
return rawCfgIgn, nil
235+
}
236+
237+
func RunFeatureGateBootstrap(templateDir string, features *osev1.FeatureGate, controllerConfig *mcfgv1.ControllerConfig, mcpPools []*mcfgv1.MachineConfigPool) ([]*mcfgv1.MachineConfig, error) {
238+
machineConfigs := []*mcfgv1.MachineConfig{}
239+
240+
for _, pool := range mcpPools {
241+
role := pool.Name
242+
rawCfgIgn, err := generateKubeConfigIgnFromFeatures(controllerConfig, templateDir, role, features)
243+
if err != nil {
244+
return nil, err
245+
}
246+
if rawCfgIgn == nil {
247+
continue
248+
}
249+
250+
// Get MachineConfig
251+
managedKey, err := getManagedFeaturesKey(pool, nil)
252+
if err != nil {
253+
return nil, err
254+
}
255+
256+
ignConfig := ctrlcommon.NewIgnConfig()
257+
mc, err := ctrlcommon.MachineConfigFromIgnConfig(role, managedKey, ignConfig)
258+
if err != nil {
259+
return nil, err
260+
}
261+
262+
mc.Spec.Config.Raw = rawCfgIgn
263+
mc.ObjectMeta.Annotations = map[string]string{
264+
ctrlcommon.GeneratedByControllerVersionAnnotationKey: version.Hash,
265+
}
266+
267+
machineConfigs = append(machineConfigs, mc)
268+
}
269+
270+
return machineConfigs, nil
271+
}

0 commit comments

Comments
 (0)