Skip to content

Commit ec6b421

Browse files
authored
chore: improve pkg parameters coverage (#10375)
1 parent ec4f156 commit ec6b421

6 files changed

Lines changed: 530 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright (C) 2022-2026 ApeCloud Co., Ltd
3+
4+
This file is part of KubeBlocks project
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package parameters
21+
22+
import (
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
26+
parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1"
27+
)
28+
29+
var _ = Describe("parameter metadata helpers", func() {
30+
It("finds config descriptions by name and template", func() {
31+
configs := []parametersv1alpha1.ComponentConfigDescription{
32+
{Name: "mysql.cnf", TemplateName: "mysql-template"},
33+
{Name: "proxy.cnf", TemplateName: "proxy-template"},
34+
{Name: "mysql-extra.cnf", TemplateName: "mysql-template"},
35+
}
36+
37+
Expect(GetComponentConfigDescription(configs, "mysql.cnf")).To(Equal(&configs[0]))
38+
Expect(GetComponentConfigDescription(configs, "missing.cnf")).To(BeNil())
39+
Expect(GetComponentConfigDescriptions(configs, "mysql-template")).To(Equal([]parametersv1alpha1.ComponentConfigDescription{
40+
configs[0],
41+
configs[2],
42+
}))
43+
Expect(GetComponentConfigDescriptions(configs, "missing-template")).To(BeEmpty())
44+
Expect(HasValidParameterTemplate(configs)).To(BeTrue())
45+
Expect(HasValidParameterTemplate(nil)).To(BeFalse())
46+
})
47+
})

pkg/parameters/core/config_util_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1919

2020
package core
2121

22+
import (
23+
"errors"
24+
"reflect"
25+
"strings"
26+
"testing"
27+
28+
"github.com/stretchr/testify/require"
29+
"k8s.io/utils/ptr"
30+
31+
parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1"
32+
)
33+
2234
// import (
2335
// "reflect"
2436
// "strings"
@@ -186,6 +198,105 @@ package core
186198
// })
187199
// }
188200
// }
201+
202+
func TestMergeUpdatedConfig(t *testing.T) {
203+
base := map[string]string{
204+
"mysql.cnf": "[mysqld]",
205+
"proxy.cnf": "port=3306",
206+
}
207+
updated := map[string]string{
208+
"mysql.cnf": "[mysqld]\nmax_connections=200",
209+
"extra.cnf": "ignored=true",
210+
}
211+
212+
got := MergeUpdatedConfig(base, updated)
213+
require.Equal(t, map[string]string{
214+
"mysql.cnf": "[mysqld]\nmax_connections=200",
215+
"proxy.cnf": "port=3306",
216+
}, got)
217+
}
218+
219+
func TestFromStringMap(t *testing.T) {
220+
port := "3306"
221+
hosts := `["mysql-0","mysql-1"]`
222+
params := map[string]*string{
223+
"port": &port,
224+
"unset": nil,
225+
"@hosts": &hosts,
226+
}
227+
228+
got, err := FromStringMap(params, ValueTransformerFunc(func(value string, fieldName string) (any, error) {
229+
if fieldName == "port" {
230+
return "tcp:" + value, nil
231+
}
232+
return value, nil
233+
}))
234+
require.NoError(t, err)
235+
require.Equal(t, "tcp:3306", got["port"])
236+
require.Nil(t, got["unset"])
237+
require.Equal(t, []interface{}{"mysql-0", "mysql-1"}, got["hosts"])
238+
239+
_, err = FromStringMap(map[string]*string{"port": &port}, ValueTransformerFunc(func(string, string) (any, error) {
240+
return nil, errors.New("bad value")
241+
}))
242+
require.ErrorContains(t, err, "bad value")
243+
}
244+
245+
func TestApplyConfigPatch(t *testing.T) {
246+
base := []byte("[mysqld]\nmax_connections=100\nold_value=keep\n")
247+
format := &parametersv1alpha1.FileFormatConfig{
248+
Format: parametersv1alpha1.Ini,
249+
FormatterAction: parametersv1alpha1.FormatterAction{
250+
IniConfig: &parametersv1alpha1.IniConfig{SectionName: "mysqld"},
251+
},
252+
}
253+
254+
got, err := ApplyConfigPatch(base, map[string]*string{
255+
"max_connections": ptr.To("200"),
256+
"old_value": nil,
257+
}, format, nil)
258+
require.NoError(t, err)
259+
require.Contains(t, got, "[mysqld]")
260+
require.Contains(t, got, "max_connections=200")
261+
262+
_, err = ApplyConfigPatch([]byte(":"), map[string]*string{"max_connections": ptr.To("200")}, format, nil)
263+
require.Error(t, err)
264+
}
265+
266+
func TestIsWatchModuleForShellTrigger(t *testing.T) {
267+
sync := true
268+
async := false
269+
270+
tests := []struct {
271+
name string
272+
trigger *parametersv1alpha1.ShellTrigger
273+
want bool
274+
}{
275+
{name: "nil trigger", want: true},
276+
{name: "nil sync", trigger: &parametersv1alpha1.ShellTrigger{}, want: true},
277+
{name: "sync trigger", trigger: &parametersv1alpha1.ShellTrigger{Sync: &sync}, want: false},
278+
{name: "async trigger", trigger: &parametersv1alpha1.ShellTrigger{Sync: &async}, want: true},
279+
}
280+
281+
for _, tt := range tests {
282+
t.Run(tt.name, func(t *testing.T) {
283+
require.Equal(t, tt.want, IsWatchModuleForShellTrigger(tt.trigger))
284+
})
285+
}
286+
}
287+
288+
func TestArrayFieldHelpers(t *testing.T) {
289+
require.True(t, hasArrayField("@hosts"))
290+
require.False(t, hasArrayField("hosts"))
291+
require.Equal(t, "@hosts", transArrayFieldName("hosts"))
292+
require.Equal(t, "hosts", GetValidFieldName("@hosts"))
293+
require.Equal(t, "hosts", GetValidFieldName("hosts"))
294+
require.Equal(t, "", transJSONString(nil))
295+
require.Equal(t, []any{}, fromJSONString(ptr.To("")))
296+
require.True(t, reflect.DeepEqual([]any{"a"}, fromJSONString(ptr.To(`["a"]`))))
297+
require.True(t, strings.HasPrefix(transJSONString([]string{"a"}), "["))
298+
}
299+
189300
//
190301
// func TestApplyConfigPatch(t *testing.T) {
191302
// type args struct {

pkg/parameters/core/reconfigure_util_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,165 @@ func TestHasDynamicParameterUpdate(t *testing.T) {
244244
})
245245
}
246246
}
247+
248+
func TestValidateConfigPatch(t *testing.T) {
249+
configs := []parametersv1alpha1.ComponentConfigDescription{{
250+
Name: "mysql.cnf",
251+
FileFormatConfig: &parametersv1alpha1.FileFormatConfig{
252+
Format: parametersv1alpha1.Ini,
253+
FormatterAction: parametersv1alpha1.FormatterAction{
254+
IniConfig: &parametersv1alpha1.IniConfig{SectionName: "mysqld"},
255+
},
256+
},
257+
}}
258+
259+
require.NoError(t, ValidateConfigPatch(&ConfigPatchInfo{}, configs))
260+
require.NoError(t, ValidateConfigPatch(&ConfigPatchInfo{
261+
IsModify: true,
262+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":"200"}}`)},
263+
}, configs))
264+
265+
err := ValidateConfigPatch(&ConfigPatchInfo{
266+
IsModify: true,
267+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":null}}`)},
268+
}, configs)
269+
require.ErrorContains(t, err, "delete config parameter [max_connections] is not support")
270+
}
271+
272+
func TestIsUpdateDynamicParameters(t *testing.T) {
273+
config := &parametersv1alpha1.FileFormatConfig{
274+
Format: parametersv1alpha1.Ini,
275+
FormatterAction: parametersv1alpha1.FormatterAction{
276+
IniConfig: &parametersv1alpha1.IniConfig{SectionName: "mysqld"},
277+
},
278+
}
279+
280+
tests := []struct {
281+
name string
282+
def *parametersv1alpha1.ParametersDefinitionSpec
283+
patch *ConfigPatchInfo
284+
want bool
285+
wantErr bool
286+
}{
287+
{
288+
name: "delete config requires restart",
289+
def: &parametersv1alpha1.ParametersDefinitionSpec{},
290+
patch: &ConfigPatchInfo{
291+
DeleteConfig: map[string]interface{}{"mysql.cnf": map[string]interface{}{"max_connections": "200"}},
292+
},
293+
want: false,
294+
},
295+
{
296+
name: "empty update is dynamic",
297+
def: &parametersv1alpha1.ParametersDefinitionSpec{},
298+
patch: &ConfigPatchInfo{},
299+
want: true,
300+
},
301+
{
302+
name: "invalid update patch",
303+
def: &parametersv1alpha1.ParametersDefinitionSpec{},
304+
patch: &ConfigPatchInfo{
305+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{`)},
306+
},
307+
wantErr: true,
308+
},
309+
{
310+
name: "dynamic parameter only",
311+
def: &parametersv1alpha1.ParametersDefinitionSpec{
312+
DynamicParameters: []string{"max_connections"},
313+
},
314+
patch: &ConfigPatchInfo{
315+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":"200"}}`)},
316+
},
317+
want: true,
318+
},
319+
{
320+
name: "static parameter is not dynamic",
321+
def: &parametersv1alpha1.ParametersDefinitionSpec{
322+
StaticParameters: []string{"max_connections"},
323+
},
324+
patch: &ConfigPatchInfo{
325+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":"200"}}`)},
326+
},
327+
want: false,
328+
},
329+
{
330+
name: "unknown parameter with dynamic list is not dynamic",
331+
def: &parametersv1alpha1.ParametersDefinitionSpec{
332+
DynamicParameters: []string{"innodb_buffer_pool_size"},
333+
},
334+
patch: &ConfigPatchInfo{
335+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":"200"}}`)},
336+
},
337+
want: false,
338+
},
339+
{
340+
name: "static list without dynamic list defaults to reload for non-static update",
341+
def: &parametersv1alpha1.ParametersDefinitionSpec{
342+
StaticParameters: []string{"innodb_buffer_pool_size"},
343+
},
344+
patch: &ConfigPatchInfo{
345+
UpdateConfig: map[string][]byte{"mysql.cnf": []byte(`{"mysqld":{"max_connections":"200"}}`)},
346+
},
347+
want: true,
348+
},
349+
}
350+
351+
for _, tt := range tests {
352+
t.Run(tt.name, func(t *testing.T) {
353+
got, err := IsUpdateDynamicParameters(config, tt.def, tt.patch)
354+
if tt.wantErr {
355+
require.Error(t, err)
356+
return
357+
}
358+
require.NoError(t, err)
359+
require.Equal(t, tt.want, got)
360+
})
361+
}
362+
}
363+
364+
func TestCheckUpdateDynamicParameters(t *testing.T) {
365+
config := &parametersv1alpha1.FileFormatConfig{
366+
Format: parametersv1alpha1.Ini,
367+
FormatterAction: parametersv1alpha1.FormatterAction{
368+
IniConfig: &parametersv1alpha1.IniConfig{SectionName: "mysqld"},
369+
},
370+
}
371+
372+
tests := []struct {
373+
name string
374+
def *parametersv1alpha1.ParametersDefinitionSpec
375+
patch string
376+
want bool
377+
wantErr bool
378+
}{
379+
{name: "empty patch", def: &parametersv1alpha1.ParametersDefinitionSpec{}, want: true},
380+
{name: "invalid patch", def: &parametersv1alpha1.ParametersDefinitionSpec{}, patch: `{`, wantErr: true},
381+
{
382+
name: "dynamic parameter",
383+
def: &parametersv1alpha1.ParametersDefinitionSpec{
384+
DynamicParameters: []string{"max_connections"},
385+
},
386+
patch: `{"mysqld":{"max_connections":"200"}}`,
387+
want: true,
388+
},
389+
{
390+
name: "no parameter lists defaults to restart",
391+
def: &parametersv1alpha1.ParametersDefinitionSpec{},
392+
patch: `{"mysqld":{"max_connections":"200"}}`,
393+
want: false,
394+
},
395+
}
396+
397+
for _, tt := range tests {
398+
t.Run(tt.name, func(t *testing.T) {
399+
got, err := CheckUpdateDynamicParameters(config, tt.def, tt.patch)
400+
if tt.wantErr {
401+
require.Error(t, err)
402+
return
403+
}
404+
require.NoError(t, err)
405+
require.Equal(t, tt.want, got)
406+
})
407+
}
408+
}

0 commit comments

Comments
 (0)