Skip to content

Commit e355ad3

Browse files
Merge pull request #404 from nrb/OCPBUGS-59251
OCPBUGS-59251: Default cloud.conf if no configmap is found
2 parents 86f8c6d + e82e1b3 commit e355ad3

4 files changed

Lines changed: 87 additions & 34 deletions

File tree

pkg/cloud/aws/aws_config_transformer.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ import (
1313
awsconfig "k8s.io/cloud-provider-aws/pkg/providers/v1/config"
1414
)
1515

16+
// defaultConfig is a string holding the absolute bare minimum INI string that the AWS CCM needs to start.
17+
// The value will be further customized for OCP in the CloudConfigTransformer.
18+
const defaultConfig = `[Global]
19+
`
20+
1621
// CloudConfigTransformer is used to inject OpenShift configuration defaults into the Cloud Provider config
17-
// for the AWS Cloud Provider.
22+
// for the AWS Cloud Provider. If an empty source string is provided, a minimal default configuration will be created.
1823
func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) {
1924
cfg, err := readAWSConfig(source)
2025
if err != nil {
@@ -26,13 +31,19 @@ func CloudConfigTransformer(source string, infra *configv1.Infrastructure, netwo
2631
return marshalAWSConfig(cfg)
2732
}
2833

34+
// readAWSConfig will parse a source string into a proper *awsconfig.CloudConfig.
35+
// If an empty source string is provided, a default configuration will be used.
2936
func readAWSConfig(source string) (*awsconfig.CloudConfig, error) {
37+
cfg := &awsconfig.CloudConfig{}
38+
39+
// There are cases in which a cloud config was not installed with a cluster, and this is valid.
40+
// We should, however, populate the configuration so that it doesn't fail on later versions that require
41+
// a cloud.conf.
3042
if len(source) == 0 {
31-
return nil, fmt.Errorf("empty INI file")
43+
source = defaultConfig
3244
}
3345

3446
// Use the same method the AWS CCM uses to load configuration.
35-
cfg := &awsconfig.CloudConfig{}
3647
if err := gcfg.FatalOnly(gcfg.ReadStringInto(cfg, source)); err != nil {
3748
return nil, fmt.Errorf("failed to parse INI file: %w", err)
3849
}

pkg/cloud/aws/aws_config_transformer_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,22 @@ func TestCloudConfigTransformer(t *testing.T) {
1313
expected string
1414
}{
1515
{
16-
name: "empty source",
16+
name: "default source",
1717
source: `[Global]
1818
`, // This is the default that gets created for any OpenShift Cluster.
1919
expected: `[Global]
2020
DisableSecurityGroupIngress = false
2121
ClusterServiceLoadBalancerHealthProbeMode = Shared
2222
ClusterServiceSharedLoadBalancerHealthProbePort = 0
23+
`,
24+
},
25+
{
26+
name: "completely empty source",
27+
source: "", // This could happen in cases where the cluster was born prior to a cloud.conf being required.
28+
expected: `[Global]
29+
DisableSecurityGroupIngress = false
30+
ClusterServiceLoadBalancerHealthProbeMode = Shared
31+
ClusterServiceSharedLoadBalancerHealthProbePort = 0
2332
`,
2433
},
2534
{

pkg/controllers/cloud_config_sync_controller.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,16 @@ func (r *CloudConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
108108
}
109109
}
110110

111-
if !managedConfigFound {
111+
// Only look for an unmanaged config if the managed one isn't found and a name was specified.
112+
if !managedConfigFound && infra.Spec.CloudConfig.Name != "" {
112113
openshiftUnmanagedCMKey := client.ObjectKey{
113114
Name: infra.Spec.CloudConfig.Name,
114115
Namespace: OpenshiftConfigNamespace,
115116
}
116-
if err := r.Get(ctx, openshiftUnmanagedCMKey, sourceCM); err != nil {
117-
klog.Errorf("unable to get cloud-config for sync")
117+
if err := r.Get(ctx, openshiftUnmanagedCMKey, sourceCM); errors.IsNotFound(err) {
118+
klog.Warningf("managed cloud-config is not found, falling back to default cloud config.")
119+
} else if err != nil {
120+
klog.Errorf("unable to get cloud-config for sync: %v", err)
118121
if err := r.setDegradedCondition(ctx); err != nil {
119122
return ctrl.Result{}, fmt.Errorf("failed to set conditions for cloud config controller: %v", err)
120123
}
@@ -188,40 +191,57 @@ func (r *CloudConfigReconciler) isCloudConfigSyncNeeded(platformStatus *configv1
188191
return false, fmt.Errorf("platformStatus is required")
189192
}
190193
switch platformStatus.Type {
191-
case configv1.AzurePlatformType,
194+
case configv1.AWSPlatformType,
195+
configv1.AzurePlatformType,
192196
configv1.GCPPlatformType,
193197
configv1.VSpherePlatformType,
194198
configv1.IBMCloudPlatformType,
195199
configv1.PowerVSPlatformType,
196200
configv1.OpenStackPlatformType,
197201
configv1.NutanixPlatformType:
198202
return true, nil
199-
case configv1.AWSPlatformType:
200-
// Some of AWS regions might require to sync a cloud-config, in such case reference in infra resource will be presented
201-
return infraCloudConfigRef.Name != "", nil
202203
default:
203204
return false, nil
204205
}
205206
}
206207

208+
// prepareSourceConfigMap creates a usable ConfigMap for further processing into a cloud.conf file.
207209
func (r *CloudConfigReconciler) prepareSourceConfigMap(source *corev1.ConfigMap, infra *configv1.Infrastructure) (*corev1.ConfigMap, error) {
210+
if source == nil {
211+
return nil, fmt.Errorf("received empty configmap for cloud config")
212+
}
213+
cloudConfCm := source.DeepCopy()
214+
// We might have an empty ConfigMap in clusters created before 4.14.
215+
if cloudConfCm.Data == nil {
216+
cloudConfCm.Data = make(map[string]string)
217+
}
218+
208219
// Keys might be different between openshift-config/cloud-config and openshift-config-managed/kube-cloud-config
209220
// Always use "cloud.conf" which is default one across openshift
210-
cloudConfCm := source.DeepCopy()
211221
if _, ok := cloudConfCm.Data[defaultConfigKey]; ok {
212222
return cloudConfCm, nil
223+
} else {
224+
// Make an entry for the default key even if it didn't exist.
225+
cloudConfCm.Data[defaultConfigKey] = ""
213226
}
214227

228+
// If a user provides their own cloud config...
215229
infraConfigKey := infra.Spec.CloudConfig.Key
216-
if val, ok := cloudConfCm.Data[infraConfigKey]; ok {
217-
cloudConfCm.Data[defaultConfigKey] = val
218-
delete(cloudConfCm.Data, infraConfigKey)
219-
return cloudConfCm, nil
230+
if infraConfigKey != "" {
231+
if val, ok := cloudConfCm.Data[infraConfigKey]; ok {
232+
// ..., copy that over into the default key.
233+
cloudConfCm.Data[defaultConfigKey] = val
234+
delete(cloudConfCm.Data, infraConfigKey)
235+
return cloudConfCm, nil
236+
} else if !ok {
237+
// Return an error if they provided a non-existent one and there was a cloud.conf specified.
238+
return nil, fmt.Errorf("key %s specified in infra resource does not exist in source configmap %s",
239+
infraConfigKey, client.ObjectKeyFromObject(source),
240+
)
241+
}
220242
}
221-
return nil, fmt.Errorf(
222-
"key %s specified in infra resource does not found in source configmap %s",
223-
infraConfigKey, client.ObjectKeyFromObject(source),
224-
)
243+
244+
return cloudConfCm, nil
225245
}
226246

227247
func (r *CloudConfigReconciler) isCloudConfigEqual(source *corev1.ConfigMap, target *corev1.ConfigMap) bool {

pkg/controllers/cloud_config_sync_controller_test.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const (
2828
infraCloudConfKey = "foo"
2929

3030
defaultAzureConfig = `{"cloud":"AzurePublicCloud","tenantId":"0000000-0000-0000-0000-000000000000","Entries":null,"subscriptionId":"0000000-0000-0000-0000-000000000000","vmType":"standard","putVMSSVMBatchSize":0,"enableMigrateToIPBasedBackendPoolAPI":false,"clusterServiceLoadBalancerHealthProbeMode":"shared"}`
31+
defaultAWSConfig = `[Global]
32+
`
3133
)
3234

3335
func makeInfrastructureResource(platform configv1.PlatformType) *configv1.Infrastructure {
@@ -84,8 +86,7 @@ func makeInfraStatus(platform configv1.PlatformType) configv1.InfrastructureStat
8486
}
8587

8688
func makeInfraCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
87-
defaultConfig := `[Global]
88-
`
89+
defaultConfig := defaultAWSConfig
8990

9091
if platform == configv1.AzurePlatformType {
9192
defaultConfig = defaultAzureConfig
@@ -98,8 +99,7 @@ func makeInfraCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
9899
}
99100

100101
func makeManagedCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
101-
defaultConfig := `[Global]
102-
`
102+
defaultConfig := defaultAWSConfig
103103

104104
if platform == configv1.AzurePlatformType {
105105
defaultConfig = defaultAzureConfig
@@ -151,14 +151,6 @@ var _ = Describe("prepareSourceConfigMap reconciler method", func() {
151151
Expect(reconciler.isCloudConfigEqual(preparedConfig, managedCloudConfig)).Should(BeTrue())
152152
})
153153

154-
It("config preparation should fail if key from infra resource does not found", func() {
155-
brokenInfraConfig := infraCloudConfig.DeepCopy()
156-
brokenInfraConfig.Data = map[string]string{"hehehehehe": "bar"}
157-
_, err := reconciler.prepareSourceConfigMap(brokenInfraConfig, infra)
158-
Expect(err).Should(Not(Succeed()))
159-
Expect(err.Error()).Should(BeEquivalentTo("key foo specified in infra resource does not found in source configmap openshift-config/test-config"))
160-
})
161-
162154
It("config preparation should not touch extra fields in infra ConfigMap", func() {
163155
extendedInfraConfig := infraCloudConfig.DeepCopy()
164156
extendedInfraConfig.Data = map[string]string{infraCloudConfKey: "{}", "{}": "{}"}
@@ -467,21 +459,29 @@ var _ = Describe("Cloud config sync reconciler", func() {
467459
Expect(cl.Create(ctx, makeInfraCloudConfig(configv1.AWSPlatformType))).To(Succeed())
468460
})
469461

470-
It("should skip config sync for AWS platform if there is no reference in infra resource", func() {
462+
It("should sync a default config AWS platform if there is no reference in infra resource", func() {
471463
infraResource := makeInfrastructureResource(configv1.AWSPlatformType)
472464
infraResource.Spec.CloudConfig.Name = ""
465+
infraResource.Spec.CloudConfig.Key = ""
473466
Expect(cl.Create(ctx, infraResource)).To(Succeed())
474467

475468
infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type)
476469
Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed())
477470

471+
fetchedResource := &configv1.Infrastructure{}
472+
Expect(cl.Get(ctx, client.ObjectKeyFromObject(infraResource), fetchedResource)).To(Succeed())
473+
Expect(fetchedResource.Spec.CloudConfig.Name).To(Equal(""))
474+
478475
_, err := reconciler.Reconcile(context.TODO(), ctrl.Request{})
479476
Expect(err).To(BeNil())
480477

481478
allCMs := &corev1.ConfigMapList{}
482479
Expect(cl.List(ctx, allCMs, &client.ListOptions{Namespace: targetNamespaceName})).To(Succeed())
483480

484-
Expect(len(allCMs.Items)).To(BeZero())
481+
Expect(len(allCMs.Items)).To(BeEquivalentTo(1))
482+
// Our code ensures that there is at a minimum a global section.
483+
// The CCM itself may end up defaulting values for us, so don't use exact string matching.
484+
Expect(allCMs.Items[0].Data[defaultConfigKey]).To(HavePrefix(defaultAWSConfig))
485485
})
486486

487487
It("should perform config sync for AWS platform if there is a reference in infra resource", func() {
@@ -500,6 +500,19 @@ var _ = Describe("Cloud config sync reconciler", func() {
500500
Expect(len(allCMs.Items)).NotTo(BeZero())
501501
Expect(len(allCMs.Items)).To(BeEquivalentTo(1))
502502
})
503+
504+
It("should error if a user-specified configmap key isn't present", func() {
505+
infraResource := makeInfrastructureResource(configv1.AWSPlatformType)
506+
infraResource.Spec.CloudConfig.Key = "notfound"
507+
Expect(cl.Create(ctx, infraResource)).To(Succeed())
508+
509+
infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type)
510+
Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed())
511+
512+
_, err := reconciler.Reconcile(context.TODO(), ctrl.Request{})
513+
Expect(err.Error()).To(ContainSubstring("specified in infra resource does not exist in source configmap"))
514+
515+
})
503516
})
504517

505518
Context("On Azure platform", func() {

0 commit comments

Comments
 (0)