Skip to content

Commit 504805e

Browse files
author
bing.ma
committed
use mysql compatible configuration and allow duplicate keys in mysqlConf field of cr
1 parent 37c6410 commit 504805e

File tree

12 files changed

+167
-84
lines changed

12 files changed

+167
-84
lines changed

config/crd/bases/mysql.presslabs.org_mysqlclusters.yaml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,8 @@ spec:
9898
description: The number of pods from that set that must still be available after the eviction, even in the absence of the evicted pod Defaults to 50%
9999
type: string
100100
mysqlConf:
101-
additionalProperties:
102-
anyOf:
103-
- type: integer
104-
- type: string
105-
x-kubernetes-int-or-string: true
106-
description: A map[string]string that will be passed to my.cnf file.
107-
type: object
101+
description: A string that will be passed to my.cnf file, it should be compatible with mysql config format.
102+
type: string
108103
mysqlVersion:
109104
description: 'Represents the MySQL version that will be run. The available version can be found here: https://github.com/bitpoke/mysql-operator/blob/0fd4641ce4f756a0aab9d31c8b1f1c44ee10fcb2/pkg/util/constants/constants.go#L87 This field should be set even if the Image is set to let the operator know which mysql version is running. Based on this version the operator can take decisions which features can be used. Defaults to 5.7'
110105
type: string

deploy/charts/mysql-operator/crds/mysql.presslabs.org_mysqlclusters.yaml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,8 @@ spec:
9999
description: The number of pods from that set that must still be available after the eviction, even in the absence of the evicted pod Defaults to 50%
100100
type: string
101101
mysqlConf:
102-
additionalProperties:
103-
anyOf:
104-
- type: integer
105-
- type: string
106-
x-kubernetes-int-or-string: true
107-
description: A map[string]string that will be passed to my.cnf file.
108-
type: object
102+
description: A string that will be passed to my.cnf file, it should be compatible with mysql config format.
103+
type: string
109104
mysqlVersion:
110105
description: 'Represents the MySQL version that will be run. The available version can be found here: https://github.com/bitpoke/mysql-operator/blob/0fd4641ce4f756a0aab9d31c8b1f1c44ee10fcb2/pkg/util/constants/constants.go#L87 This field should be set even if the Image is set to let the operator know which mysql version is running. Based on this version the operator can take decisions which features can be used. Defaults to 5.7'
111106
type: string

examples/example-cluster.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ spec:
5858
# serverIDOffset: 100
5959

6060
## Configs that will be added to my.cnf for cluster
61-
mysqlConf:
62-
# innodb-buffer-size: 128M
61+
## just write it the same way you write the MySQL configuration file
62+
mysqlConf: |-
63+
# innodb-buffer-size = 128M
6364

6465

6566
## Specify additional pod specification

pkg/apis/mysql/v1alpha1/mysqlcluster_defaults.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func SetDefaults_MysqlCluster(c *MysqlCluster) {
5050
}
5151

5252
if len(c.Spec.MysqlConf) == 0 {
53-
c.Spec.MysqlConf = make(MysqlConf)
53+
c.Spec.MysqlConf = ""
5454
}
5555

5656
if len(c.Spec.MinAvailable) == 0 && *c.Spec.Replicas > 1 {

pkg/apis/mysql/v1alpha1/mysqlcluster_types.go

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
"fmt"
21+
"strings"
22+
2023
core "k8s.io/api/core/v1"
2124
"k8s.io/apimachinery/pkg/api/resource"
2225
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -100,7 +103,7 @@ type MysqlClusterSpec struct {
100103
// +optional
101104
BackupScheduleJobsHistoryLimit *int `json:"backupScheduleJobsHistoryLimit,omitempty"`
102105

103-
// A map[string]string that will be passed to my.cnf file.
106+
// A string that will be passed to my.cnf file, it should be compatible with mysql config format.
104107
// +optional
105108
MysqlConf MysqlConf `json:"mysqlConf,omitempty"`
106109

@@ -175,9 +178,100 @@ type MysqlClusterSpec struct {
175178
InitFileExtraSQL []string `json:"initFileExtraSQL,omitempty"`
176179
}
177180

178-
// MysqlConf defines type for extra cluster configs. It's a simple map between
179-
// string and string.
180-
type MysqlConf map[string]intstr.IntOrString
181+
// MysqlConf defines type for extra cluster configs. value is a mysql ini string, allow duplicate keys, like:
182+
// replicated_do_db=db1
183+
// replicated_do_db=db2
184+
type MysqlConf string
185+
186+
// Get returns the value of key, if found, mysql allow no value in config, so we need to return a bool
187+
func (m MysqlConf) Get(key string) (string, bool) {
188+
s := string(m)
189+
lines := strings.Split(s, "\n")
190+
for _, line := range lines {
191+
if line == "" {
192+
continue
193+
}
194+
keyAndValue := strings.Split(strings.TrimSpace(line), "=")
195+
if strings.TrimSpace(keyAndValue[0]) == strings.TrimSpace(key) {
196+
if len(keyAndValue) == 1 {
197+
return "", true
198+
}
199+
if len(keyAndValue) == 2 {
200+
return strings.TrimSpace(keyAndValue[1]), true
201+
}
202+
}
203+
}
204+
return "", false
205+
}
206+
207+
// Set a key value pair, if key already exists, it will be overwritten else it will append at the end
208+
func (m MysqlConf) Set(key string, value string) MysqlConf {
209+
s := string(m)
210+
lines := strings.Split(s, "\n")
211+
trimmedKey := strings.TrimSpace(key)
212+
trimmedValue := strings.TrimSpace(value)
213+
found := false
214+
for index, line := range lines {
215+
keyAndValue := strings.Split(strings.TrimSpace(line), "=")
216+
if strings.TrimSpace(keyAndValue[0]) == trimmedKey {
217+
found = true
218+
// mysql allow bare boolean options with no value assignment,like:
219+
// [mysqld]
220+
// skip-name-resolve # no value here, when len(keyAndValue) == 1
221+
222+
// when len(keyAndValue) == 2 means "key = value"
223+
if len(keyAndValue) == 1 && trimmedValue != "" || len(keyAndValue) == 2 {
224+
// need to set key = value
225+
lines[index] = fmt.Sprintf("%s = %s", trimmedKey, trimmedValue)
226+
}
227+
}
228+
}
229+
res := strings.Join(lines, "\n")
230+
if !found {
231+
if trimmedValue != "" {
232+
res += fmt.Sprintf("\n%s = %s", trimmedKey, trimmedValue)
233+
} else {
234+
res += fmt.Sprintf("\n%s", trimmedKey)
235+
}
236+
}
237+
return MysqlConf(res)
238+
}
239+
240+
// ToMap returns a map[string]string, to compatible with addKVConfigsToSection
241+
func (m MysqlConf) ToMap() []map[string]intstr.IntOrString {
242+
trimmed := strings.TrimSpace(string(m))
243+
res := make([]map[string]intstr.IntOrString, 0)
244+
if trimmed == "" {
245+
return res
246+
}
247+
lines := strings.Split(trimmed, "\n")
248+
total := make(map[string]intstr.IntOrString)
249+
for _, line := range lines {
250+
trimmedLine := strings.TrimSpace(line)
251+
if trimmedLine == "" {
252+
continue
253+
}
254+
keyAndValue := strings.Split(trimmedLine, "=")
255+
length := len(keyAndValue)
256+
if length != 1 && length != 2 {
257+
continue
258+
}
259+
key := strings.TrimSpace(keyAndValue[0])
260+
value := intstr.FromString("<nil>")
261+
if length == 2 {
262+
value = intstr.FromString(strings.TrimSpace(keyAndValue[1]))
263+
}
264+
if _, ok := total[key]; ok {
265+
res = append(res, map[string]intstr.IntOrString{key: value})
266+
} else {
267+
total[key] = value
268+
}
269+
}
270+
if len(total) != 0 {
271+
res = append(res, total)
272+
}
273+
return res
274+
}
181275

182276
// PodSpec defines type for configure cluster pod spec.
183277
type PodSpec struct {
@@ -376,7 +470,6 @@ type MysqlClusterStatus struct {
376470
// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas",description="The number of desired nodes"
377471
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
378472
// +kubebuilder:resource:shortName=mysql
379-
//
380473
type MysqlCluster struct {
381474
metav1.TypeMeta `json:",inline"`
382475
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -387,7 +480,6 @@ type MysqlCluster struct {
387480

388481
// MysqlClusterList contains a list of MysqlCluster
389482
// +kubebuilder:object:root=true
390-
//
391483
type MysqlClusterList struct {
392484
metav1.TypeMeta `json:",inline"`
393485
metav1.ListMeta `json:"metadata,omitempty"`

pkg/apis/mysql/v1alpha1/mysqlcluster_types_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ var _ = Describe("MysqlCluster CRUD", func() {
8383
It("should populate fields defaults", func() {
8484
SetDefaults_MysqlCluster(cluster)
8585

86-
Expect(cluster.Spec.MysqlConf).NotTo(BeNil())
86+
Expect(cluster.Spec.MysqlConf).NotTo(Equal(""))
8787
})
8888
})
8989

pkg/apis/mysql/v1alpha1/zz_generated.deepcopy.go

Lines changed: 0 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/mysqlbackupcron/mysqlbackupcron_controller_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3333
"k8s.io/apimachinery/pkg/labels"
3434
"k8s.io/apimachinery/pkg/types"
35-
"k8s.io/apimachinery/pkg/util/intstr"
3635
"sigs.k8s.io/controller-runtime/pkg/client"
3736
"sigs.k8s.io/controller-runtime/pkg/manager"
3837
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -170,9 +169,8 @@ var _ = Describe("MysqlBackupCron controller", func() {
170169

171170
It("should be just one entry for a cluster", func() {
172171
// update cluster spec
173-
cluster.Spec.MysqlConf = map[string]intstr.IntOrString{
174-
"something": intstr.FromString("new"),
175-
}
172+
cluster.Spec.MysqlConf = "something = new"
173+
176174
Expect(c.Update(context.TODO(), cluster)).To(Succeed())
177175

178176
// expect an reconcile event

pkg/controller/mysqlcluster/internal/syncer/config_map.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ fi
9494
}
9595

9696
func buildMysqlConfData(cluster *mysqlcluster.MysqlCluster) (string, error) {
97-
cfg := ini.Empty()
97+
// allow duplicate key in section
98+
cfg := ini.Empty(ini.LoadOptions{
99+
AllowShadows: true,
100+
})
98101
sec := cfg.Section("mysqld")
99102

100103
if cluster.GetMySQLSemVer().Major == 5 {
@@ -106,8 +109,13 @@ func buildMysqlConfData(cluster *mysqlcluster.MysqlCluster) (string, error) {
106109
// boolean configs
107110
addBConfigsToSection(sec, mysqlMasterSlaveBooleanConfigs)
108111
// add custom configs, would overwrite common configs
109-
addKVConfigsToSection(sec, convertMapToKVConfig(mysqlCommonConfigs), cluster.Spec.MysqlConf)
110-
112+
extraMysqld := cluster.Spec.MysqlConf.ToMap()
113+
if extraMysqld != nil {
114+
extraMysqld = append(extraMysqld, convertMapToKVConfig(mysqlCommonConfigs))
115+
} else {
116+
extraMysqld = make([]map[string]intstr.IntOrString, 0)
117+
}
118+
addKVConfigsToSection(sec, extraMysqld...)
111119
// include configs from /etc/mysql/conf.d/*.cnf
112120
_, err := sec.NewBooleanKey(fmt.Sprintf("!includedir %s", ConfDPath))
113121
if err != nil {
@@ -120,7 +128,6 @@ func buildMysqlConfData(cluster *mysqlcluster.MysqlCluster) (string, error) {
120128
}
121129

122130
return data, nil
123-
124131
}
125132

126133
func convertMapToKVConfig(m map[string]string) map[string]intstr.IntOrString {
@@ -146,8 +153,15 @@ func addKVConfigsToSection(s *ini.Section, extraMysqld ...map[string]intstr.IntO
146153

147154
for _, k := range keys {
148155
value := extra[k]
149-
if _, err := s.NewKey(k, value.String()); err != nil {
150-
log.Error(err, "failed to add key to config section", "key", k, "value", extra[k], "section", s)
156+
// in (m MysqlConf) ToMap() we set it to "<nil>" when spec.mysqlConf no value.
157+
if value.String() == "<nil>" {
158+
if _, err := s.NewBooleanKey(k); err != nil {
159+
log.Error(err, "failed to add boolean key to config section", "key", k)
160+
}
161+
} else {
162+
if _, err := s.NewKey(k, value.String()); err != nil {
163+
log.Error(err, "failed to add key to config section", "key", k, "value", extra[k], "section", s)
164+
}
151165
}
152166
}
153167
}

pkg/internal/mysqlcluster/defaults.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ func (cluster *MysqlCluster) SetDefaults(opt *options.Options) {
8787
if mem := cluster.Spec.PodSpec.Resources.Requests.Memory(); mem != nil && !mem.IsZero() {
8888
var cErr error
8989
if innodbBufferPoolSize, cErr = computeInnodbBufferPoolSize(mem); cErr == nil {
90-
setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-buffer-pool-size", humanizeSize(innodbBufferPoolSize))
90+
cluster.Spec.MysqlConf = setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-buffer-pool-size", humanizeSize(innodbBufferPoolSize))
9191
}
9292
}
9393

9494
if mem := cluster.Spec.PodSpec.Resources.Requests.Memory(); mem != nil {
9595
logFileSize := humanizeSize(computeInnodbLogFileSize(mem))
96-
setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-log-file-size", logFileSize)
96+
cluster.Spec.MysqlConf = setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-log-file-size", logFileSize)
9797
}
9898

9999
if pvc := cluster.Spec.VolumeSpec.PersistentVolumeClaim; pvc != nil {
@@ -107,19 +107,22 @@ func (cluster *MysqlCluster) SetDefaults(opt *options.Options) {
107107

108108
if cluster.IsPerconaImage() {
109109
// binlog-space-limit = totalSpace / 2
110-
setConfigIfNotSet(cluster.Spec.MysqlConf, "binlog-space-limit", humanizeSize(binlogSpaceLimit))
110+
cluster.Spec.MysqlConf = setConfigIfNotSet(cluster.Spec.MysqlConf, "binlog-space-limit",
111+
humanizeSize(binlogSpaceLimit))
111112
}
112113

113114
// max-binlog-size = min(binlog-space-limit / 4, 1*gb)
114-
setConfigIfNotSet(cluster.Spec.MysqlConf, "max-binlog-size", humanizeSize(maxBinlogSize))
115+
cluster.Spec.MysqlConf = setConfigIfNotSet(cluster.Spec.MysqlConf, "max-binlog-size",
116+
humanizeSize(maxBinlogSize))
115117
}
116118
}
117119

118120
if cpu := cluster.Spec.PodSpec.Resources.Limits.Cpu(); cpu != nil && !cpu.IsZero() {
119121
// innodb_buffer_pool_instances = min(ceil(resources.limits.cpu), floor(innodb_buffer_pool_size/1Gi))
120122
cpuRounded := math.Ceil(float64(cpu.MilliValue()) / float64(1000))
121123
instances := math.Max(math.Min(cpuRounded, math.Floor(float64(innodbBufferPoolSize)/float64(gb))), 1)
122-
setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-buffer-pool-instances", intstr.FromInt(int(instances)))
124+
cluster.Spec.MysqlConf = setConfigIfNotSet(cluster.Spec.MysqlConf, "innodb-buffer-pool-instances",
125+
intstr.FromInt(int(instances)))
123126
}
124127

125128
// set default xtrabackup target directory
@@ -128,10 +131,12 @@ func (cluster *MysqlCluster) SetDefaults(opt *options.Options) {
128131
}
129132
}
130133

131-
func setConfigIfNotSet(conf api.MysqlConf, option string, value intstr.IntOrString) {
132-
if _, ok := conf[option]; !ok {
133-
conf[option] = value
134+
func setConfigIfNotSet(conf api.MysqlConf, option string, value intstr.IntOrString) api.MysqlConf {
135+
valueStr := value.String()
136+
if valueStr == "<nil>" {
137+
valueStr = ""
134138
}
139+
return conf.Set(option, valueStr)
135140
}
136141

137142
func getRequestedStorage(pvc *core.PersistentVolumeClaimSpec) *resource.Quantity {

0 commit comments

Comments
 (0)