Skip to content

Commit d3899cf

Browse files
author
smiletan
authored
Merge pull request #364 from catpineapple/pvc-modify
[improve](dcr) support pvc template and be multi-data disk
2 parents 7fc3d32 + 36de5e7 commit d3899cf

26 files changed

+457
-56
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package doris
19+
20+
import (
21+
"strings"
22+
)
23+
24+
// ResolveStorageRootPath transforms a string of storage paths into a slice of StorageRootPathInfo.
25+
func ResolveStorageRootPath(configPath string) []string {
26+
var res []string
27+
28+
if configPath == "" {
29+
return res
30+
}
31+
32+
// Separate multiple paths by ';'
33+
configPathSplit := strings.Split(configPath, ";")
34+
35+
// Remove empty elements
36+
for _, c := range configPathSplit {
37+
if path := parseSinglePath(c); path != "" {
38+
res = append(res, path)
39+
}
40+
}
41+
42+
return res
43+
}
44+
45+
// Resolving a single storage path
46+
func parseSinglePath(pathConfig string) string {
47+
if pathConfig == "" {
48+
return ""
49+
}
50+
path := strings.Split(strings.Split(pathConfig, ".")[0], ",")[0]
51+
path = strings.TrimSpace(path)
52+
if strings.HasSuffix(path, "/") {
53+
path = path[:len(path)-1]
54+
}
55+
return path
56+
}
57+
58+
// GetNameOfEachPath is used to parse a set of paths to obtain unique and concise names for each path.
59+
// If the paths are repeated, the returned names may also be repeated.
60+
// And the order of each name in the array is consistent with the input paths.
61+
// For example:
62+
//
63+
// ["/path1"] >> ["path1"]
64+
// ["/opt/doris/path1"] >> ["path1"]
65+
// ["/path1", "/path2"] >> ["path1", "path2"]
66+
// ["/home/disk1/doris", "/home/disk2/doris"] >> ["doris", "disk2-doris"]
67+
// ["/home/doris/disk1", "/home/doris/disk2"] >> ["disk1", "disk2"]
68+
// ["/home/disk1/doris", "/home/disk1/doris", "/home/disk2/doris"] >> ["disk1-doris", "disk1-doris", "disk2-doris"]
69+
func GetNameOfEachPath(paths []string) []string {
70+
namePath := map[string]string{}
71+
pathName := map[string]string{}
72+
for _, path := range paths {
73+
//use unix path separator.
74+
sp := strings.Split(path, "/")
75+
name := ""
76+
for i := 1; i <= len(sp); i++ {
77+
if sp[len(sp)-i] == "" {
78+
continue
79+
}
80+
81+
if name == "" {
82+
name = sp[len(sp)-i]
83+
} else {
84+
name = sp[len(sp)-i] + "-" + name
85+
}
86+
87+
if _, ok := namePath[name]; !ok {
88+
break
89+
}
90+
}
91+
92+
namePath[name] = path
93+
pathName[path] = name
94+
}
95+
res := make([]string, len(paths))
96+
for k := range paths {
97+
res[k] = pathName[paths[k]]
98+
}
99+
return res
100+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package doris
19+
20+
import (
21+
"reflect"
22+
"testing"
23+
)
24+
25+
func TestResolveStorageRootPath(t *testing.T) {
26+
var empty []string
27+
tests := []struct {
28+
name string
29+
input string
30+
want []string
31+
wantErr bool
32+
}{
33+
// Normal test
34+
{
35+
input: "",
36+
want: empty,
37+
},
38+
{
39+
input: "/path1",
40+
want: []string{"/path1"},
41+
},
42+
{
43+
input: "/path1;/path2",
44+
want: []string{"/path1", "/path2"},
45+
},
46+
{
47+
input: "/home/disk1/doris.HDD,50",
48+
want: []string{"/home/disk1/doris"},
49+
},
50+
{
51+
input: "/home/disk1/doris,medium:ssd,capacity:50",
52+
want: []string{"/home/disk1/doris"},
53+
},
54+
{
55+
input: "/home/disk1/doris.SSD,100;/home/disk2/doris,medium:hdd,capacity:200",
56+
want: []string{"/home/disk1/doris", "/home/disk2/doris"},
57+
},
58+
{
59+
input: "/home/disk1/doris/,capacity:50",
60+
want: []string{"/home/disk1/doris"},
61+
},
62+
{
63+
input: "/home/disk1/doris.HDD,medium:ssd",
64+
want: []string{"/home/disk1/doris"},
65+
},
66+
{
67+
input: "/home/disk1/doris,capacity:50;",
68+
want: []string{"/home/disk1/doris"},
69+
},
70+
{
71+
input: " /home/disk1/doris , capacity : 50 ; /home/disk2/doris , medium : ssd ",
72+
want: []string{"/home/disk1/doris", "/home/disk2/doris"},
73+
},
74+
75+
{
76+
input: "/home/disk1/doris,capacity:50;/home/disk1/doris,medium:ssd",
77+
want: []string{"/home/disk1/doris", "/home/disk1/doris"},
78+
},
79+
80+
{
81+
input: ",capacity:50",
82+
want: empty,
83+
},
84+
85+
{
86+
input: "/home/disk1/doris/,unknown:value",
87+
want: []string{"/home/disk1/doris"},
88+
},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
got := ResolveStorageRootPath(tt.input)
94+
if !reflect.DeepEqual(got, tt.want) {
95+
t.Errorf("input: %s, got %v, Expectation %v", tt.input, got, tt.want)
96+
}
97+
})
98+
}
99+
}
100+
101+
func TestGetNameOfEachPath(t *testing.T) {
102+
tests := []struct {
103+
name string
104+
input []string
105+
want []string
106+
}{
107+
{
108+
input: []string{},
109+
want: []string{},
110+
},
111+
{
112+
input: []string{"", "", "", ""},
113+
want: []string{"", "", "", ""},
114+
},
115+
{
116+
input: []string{"", ""},
117+
want: []string{"", ""},
118+
},
119+
{
120+
input: []string{"/path1"},
121+
want: []string{"path1"},
122+
},
123+
{
124+
input: []string{"/opt/doris/path1"},
125+
want: []string{"path1"},
126+
},
127+
{
128+
input: []string{"/path1", "/path2"},
129+
want: []string{"path1", "path2"},
130+
},
131+
{
132+
input: []string{"/home/disk1/doris", "/home/disk2/doris"},
133+
want: []string{"doris", "disk2-doris"},
134+
},
135+
{
136+
input: []string{"/home/disk1/doris", "/home/disk1/doris"},
137+
want: []string{"disk1-doris", "disk1-doris"},
138+
},
139+
{
140+
input: []string{"/home/disk1/doris", "/home/disk1/doris", "/home/disk2/doris"},
141+
want: []string{"disk1-doris", "disk1-doris", "disk2-doris"},
142+
},
143+
{
144+
input: []string{"/home/disk1/doris", "/home/disk2/doris", "/home/disk3/doris"},
145+
want: []string{"doris", "disk2-doris", "disk3-doris"},
146+
},
147+
{
148+
input: []string{"/home/disk1/doris", "/home/disk1/doris/subdir"},
149+
want: []string{"doris", "subdir"},
150+
},
151+
{
152+
input: []string{"/home/disk1/doris", "/home/disk1/doris/subdir", "/home/disk1/doris/subdir/subsubdir"},
153+
want: []string{"doris", "subdir", "subsubdir"},
154+
},
155+
{
156+
input: []string{"/home/disk1/doris", "/home/disk1/doris/subdir", "/home/disk1/doris/subdir/subsubdir", "/home/disk1/doris/subdir/subsubdir"},
157+
want: []string{"doris", "subdir", "subdir-subsubdir", "subdir-subsubdir"},
158+
},
159+
{
160+
input: []string{"/home/disk1/doris", "/home/disk1/doris", "/home/disk2/doris"},
161+
want: []string{"disk1-doris", "disk1-doris", "disk2-doris"},
162+
},
163+
}
164+
165+
for _, tt := range tests {
166+
t.Run(tt.name, func(t *testing.T) {
167+
got := GetNameOfEachPath(tt.input)
168+
if !reflect.DeepEqual(got, tt.want) {
169+
t.Errorf("input: %v, got %v, Expectation %v", tt.input, got, tt.want)
170+
}
171+
})
172+
}
173+
}

pkg/common/utils/resource/configmap.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/spf13/viper"
2525
corev1 "k8s.io/api/core/v1"
2626
"k8s.io/klog/v2"
27+
"os"
2728
)
2829

2930
// the fe ports key
@@ -124,7 +125,9 @@ func ResolveConfigMaps(configMaps []*corev1.ConfigMap, componentType dorisv1.Com
124125
continue
125126
}
126127
if value, ok := configMap.Data[key]; ok {
128+
os.Setenv("DORIS_HOME", getDefaultDorisHome(componentType))
127129
viper.SetConfigType("properties")
130+
viper.AutomaticEnv()
128131
viper.ReadConfig(bytes.NewBuffer([]byte(value)))
129132
return viper.AllSettings(), nil
130133
}

pkg/common/utils/resource/persistent_volume_claim.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ package resource
1919

2020
import (
2121
dorisv1 "github.com/apache/doris-operator/api/doris/v1"
22+
"github.com/apache/doris-operator/pkg/common/utils/doris"
2223
"github.com/apache/doris-operator/pkg/common/utils/hash"
2324
corev1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/klog/v2"
2527
)
2628

2729
var (
@@ -66,3 +68,87 @@ func buildPVCAnnotations(volume dorisv1.PersistentVolume) Annotations {
6668
}
6769
return annotations
6870
}
71+
72+
func getDefaultDorisHome(componentType dorisv1.ComponentType) string {
73+
switch componentType {
74+
case dorisv1.Component_FE:
75+
return DEFAULT_ROOT_PATH + "/fe"
76+
case dorisv1.Component_BE, dorisv1.Component_CN:
77+
return DEFAULT_ROOT_PATH + "/be"
78+
case dorisv1.Component_Broker:
79+
return DEFAULT_ROOT_PATH + "/apache_hdfs_broker"
80+
default:
81+
klog.Infof("the componentType: %s have not default DORIS_HOME", componentType)
82+
}
83+
return ""
84+
}
85+
86+
// GenerateEveryoneMountPathPersistentVolume is used to process the pvc template configuration in CRD.
87+
// The template is defined as follows:
88+
// - PersistentVolume.MountPath is "", it`s template configuration.
89+
// - PersistentVolume.MountPath is not "", it`s actual pvc configuration.
90+
// The Explain rules are as follows:
91+
// 1. Non-templated PersistentVolumes are returned directly in the result list.
92+
// 2. If there is a pvc template, return the actual list of pvcs after processing.
93+
// 3. The template needs to parse the configuration of the doris config file to create the pvc.
94+
// 4. If there are multiple templates, the last valid template will be used.
95+
func GenerateEveryoneMountPathPersistentVolume(spec *dorisv1.BaseSpec, config map[string]interface{}, componentType dorisv1.ComponentType) ([]dorisv1.PersistentVolume, error) {
96+
97+
// Only the last data pvc template configuration takes effect
98+
var template *dorisv1.PersistentVolume
99+
// pvs is the pvc that needs to be actually created, specified by the user
100+
var pvs []dorisv1.PersistentVolume
101+
102+
for i := range spec.PersistentVolumes {
103+
if spec.PersistentVolumes[i].MountPath != "" {
104+
pvs = append(pvs, spec.PersistentVolumes[i])
105+
106+
} else {
107+
template = (&spec.PersistentVolumes[i]).DeepCopy()
108+
}
109+
}
110+
111+
if template == nil {
112+
return pvs, nil
113+
}
114+
115+
// Processing pvc template
116+
var dataPathKey, dataDefaultPath string
117+
var dataPaths []string
118+
dorisHome := getDefaultDorisHome(componentType)
119+
switch componentType {
120+
case dorisv1.Component_FE:
121+
dataPathKey = "meta_dir"
122+
dataDefaultPath = dorisHome + "/doris-meta"
123+
case dorisv1.Component_BE, dorisv1.Component_CN:
124+
dataPathKey = "storage_root_path"
125+
dataDefaultPath = dorisHome + "/storage"
126+
default:
127+
klog.Infof("GenerateEveryoneMountPathPersistentVolume the componentType: %s is not supported, PersistentVolume template will not work ", componentType)
128+
return pvs, nil
129+
}
130+
131+
dataPathValue, dataExist := config[dataPathKey]
132+
if !dataExist {
133+
klog.Infof("GenerateEveryoneMountPathPersistentVolume: dataPathKey '%s' not found in config, default value will effect", dataPathKey)
134+
dataPaths = append(dataPaths, dataDefaultPath)
135+
} else {
136+
dataPaths = doris.ResolveStorageRootPath(dataPathValue.(string))
137+
}
138+
139+
if len(dataPaths) == 1 {
140+
tmp := *template.DeepCopy()
141+
tmp.MountPath = dataPaths[0]
142+
pvs = append(pvs, tmp)
143+
} else {
144+
pathName := doris.GetNameOfEachPath(dataPaths)
145+
for i := range dataPaths {
146+
tmp := *template.DeepCopy()
147+
tmp.Name = tmp.Name + "-" + pathName[i]
148+
tmp.MountPath = dataPaths[i]
149+
pvs = append(pvs, tmp)
150+
}
151+
}
152+
153+
return pvs, nil
154+
}

0 commit comments

Comments
 (0)