Skip to content

Commit e159560

Browse files
chores: unify extra config behaviour, mount keeper extra config as a separate file (#156)
1 parent c5c0577 commit e159560

10 files changed

Lines changed: 61 additions & 108 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module github.com/ClickHouse/clickhouse-operator
33
go 1.26.0
44

55
require (
6-
dario.cat/mergo v1.0.2
76
github.com/ClickHouse/clickhouse-go/v2 v2.43.0
87
github.com/blang/semver/v4 v4.0.0
98
github.com/cert-manager/cert-manager v1.20.1
@@ -30,6 +29,7 @@ require (
3029

3130
require (
3231
cel.dev/expr v0.25.1 // indirect
32+
dario.cat/mergo v1.0.2 // indirect
3333
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
3434
github.com/ClickHouse/ch-go v0.71.0 // indirect
3535
github.com/Masterminds/semver/v3 v3.4.0 // indirect

internal/controller/keeper/constants.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ const (
1616
QuorumConfigPath = "/etc/clickhouse-keeper/"
1717
QuorumConfigFileName = "config.yaml"
1818

19-
ConfigPath = QuorumConfigPath + "config.d/"
20-
ConfigFileName = "00-config.yaml"
19+
ConfigPath = QuorumConfigPath + "config.d/"
20+
ConfigFileName = "00-config.yaml"
21+
ExtraConfigFileName = "99-extra-config.yaml"
2122

2223
TLSConfigPath = "/etc/clickhouse-keeper/tls/"
2324
CABundleFilename = "ca-bundle.crt"

internal/controller/keeper/controller.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ func (cc *ClusterController) Reconcile(ctx context.Context, req ctrl.Request) (c
101101
v1.KeeperReplicaID,
102102
replicaState,
103103
](cc, cluster),
104-
ExtraConfig: map[string]any{},
105104
}
106105

107106
return reconciler.sync(ctx, logger)

internal/controller/keeper/controller_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
. "github.com/onsi/ginkgo/v2"
1010
. "github.com/onsi/gomega"
11-
"gopkg.in/yaml.v2"
1211
appsv1 "k8s.io/api/apps/v1"
1312
batchv1 "k8s.io/api/batch/v1"
1413
corev1 "k8s.io/api/core/v1"
@@ -234,11 +233,8 @@ var _ = When("reconciling standalone KeeperCluster resource", Ordered, func() {
234233
}, &configmap)).To(Succeed())
235234

236235
Expect(configmap.Data).To(HaveKey(ConfigFileName))
237-
238-
var config confMap
239-
Expect(yaml.Unmarshal([]byte(configmap.Data[ConfigFileName]), &config)).To(Succeed())
240-
//nolint:forcetypeassert
241-
Expect(config["keeper_server"].(confMap)["coordination_settings"].(confMap)["quorum_reads"]).To(BeTrue())
236+
Expect(configmap.Data).To(HaveKey(ExtraConfigFileName))
237+
Expect(configmap.Data[ExtraConfigFileName]).To(ContainSubstring("quorum_reads"))
242238
})
243239

244240
It("should use security context overrides from spec", func(ctx context.Context) {

internal/controller/keeper/sync.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package keeper
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"maps"
87
"math"
@@ -109,8 +108,6 @@ type keeperReconciler struct {
109108
reconcilerBase
110109

111110
versionProbe chctrl.VersionProbeResult
112-
// Should be populated after reconcileClusterRevisions with parsed extra config.
113-
ExtraConfig map[string]any
114111
// Computed by reconcileActiveReplicaStatus
115112
HorizontalScaleAllowed bool
116113
pvcRevision string
@@ -207,16 +204,7 @@ func (r *keeperReconciler) reconcileClusterRevisions(ctx context.Context, log ct
207204
log.Debug(fmt.Sprintf("observed new CR revision %q", updateRevision))
208205
}
209206

210-
var extraConfig map[string]any
211-
if len(r.Cluster.Spec.Settings.ExtraConfig.Raw) > 0 {
212-
if err := json.Unmarshal(r.Cluster.Spec.Settings.ExtraConfig.Raw, &extraConfig); err != nil {
213-
return nil, fmt.Errorf("unmarshal extra config: %w", err)
214-
}
215-
216-
r.ExtraConfig = extraConfig
217-
}
218-
219-
configRevision, err := getConfigurationRevision(r.Cluster, r.ExtraConfig)
207+
configRevision, err := getConfigurationRevision(r.Cluster)
220208
if err != nil {
221209
return nil, fmt.Errorf("get configuration revision: %w", err)
222210
}
@@ -761,7 +749,7 @@ func (r *keeperReconciler) updateReplica(ctx context.Context, log ctrlutil.Logge
761749
log = log.With("replica_id", replicaID)
762750
log.Info("updating replica")
763751

764-
configMap, err := templateConfigMap(r.Cluster, r.ExtraConfig, replicaID)
752+
configMap, err := templateConfigMap(r.Cluster, replicaID)
765753
if err != nil {
766754
return nil, fmt.Errorf("template replica %q ConfigMap: %w", replicaID, err)
767755
}

internal/controller/keeper/sync_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ func setupReconciler() (util.Logger, *keeperReconciler, context.CancelFunc) {
210210
StatefulSetRevision: "sts-v1",
211211
},
212212
}),
213-
ExtraConfig: map[string]any{},
214213
}
215214

216215
eventContext, cancel := context.WithCancel(context.Background())

internal/controller/keeper/templates.go

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"strconv"
88
"strings"
99

10-
"dario.cat/mergo"
1110
"gopkg.in/yaml.v2"
1211
appsv1 "k8s.io/api/apps/v1"
1312
corev1 "k8s.io/api/core/v1"
@@ -204,13 +203,13 @@ type keeperServer struct {
204203
HTTPControl httpControl `yaml:"http_control"`
205204
}
206205

207-
func getConfigurationRevision(cr *v1.KeeperCluster, extraConfig map[string]any) (string, error) {
208-
config, err := generateConfigForSingleReplica(cr, extraConfig, 0)
206+
func getConfigurationRevision(cr *v1.KeeperCluster) (string, error) {
207+
cfg, err := generateConfigForSingleReplica(cr, 0)
209208
if err != nil {
210209
return "", fmt.Errorf("generate template configuration: %w", err)
211210
}
212211

213-
hash, err := controllerutil.DeepHashObject(config)
212+
hash, err := controllerutil.DeepHashObject(cfg)
214213
if err != nil {
215214
return "", fmt.Errorf("hash template configuration: %w", err)
216215
}
@@ -234,8 +233,8 @@ func getStatefulSetRevision(cr *v1.KeeperCluster) (string, error) {
234233
return hash, nil
235234
}
236235

237-
func templateConfigMap(cr *v1.KeeperCluster, extraConfig map[string]any, id v1.KeeperReplicaID) (*corev1.ConfigMap, error) {
238-
config, err := generateConfigForSingleReplica(cr, extraConfig, id)
236+
func templateConfigMap(cr *v1.KeeperCluster, id v1.KeeperReplicaID) (*corev1.ConfigMap, error) {
237+
cfg, err := generateConfigForSingleReplica(cr, id)
239238
if err != nil {
240239
return nil, fmt.Errorf("generate configmap for replica %q: %w", id, err)
241240
}
@@ -251,9 +250,7 @@ func templateConfigMap(cr *v1.KeeperCluster, extraConfig map[string]any, id v1.K
251250
Labels: controllerutil.MergeMaps(cr.Spec.Labels, replicaLabels(cr, id)),
252251
Annotations: cr.Spec.Annotations,
253252
},
254-
Data: map[string]string{
255-
ConfigFileName: config,
256-
},
253+
Data: cfg,
257254
}, nil
258255
}
259256

@@ -327,7 +324,7 @@ func replicaLabels(cr *v1.KeeperCluster, id v1.KeeperReplicaID) map[string]strin
327324
return labels
328325
}
329326

330-
func generateConfigForSingleReplica(cr *v1.KeeperCluster, extraConfig map[string]any, id v1.KeeperReplicaID) (string, error) {
327+
func generateConfigForSingleReplica(cr *v1.KeeperCluster, id v1.KeeperReplicaID) (map[string]string, error) {
331328
config := config{
332329
ListenHost: "0.0.0.0",
333330
Path: internal.KeeperDataPath,
@@ -370,26 +367,18 @@ func generateConfigForSingleReplica(cr *v1.KeeperCluster, extraConfig map[string
370367

371368
yamlConfig, err := yaml.Marshal(config)
372369
if err != nil {
373-
return "", fmt.Errorf("error marshalling config to yaml: %w", err)
370+
return nil, fmt.Errorf("error marshalling config to yaml: %w", err)
374371
}
375372

376-
if len(extraConfig) > 0 {
377-
configMap := map[string]any{}
378-
if err := yaml.Unmarshal(yamlConfig, &configMap); err != nil {
379-
return "", fmt.Errorf("error unmarshalling config from yaml: %w", err)
380-
}
381-
382-
if err := mergo.Merge(&configMap, extraConfig, mergo.WithOverride); err != nil {
383-
return "", fmt.Errorf("error merging config with extraConfig: %w", err)
384-
}
373+
data := map[string]string{
374+
ConfigFileName: string(yamlConfig),
375+
}
385376

386-
yamlConfig, err = yaml.Marshal(configMap)
387-
if err != nil {
388-
return "", fmt.Errorf("error marshalling merged config to yaml: %w", err)
389-
}
377+
if len(cr.Spec.Settings.ExtraConfig.Raw) > 0 {
378+
data[ExtraConfigFileName] = string(cr.Spec.Settings.ExtraConfig.Raw)
390379
}
391380

392-
return string(yamlConfig), nil
381+
return data, nil
393382
}
394383

395384
func templatePodSpec(cr *v1.KeeperCluster, id v1.KeeperReplicaID) (corev1.PodSpec, error) {

internal/controller/keeper/templates_test.go

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import (
66
"github.com/google/go-cmp/cmp"
77
. "github.com/onsi/ginkgo/v2"
88
. "github.com/onsi/gomega"
9-
"gopkg.in/yaml.v2"
109
corev1 "k8s.io/api/core/v1"
1110
policyv1 "k8s.io/api/policy/v1"
1211
"k8s.io/apimachinery/pkg/api/resource"
1312
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
1414
"k8s.io/apimachinery/pkg/util/intstr"
1515
"sigs.k8s.io/randfill"
1616

@@ -19,8 +19,6 @@ import (
1919
"github.com/ClickHouse/clickhouse-operator/internal/controllerutil"
2020
)
2121

22-
type confMap map[any]any
23-
2422
var _ = Describe("ServerRevision", func() {
2523
var (
2624
baseCR *v1.KeeperCluster
@@ -40,7 +38,7 @@ var _ = Describe("ServerRevision", func() {
4038
},
4139
}
4240

43-
baseCfgRevision, err = getConfigurationRevision(baseCR, nil)
41+
baseCfgRevision, err = getConfigurationRevision(baseCR)
4442
Expect(err).ToNot(HaveOccurred())
4543
Expect(baseCfgRevision).ToNot(BeEmpty())
4644

@@ -52,7 +50,7 @@ var _ = Describe("ServerRevision", func() {
5250
It("should not change config revision if only replica count changes", func() {
5351
cr := baseCR.DeepCopy()
5452
cr.Spec.Replicas = new(int32(3))
55-
cfgRevisionUpdated, err := getConfigurationRevision(cr, nil)
53+
cfgRevisionUpdated, err := getConfigurationRevision(cr)
5654
Expect(err).ToNot(HaveOccurred())
5755
Expect(baseCfgRevision).ToNot(BeEmpty())
5856
Expect(cfgRevisionUpdated).To(Equal(baseCfgRevision), "server config revision shouldn't depend on replica count")
@@ -66,7 +64,7 @@ var _ = Describe("ServerRevision", func() {
6664
It("should not change sts revision if only config changes", func() {
6765
cr := baseCR.DeepCopy()
6866
cr.Spec.Settings.Logger.Level = "warning"
69-
cfgRevisionUpdated, err := getConfigurationRevision(cr, nil)
67+
cfgRevisionUpdated, err := getConfigurationRevision(cr)
7068
Expect(err).ToNot(HaveOccurred())
7169
Expect(cfgRevisionUpdated).ToNot(BeEmpty())
7270
Expect(cfgRevisionUpdated).ToNot(Equal(baseCfgRevision), "configuration change should update config revision")
@@ -79,64 +77,32 @@ var _ = Describe("ServerRevision", func() {
7977
})
8078

8179
var _ = Describe("ExtraConfig", func() {
82-
var (
83-
cr *v1.KeeperCluster
84-
baseConfigYAML string
85-
baseConfig confMap
86-
)
87-
88-
BeforeEach(func() {
89-
cr = &v1.KeeperCluster{
90-
ObjectMeta: metav1.ObjectMeta{
91-
Name: "test",
92-
},
80+
It("should add extra config as a separate ConfigMap key", func() {
81+
cr := &v1.KeeperCluster{
82+
ObjectMeta: metav1.ObjectMeta{Name: "test"},
9383
Spec: v1.KeeperClusterSpec{
9484
Replicas: new(int32(1)),
95-
},
96-
}
97-
98-
var err error
99-
100-
baseConfigYAML, err = generateConfigForSingleReplica(cr, nil, 1)
101-
Expect(err).NotTo(HaveOccurred())
102-
Expect(yaml.Unmarshal([]byte(baseConfigYAML), &baseConfig)).To(Succeed())
103-
})
104-
105-
It("should reflect config changes in generated config", func() {
106-
configYAML, err := generateConfigForSingleReplica(cr, map[string]any{
107-
"keeper_server": confMap{
108-
"coordination_settings": confMap{
109-
"quorum_reads": true,
85+
Settings: v1.KeeperSettings{
86+
ExtraConfig: runtime.RawExtension{Raw: []byte(`{"keeper_server": {"coordination_settings": {"quorum_reads": true}}}`)},
11087
},
11188
},
112-
}, 1)
89+
}
90+
data, err := generateConfigForSingleReplica(cr, 1)
11391
Expect(err).NotTo(HaveOccurred())
114-
115-
var config confMap
116-
Expect(yaml.Unmarshal([]byte(configYAML), &config)).To(Succeed())
117-
Expect(config).ToNot(Equal(baseConfig), cmp.Diff(config, baseConfig))
118-
//nolint:forcetypeassert
119-
Expect(config["keeper_server"].(confMap)["coordination_settings"].(confMap)["quorum_reads"]).To(BeTrue())
92+
Expect(data).To(HaveKey(ConfigFileName))
93+
Expect(data).To(HaveKey(ExtraConfigFileName))
94+
Expect(data[ExtraConfigFileName]).To(ContainSubstring("quorum_reads"))
12095
})
12196

122-
It("should override existing setting by extra config", func() {
123-
configYAML, err := generateConfigForSingleReplica(cr, map[string]any{
124-
"keeper_server": confMap{
125-
"coordination_settings": confMap{
126-
"compress_logs": true,
127-
},
128-
},
129-
}, 1)
130-
Expect(err).NotTo(HaveOccurred())
131-
132-
var config confMap
133-
134-
err = yaml.Unmarshal([]byte(configYAML), &config)
97+
It("should not include extra config key when empty", func() {
98+
cr := &v1.KeeperCluster{
99+
ObjectMeta: metav1.ObjectMeta{Name: "test"},
100+
Spec: v1.KeeperClusterSpec{Replicas: new(int32(1))},
101+
}
102+
data, err := generateConfigForSingleReplica(cr, 1)
135103
Expect(err).NotTo(HaveOccurred())
136-
137-
Expect(config).ToNot(Equal(baseConfig), cmp.Diff(config, baseConfig))
138-
//nolint:forcetypeassert
139-
Expect(config["keeper_server"].(confMap)["coordination_settings"].(confMap)["compress_logs"]).To(BeTrue())
104+
Expect(data).To(HaveKey(ConfigFileName))
105+
Expect(data).ToNot(HaveKey(ExtraConfigFileName))
140106
})
141107
})
142108

test/e2e/clickhouse_e2e_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,15 @@ func WaitClickHouseUpdatedAndReady(
864864
g.Expect(cluster.Status.CurrentRevision).To(Equal(cluster.Status.UpdateRevision))
865865
g.Expect(cluster.Status.ReadyReplicas).To(Equal(cluster.Replicas() * cluster.Shards()))
866866

867-
for _, cond := range cluster.Status.Conditions {
867+
for _, conditionType := range []v1.ConditionType{
868+
v1.ConditionTypeReady,
869+
v1.ConditionTypeHealthy,
870+
v1.ConditionTypeClusterSizeAligned,
871+
v1.ConditionTypeConfigurationInSync,
872+
v1.ClickHouseConditionTypeSchemaInSync,
873+
} {
874+
cond := meta.FindStatusCondition(cluster.Status.Conditions, string(conditionType))
875+
g.Expect(cond).ToNot(BeNil())
868876
g.Expect(cond.Status).To(
869877
Equal(metav1.ConditionTrue),
870878
fmt.Sprintf("condition %s is false: %s", cond.Type, cond.Message),

test/e2e/keeper_e2e_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,14 @@ func WaitKeeperUpdatedAndReady(ctx context.Context, cr *v1.KeeperCluster, timeou
314314
g.Expect(cluster.Status.CurrentRevision).To(Equal(cluster.Status.UpdateRevision))
315315
g.Expect(cluster.Status.ReadyReplicas).To(Equal(cluster.Replicas()))
316316

317-
for _, cond := range cluster.Status.Conditions {
317+
for _, conditionType := range []v1.ConditionType{
318+
v1.ConditionTypeReady,
319+
v1.ConditionTypeHealthy,
320+
v1.ConditionTypeClusterSizeAligned,
321+
v1.ConditionTypeConfigurationInSync,
322+
} {
323+
cond := meta.FindStatusCondition(cluster.Status.Conditions, string(conditionType))
324+
g.Expect(cond).ToNot(BeNil())
318325
g.Expect(cond.Status).To(
319326
Equal(metav1.ConditionTrue),
320327
fmt.Sprintf("condition %s is false: %s", cond.Type, cond.Message),

0 commit comments

Comments
 (0)