Skip to content

Commit 141bfef

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

File tree

12 files changed

+159
-83
lines changed

12 files changed

+159
-83
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: 95 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,97 @@ 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+
keyAndValue := strings.Split(strings.TrimSpace(line), "=")
192+
if strings.TrimSpace(keyAndValue[0]) == strings.TrimSpace(key) {
193+
if len(keyAndValue) == 1 {
194+
return "", true
195+
}
196+
if len(keyAndValue) == 2 {
197+
return strings.TrimSpace(keyAndValue[1]), true
198+
}
199+
}
200+
}
201+
return "", false
202+
}
203+
204+
// Set a key value pair, if key already exists, it will be overwritten else it will append at the end
205+
func (m MysqlConf) Set(key string, value string) MysqlConf {
206+
s := string(m)
207+
lines := strings.Split(s, "\n")
208+
trimmedKey := strings.TrimSpace(key)
209+
trimmedValue := strings.TrimSpace(value)
210+
found := false
211+
for index, line := range lines {
212+
keyAndValue := strings.Split(strings.TrimSpace(line), "=")
213+
if strings.TrimSpace(keyAndValue[0]) == trimmedKey {
214+
found = true
215+
// mysql allow bare boolean options with no value assignment,like:
216+
// [mysqld]
217+
// skip-name-resolve # no value here, when len(keyAndValue) == 1
218+
219+
// when len(keyAndValue) == 2 means "key = value"
220+
if len(keyAndValue) == 1 && trimmedValue != "" || len(keyAndValue) == 2 {
221+
// need to set key = value
222+
lines[index] = fmt.Sprintf("%s = %s", trimmedKey, trimmedValue)
223+
}
224+
}
225+
}
226+
res := strings.Join(lines, "\n")
227+
if !found {
228+
if trimmedValue != "" {
229+
res += fmt.Sprintf("\n%s = %s", trimmedKey, trimmedValue)
230+
} else {
231+
res += fmt.Sprintf("\n%s", trimmedKey)
232+
}
233+
}
234+
return MysqlConf(res)
235+
}
236+
237+
// ToMap returns a map[string]string, to compatible with addKVConfigsToSection
238+
func (m MysqlConf) ToMap() []map[string]intstr.IntOrString {
239+
trimmed := strings.TrimSpace(string(m))
240+
res := make([]map[string]intstr.IntOrString, 0)
241+
if trimmed == "" {
242+
return res
243+
}
244+
lines := strings.Split(trimmed, "\n")
245+
total := make(map[string]intstr.IntOrString)
246+
for _, line := range lines {
247+
trimmedLine := strings.TrimSpace(line)
248+
if trimmedLine == "" {
249+
continue
250+
}
251+
keyAndValue := strings.Split(trimmedLine, "=")
252+
length := len(keyAndValue)
253+
if length != 1 && length != 2 {
254+
continue
255+
}
256+
key := strings.TrimSpace(keyAndValue[0])
257+
value := intstr.FromString("<nil>")
258+
if length == 2 {
259+
value = intstr.FromString(strings.TrimSpace(keyAndValue[1]))
260+
}
261+
if _, ok := total[key]; ok {
262+
res = append(res, map[string]intstr.IntOrString{key: value})
263+
} else {
264+
total[key] = value
265+
}
266+
}
267+
if len(total) != 0 {
268+
res = append(res, total)
269+
}
270+
return res
271+
}
181272

182273
// PodSpec defines type for configure cluster pod spec.
183274
type PodSpec struct {
@@ -376,7 +467,6 @@ type MysqlClusterStatus struct {
376467
// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas",description="The number of desired nodes"
377468
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
378469
// +kubebuilder:resource:shortName=mysql
379-
//
380470
type MysqlCluster struct {
381471
metav1.TypeMeta `json:",inline"`
382472
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -387,7 +477,6 @@ type MysqlCluster struct {
387477

388478
// MysqlClusterList contains a list of MysqlCluster
389479
// +kubebuilder:object:root=true
390-
//
391480
type MysqlClusterList struct {
392481
metav1.TypeMeta `json:",inline"`
393482
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 {

pkg/internal/mysqlcluster/mysqlcluster.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package mysqlcluster
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"strings"
2223

2324
"github.com/blang/semver"
@@ -246,10 +247,15 @@ func (c *MysqlCluster) ExporterDataSourcePort() int {
246247
extraMaxConnectionsSettings := []string{"extra_max_connections", "extra-max-connections"}
247248

248249
for _, setting := range extraPortSettings {
249-
if port, ok := c.Spec.MysqlConf[setting]; ok {
250+
if port, ok := c.Spec.MysqlConf.Get(setting); ok {
250251
for _, setting := range extraMaxConnectionsSettings {
251-
if conns, ok := c.Spec.MysqlConf[setting]; ok && conns.IntValue() > 1 {
252-
return port.IntValue()
252+
if conns, ok := c.Spec.MysqlConf.Get(setting); ok {
253+
connsInt, err := strconv.Atoi(conns)
254+
if err == nil && connsInt > 1 {
255+
if portInt, err := strconv.Atoi(port); err == nil {
256+
return portInt
257+
}
258+
}
253259
}
254260
}
255261
}

0 commit comments

Comments
 (0)