| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| package sub_controller | |
| import ( | |
| "bytes" | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "os" | |
| "path" | |
| "path/filepath" | |
| "strconv" | |
| "strings" | |
| "github.com/apache/doris-operator/api/disaggregated/v1" | |
| "github.com/apache/doris-operator/pkg/common/utils/k8s" | |
| "github.com/apache/doris-operator/pkg/common/utils/metadata" | |
| "github.com/apache/doris-operator/pkg/common/utils/mysql" | |
| "github.com/apache/doris-operator/pkg/common/utils/resource" | |
| "github.com/apache/doris-operator/pkg/common/utils/set" | |
| "github.com/spf13/viper" | |
| appv1 "k8s.io/api/apps/v1" | |
| corev1 "k8s.io/api/core/v1" | |
| apierrors "k8s.io/apimachinery/pkg/api/errors" | |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
| "k8s.io/apimachinery/pkg/types" | |
| "k8s.io/client-go/tools/record" | |
| "k8s.io/klog/v2" | |
| "sigs.k8s.io/controller-runtime/pkg/client" | |
| ) | |
| const ( | |
| //doris use LOG_DIR as the key of log path. this update from 2.1.7 | |
| //metaservice use log_dir, when used please ignore case-sensitive | |
| oldLogPathKey = "LOG_DIR" | |
| newLogPathKey = "sys_log_dir" | |
| FEMetaPathKey = "meta_dir" | |
| FELogStoreName = "fe-log" | |
| FEMetaStoreName = "fe-meta" | |
| BELogStoreName = "be-log" | |
| BECacheStorePreName = "be-storage" | |
| MSLogStoreName = "ms-log" | |
| DefaultCacheRootPath = "/opt/apache-doris/be/file_cache" | |
| //default cache storage size: unit=B | |
| DefaultCacheSize int64 = 107374182400 | |
| FileCachePathKey = "file_cache_path" | |
| FileCacheSubConfigPathKey = "path" | |
| FileCacheSubConfigTotalSizeKey = "total_size" | |
| ) | |
| type DisaggregatedSubController interface { | |
| //Sync reconcile for sub controller. bool represent the component have updated. | |
| Sync(ctx context.Context, obj client.Object) error | |
| //clear all resource about sub-component. | |
| ClearResources(ctx context.Context, obj client.Object) (bool, error) | |
| //return the controller name, beController, feController,cnController for log. | |
| GetControllerName() string | |
| //UpdateStatus update the component status on src. | |
| UpdateComponentStatus(obj client.Object) error | |
| } | |
| type DisaggregatedSubDefaultController struct { | |
| K8sclient client.Client | |
| K8srecorder record.EventRecorder | |
| ControllerName string | |
| } | |
| func (d *DisaggregatedSubDefaultController) GetConfigValuesFromConfigMaps(namespace string, resolveKey string, cms []v1.ConfigMap) map[string]interface{} { | |
| if len(cms) == 0 { | |
| return nil | |
| } | |
| for _, cm := range cms { | |
| kcm, err := k8s.GetConfigMap(context.Background(), d.K8sclient, namespace, cm.Name) | |
| if err != nil { | |
| klog.Errorf("disaggregatedFEController getConfigValuesFromConfigMaps namespace=%s, name=%s, failed, err=%s", namespace, cm.Name, err.Error()) | |
| continue | |
| } | |
| if _, ok := kcm.Data[resolveKey]; !ok { | |
| continue | |
| } | |
| v := kcm.Data[resolveKey] | |
| return d.resolveStartConfig([]byte(v), resolveKey) | |
| } | |
| return nil | |
| } | |
| func (d *DisaggregatedSubDefaultController) resolveStartConfig(vb []byte, resolveKey string) map[string]interface{} { | |
| switch resolveKey { | |
| case resource.MS_RESOLVEKEY: | |
| os.Setenv("DORIS_HOME", resource.DEFAULT_ROOT_PATH+"/ms") | |
| case resource.FE_RESOLVEKEY: | |
| os.Setenv("DORIS_HOME", resource.DEFAULT_ROOT_PATH+"/fe") | |
| case resource.BE_RESOLVEKEY: | |
| os.Setenv("DORIS_HOME", resource.DEFAULT_ROOT_PATH+"/be") | |
| default: | |
| } | |
| viper.SetConfigType("properties") | |
| viper.ReadConfig(bytes.NewBuffer(vb)) | |
| return viper.AllSettings() | |
| } | |
| // for config default values. | |
| func (d *DisaggregatedSubDefaultController) NewDefaultService(ddc *v1.DorisDisaggregatedCluster) *corev1.Service { | |
| return &corev1.Service{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Namespace: ddc.Namespace, | |
| OwnerReferences: []metav1.OwnerReference{ | |
| metav1.OwnerReference{ | |
| APIVersion: ddc.APIVersion, | |
| Kind: ddc.Kind, | |
| Name: ddc.Name, | |
| UID: ddc.UID, | |
| }, | |
| }, | |
| }, | |
| Spec: corev1.ServiceSpec{ | |
| SessionAffinity: corev1.ServiceAffinityClientIP, | |
| }, | |
| } | |
| } | |
| func (d *DisaggregatedSubDefaultController) NewDefaultStatefulset(ddc *v1.DorisDisaggregatedCluster) *appv1.StatefulSet { | |
| return &appv1.StatefulSet{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Namespace: ddc.Namespace, | |
| OwnerReferences: []metav1.OwnerReference{resource.GetOwnerReference(ddc)}, | |
| }, | |
| Spec: appv1.StatefulSetSpec{ | |
| PodManagementPolicy: appv1.ParallelPodManagement, | |
| RevisionHistoryLimit: metadata.GetInt32Pointer(5), | |
| UpdateStrategy: appv1.StatefulSetUpdateStrategy{ | |
| Type: appv1.RollingUpdateStatefulSetStrategyType, | |
| RollingUpdate: &appv1.RollingUpdateStatefulSetStrategy{ | |
| Partition: metadata.GetInt32Pointer(0), | |
| }, | |
| }, | |
| }, | |
| } | |
| } | |
| func (d *DisaggregatedSubDefaultController) BuildDefaultConfigMapVolumesVolumeMounts(cms []v1.ConfigMap) ([]corev1.Volume, []corev1.VolumeMount) { | |
| var vs []corev1.Volume | |
| var vms []corev1.VolumeMount | |
| for _, cm := range cms { | |
| v := corev1.Volume{ | |
| Name: cm.Name, | |
| VolumeSource: corev1.VolumeSource{ | |
| ConfigMap: &corev1.ConfigMapVolumeSource{ | |
| LocalObjectReference: corev1.LocalObjectReference{ | |
| Name: cm.Name, | |
| }, | |
| }, | |
| }, | |
| } | |
| vs = append(vs, v) | |
| vm := corev1.VolumeMount{ | |
| Name: cm.Name, | |
| MountPath: cm.MountPath, | |
| } | |
| if vm.MountPath == "" { | |
| vm.MountPath = resource.ConfigEnvPath | |
| } | |
| vms = append(vms, vm) | |
| } | |
| return vs, vms | |
| } | |
| func (d *DisaggregatedSubDefaultController) ConstructDefaultAffinity(matchKey, value string, ddcAffinity *corev1.Affinity) *corev1.Affinity { | |
| affinity := d.newDefaultAffinity(matchKey, value) | |
| if ddcAffinity == nil { | |
| return affinity | |
| } | |
| ddcPodAntiAffinity := ddcAffinity.PodAntiAffinity | |
| if ddcPodAntiAffinity != nil { | |
| affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = ddcPodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution | |
| affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, ddcPodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution...) | |
| } | |
| affinity.NodeAffinity = ddcAffinity.NodeAffinity | |
| affinity.PodAffinity = ddcAffinity.PodAffinity | |
| return affinity | |
| } | |
| func (d *DisaggregatedSubDefaultController) newDefaultAffinity(matchKey, value string) *corev1.Affinity { | |
| if matchKey == "" || value == "" { | |
| return nil | |
| } | |
| podAffinityTerm := corev1.WeightedPodAffinityTerm{ | |
| Weight: 20, | |
| PodAffinityTerm: corev1.PodAffinityTerm{ | |
| LabelSelector: &metav1.LabelSelector{ | |
| MatchExpressions: []metav1.LabelSelectorRequirement{ | |
| {Key: matchKey, Operator: metav1.LabelSelectorOpIn, Values: []string{value}}, | |
| }, | |
| }, | |
| TopologyKey: resource.NODE_TOPOLOGYKEY, | |
| }, | |
| } | |
| return &corev1.Affinity{ | |
| PodAntiAffinity: &corev1.PodAntiAffinity{ | |
| PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{podAffinityTerm}, | |
| }, | |
| } | |
| } | |
| // the common logic to apply service, will used by fe,be,ms. | |
| func (d *DisaggregatedSubDefaultController) DefaultReconcileService(ctx context.Context, svc *corev1.Service) (*Event, error) { | |
| if err := k8s.ApplyService(ctx, d.K8sclient, svc, func(nsvc, osvc *corev1.Service) bool { | |
| return resource.ServiceDeepEqualWithAnnoKey(nsvc, osvc, v1.DisaggregatedSpecHashValueAnnotation) | |
| }); err != nil { | |
| klog.Errorf("disaggregatedSubDefaultController reconcileService apply service namespace=%s name=%s failed, err=%s", svc.Namespace, svc.Name, err.Error()) | |
| return &Event{Type: EventWarning, Reason: ServiceApplyedFailed, Message: err.Error()}, err | |
| } | |
| return nil, nil | |
| } | |
| // generate map for mountpath:secret | |
| func (d *DisaggregatedSubDefaultController) CheckSecretMountPath(ddc *v1.DorisDisaggregatedCluster, secrets []v1.Secret) { | |
| var mountsMap = make(map[string]v1.Secret) | |
| for _, secret := range secrets { | |
| path := secret.MountPath | |
| if s, exist := mountsMap[path]; exist { | |
| klog.Errorf("CheckSecretMountPath error: the mountPath %s is repeated between secret: %s and secret: %s.", path, secret.SecretName, s.SecretName) | |
| d.K8srecorder.Event(ddc, string(EventWarning), string(SecretPathRepeated), fmt.Sprintf("the mountPath %s is repeated between secret: %s and secret: %s.", path, secret.SecretName, s.SecretName)) | |
| } | |
| mountsMap[path] = secret | |
| } | |
| } | |
| // CheckSecretExist, check the secret exist or not in specify namespace. | |
| func (d *DisaggregatedSubDefaultController) CheckSecretExist(ctx context.Context, ddc *v1.DorisDisaggregatedCluster, secrets []v1.Secret) { | |
| errMessage := "" | |
| for _, secret := range secrets { | |
| var s corev1.Secret | |
| if getErr := d.K8sclient.Get(ctx, types.NamespacedName{Namespace: ddc.Namespace, Name: secret.SecretName}, &s); getErr != nil { | |
| errMessage = errMessage + fmt.Sprintf("(name: %s, namespace: %s, err: %s), ", secret.SecretName, ddc.Namespace, getErr.Error()) | |
| } | |
| } | |
| if errMessage != "" { | |
| klog.Errorf("CheckSecretExist error: %s.", errMessage) | |
| d.K8srecorder.Event(ddc, string(EventWarning), string(SecretNotExist), fmt.Sprintf("CheckSecretExist error: %s.", errMessage)) | |
| } | |
| } | |
| // RestrictConditionsEqual adds two StatefulSet, | |
| // It is used to control the conditions for comparing. | |
| func (d *DisaggregatedSubDefaultController) RestrictConditionsEqual(new *appv1.StatefulSet, old *appv1.StatefulSet) { | |
| //shield persistent volume update when the pvcProvider=Operator | |
| //in webhook should intercept the volume spec updated when use statefulset pvc. | |
| // TODO: updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden | |
| if len(old.Spec.VolumeClaimTemplates) != 0 { | |
| new.Spec.VolumeClaimTemplates = old.Spec.VolumeClaimTemplates | |
| } | |
| } | |
| func (d *DisaggregatedSubDefaultController) GetManagementAdminUserAndPWD(ctx context.Context, ddc *v1.DorisDisaggregatedCluster) (string, string) { | |
| adminUserName := "root" | |
| password := "" | |
| if ddc.Spec.AuthSecret != "" { | |
| secret, _ := k8s.GetSecret(ctx, d.K8sclient, ddc.Namespace, ddc.Spec.AuthSecret) | |
| adminUserName, password = resource.GetDorisLoginInformation(secret) | |
| } else if ddc.Spec.AdminUser != nil { | |
| adminUserName = ddc.Spec.AdminUser.Name | |
| password = ddc.Spec.AdminUser.Password | |
| } | |
| return adminUserName, password | |
| } | |
| // add cluster specification on container spec. this is useful to add common spec on different type pods, example: kerberos volume for fe and be. | |
| func (d *DisaggregatedSubDefaultController) AddClusterSpecForPodTemplate(componentType v1.DisaggregatedComponentType, configMap map[string]interface{}, spec *v1.DorisDisaggregatedClusterSpec, pts *corev1.PodTemplateSpec) { | |
| var c *corev1.Container | |
| switch componentType { | |
| case v1.DisaggregatedFE: | |
| for i, _ := range pts.Spec.Containers { | |
| if pts.Spec.Containers[i].Name == resource.DISAGGREGATED_FE_MAIN_CONTAINER_NAME { | |
| c = &pts.Spec.Containers[i] | |
| break | |
| } | |
| } | |
| case v1.DisaggregatedBE: | |
| for i, _ := range pts.Spec.Containers { | |
| if pts.Spec.Containers[i].Name == resource.DISAGGREGATED_BE_MAIN_CONTAINER_NAME { | |
| c = &pts.Spec.Containers[i] | |
| break | |
| } | |
| } | |
| default: | |
| klog.Errorf("DisaggregatedSubDefaultController AddClusterSpecForPodTemplate componentType %s not supported.", componentType) | |
| return | |
| } | |
| //add pod envs | |
| envs := resource.BuildKerberosEnvForDDC(spec.KerberosInfo, configMap, componentType) | |
| if len(envs) != 0 { | |
| c.Env = append(c.Env, envs...) | |
| } | |
| //add kerberos volumeMounts and volumes | |
| volumes, volumeMounts := resource.GetDv1KerberosVolumeAndVolumeMount(spec.KerberosInfo) | |
| if len(volumeMounts) != 0 { | |
| c.VolumeMounts = append(c.VolumeMounts, volumeMounts...) | |
| } | |
| if len(volumes) != 0 { | |
| pts.Spec.Volumes = append(pts.Spec.Volumes, volumes...) | |
| } | |
| } | |
| // return which generation had updated the statefulset. | |
| func (d *DisaggregatedSubDefaultController) ReturnStatefulsetUpdatedGeneration(sts *appv1.StatefulSet, annoGenerationKey string) int64 { | |
| if sts == nil { | |
| return 0 | |
| } | |
| if len(sts.Annotations) == 0 { | |
| return 0 | |
| } | |
| g_str := sts.Annotations[annoGenerationKey] | |
| //if g_str is empty, g will be zero, this is our expectation, so ignore parse failed or not. | |
| g, _ := strconv.ParseInt(g_str, 10, 64) | |
| return g | |
| } | |
| // use statefulset.status.updateRevision and pod `controller-revision-hash` annotation to check pods updated to new revision. | |
| // if all pods used new updateRevision return true, else return false. | |
| func (d *DisaggregatedSubDefaultController) StatefulsetControlledPodsAllUseNewUpdateRevision(stsUpdateRevision string, pods []corev1.Pod) bool { | |
| if stsUpdateRevision == "" { | |
| return false | |
| } | |
| if len(pods) == 0 { | |
| return false | |
| } | |
| for _, pod := range pods { | |
| labels := pod.Labels | |
| podControlledRevision := labels[resource.POD_CONTROLLER_REVISION_HASH_KEY] | |
| //if use selector filter pods have one controlled by new revision of statefulset, represents the new revision is working. | |
| if stsUpdateRevision != podControlledRevision { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func (d *DisaggregatedSubDefaultController) ReconcilePVC( | |
| ctx context.Context, | |
| ddc *v1.DorisDisaggregatedCluster, | |
| cm map[string]interface{}, | |
| componentType v1.DisaggregatedComponentType, | |
| sts *appv1.StatefulSet, | |
| cg *v1.ComputeGroup, | |
| ) (*Event, error) { | |
| var commonSpec *v1.CommonSpec | |
| switch componentType { | |
| case v1.DisaggregatedMS: | |
| commonSpec = &ddc.Spec.MetaService.CommonSpec | |
| case v1.DisaggregatedFE: | |
| commonSpec = &ddc.Spec.FeSpec.CommonSpec | |
| case v1.DisaggregatedBE: | |
| commonSpec = &cg.CommonSpec | |
| default: | |
| } | |
| _, _, pvcTemplates := d.BuildVolumesVolumeMountsAndPVCs(cm, componentType, commonSpec) | |
| oldPvcList := corev1.PersistentVolumeClaimList{} | |
| selector := sts.Spec.Selector.MatchLabels | |
| if err := d.K8sclient.List(ctx, &oldPvcList, client.InNamespace(ddc.Namespace), client.MatchingLabels(selector)); err != nil { | |
| message := fmt.Sprintf("ReconcilePVC list pvc failed, namespace: %s, name: %s, error: %s", ddc.Namespace, ddc.Name, err.Error()) | |
| klog.Error(message) | |
| return &Event{Type: EventWarning, Reason: PVCListFailed, Message: message}, err | |
| } | |
| for i := range pvcTemplates { | |
| manager := pvcTemplates[i].Annotations[resource.PVCManagerAnnotationApache] | |
| if manager != string(v1.PVCProvisionerOperator) { | |
| continue | |
| } | |
| for ordinal := range *commonSpec.Replicas { | |
| pvc := resource.BuildDisaggregatedPVC(pvcTemplates[i], selector, ddc.Namespace, sts.Name, strconv.FormatInt(int64(ordinal), 10)) | |
| oldPvc := getPvc(oldPvcList, pvc.Name) | |
| // need add new pvc, | |
| // however, this feature seems to have some additional limitations and may require recreating the statefulset. | |
| // The impact of this behavior on the stability of the Doris cluster is still under investigation; | |
| // therefore, the recreating statefulset needs to be performed manually. | |
| if oldPvc == nil { | |
| if err := d.K8sclient.Create(ctx, &pvc); err != nil && !apierrors.IsAlreadyExists(err) { | |
| message := fmt.Sprintf("ReconcilePVC create pvc failed, namespace: %s, name: %s create pvc %s, error %s.", ddc.Namespace, ddc.Name, pvc.Name, err.Error()) | |
| //d.K8srecorder.Event(ddc, string(EventWarning), PVCCreateFailed, message) | |
| klog.Error(message) | |
| return &Event{Type: EventWarning, Reason: PVCCreateFailed, Message: message}, err | |
| } | |
| message := fmt.Sprintf("ReconcilePVC create pvc, namespace: %s, name: %s create pvc %s .", ddc.Namespace, ddc.Name, pvc.Name) | |
| klog.Infof(message) | |
| d.K8srecorder.Event(ddc, string(EventNormal), PVCCreate, message) | |
| continue | |
| } | |
| oldQuantity := oldPvc.Spec.Resources.Requests[corev1.ResourceStorage] | |
| newQuantity := pvc.Spec.Resources.Requests[corev1.ResourceStorage] | |
| //if !oldQuantity.Equal(newQuantity){ | |
| if oldQuantity.Cmp(newQuantity) == -1 { | |
| // pvc need update | |
| oldPvc.Spec.Resources.Requests[corev1.ResourceStorage] = newQuantity | |
| if err := d.K8sclient.Patch(ctx, oldPvc, client.Merge); err != nil { | |
| message := fmt.Sprintf("ReconcilePVC patch pvc failed, namespace: %s, ddc name: %s, patch pvc %s, error: %s", ddc.Namespace, ddc.Name, pvc.Name, err.Error()) | |
| klog.Errorf(message) | |
| return &Event{Type: EventWarning, Reason: PVCUpdateFailed, Message: message}, err | |
| } | |
| message := fmt.Sprintf("ReconcilePVC patch pvc, namespace: %s, ddc name: %s update pvc %s .", ddc.Namespace, ddc.Name, pvc.Name) | |
| klog.Infof(message) | |
| d.K8srecorder.Event(ddc, string(EventNormal), PVCUpdate, message) | |
| } | |
| if oldQuantity.Cmp(newQuantity) == 1 { | |
| message := fmt.Sprintf("ReconcilePVC pvc resize is rejected, PVC shrinking is not supported. namespace: %s, ddc name: %s, resize pvc %s", ddc.Namespace, ddc.Name, pvc.Name) | |
| klog.Warningf(message) | |
| d.K8srecorder.Event(ddc, string(EventWarning), PVCUpdateFailed, message) | |
| } | |
| } | |
| } | |
| return nil, nil | |
| } | |
| func getPvc(pvcs corev1.PersistentVolumeClaimList, pvcName string) *corev1.PersistentVolumeClaim { | |
| for _, pvc := range pvcs.Items { | |
| if pvc.Name == pvcName { | |
| return &pvc | |
| } | |
| } | |
| return nil | |
| } | |
| func (d *DisaggregatedSubDefaultController) BuildVolumesVolumeMountsAndPVCs(confMap map[string]interface{}, componentType v1.DisaggregatedComponentType, commonSpec *v1.CommonSpec) ([]corev1.Volume, []corev1.VolumeMount, []corev1.PersistentVolumeClaim) { | |
| if commonSpec.PersistentVolume == nil && len(commonSpec.PersistentVolumes) == 0 { | |
| vs, vms := d.getEmptyDirVolumesVolumeMounts(confMap, componentType) | |
| return vs, vms, nil | |
| } | |
| if commonSpec.PersistentVolume != nil { | |
| return d.PersistentVolumeBuildVolumesVolumeMountsAndPVCs(commonSpec, confMap, componentType) | |
| } | |
| return d.PersistentVolumeArrayBuildVolumesVolumeMountsAndPVCs(commonSpec, confMap, componentType) | |
| } | |
| // PersistentVolumeBuildVolumesVolumeMountsAndPVCs the old config before 25.2.1, the requiredPaths should filter log path before call this function. | |
| func (d *DisaggregatedSubDefaultController) PersistentVolumeBuildVolumesVolumeMountsAndPVCs(commonSpec *v1.CommonSpec, confMap map[string]interface{}, componentType v1.DisaggregatedComponentType) ([]corev1.Volume, []corev1.VolumeMount, []corev1.PersistentVolumeClaim) { | |
| v1pv := commonSpec.PersistentVolume | |
| if v1pv == nil { | |
| return nil, nil, nil | |
| } | |
| pathName := map[string]string{} /*key=path, value=name*/ | |
| var requiredPaths []string | |
| switch componentType { | |
| case v1.DisaggregatedMS: | |
| //if logNotStore anywhere is true, not build pvc. | |
| if !commonSpec.PersistentVolume.LogNotStore && !commonSpec.LogNotStore { | |
| logPath := d.getLogPath(confMap, componentType) | |
| pathName[logPath] = MSLogStoreName | |
| requiredPaths = append(requiredPaths, logPath) | |
| } | |
| case v1.DisaggregatedFE: | |
| if !commonSpec.PersistentVolume.LogNotStore && !commonSpec.LogNotStore { | |
| logPath := d.getLogPath(confMap, componentType) | |
| pathName[logPath] = FELogStoreName | |
| requiredPaths = append(requiredPaths, logPath) | |
| } | |
| metaPath := d.getFEMetaPath(confMap) | |
| pathName[metaPath] = FEMetaStoreName | |
| requiredPaths = append(requiredPaths, metaPath) | |
| case v1.DisaggregatedBE: | |
| if !commonSpec.PersistentVolume.LogNotStore && !commonSpec.LogNotStore { | |
| logPath := d.getLogPath(confMap, componentType) | |
| pathName[logPath] = BELogStoreName | |
| requiredPaths = append(requiredPaths, logPath) | |
| } | |
| cachePaths, _ := d.getCacheMaxSizeAndPaths(confMap) | |
| for i, _ := range cachePaths { | |
| path_i := BECacheStorePreName + strconv.Itoa(i) | |
| pathName[cachePaths[i]] = path_i | |
| requiredPaths = append(requiredPaths, cachePaths[i]) | |
| } | |
| //generate the last path's name, the ordinal is length of cache paths. | |
| baseIndex := len(cachePaths) | |
| for _, path := range v1pv.MountPaths { | |
| if _, ok := pathName[path]; ok { | |
| //compatible before name= storage+i | |
| continue | |
| } | |
| requiredPaths = append(requiredPaths, path) | |
| pathName[path] = BECacheStorePreName + strconv.Itoa(baseIndex) | |
| baseIndex = baseIndex + 1 | |
| } | |
| default: | |
| } | |
| var vs []corev1.Volume | |
| var vms []corev1.VolumeMount | |
| var pvcs []corev1.PersistentVolumeClaim | |
| for _, path := range requiredPaths { | |
| name := pathName[path] | |
| vs = append(vs, corev1.Volume{Name: name, VolumeSource: corev1.VolumeSource{ | |
| PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ | |
| ClaimName: name, | |
| }}}) | |
| vms = append(vms, corev1.VolumeMount{Name: name, MountPath: path}) | |
| pvcs = append(pvcs, corev1.PersistentVolumeClaim{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: name, | |
| Annotations: resource.BuildDisaggregatedPVCAnnotations(*v1pv), | |
| }, | |
| Spec: *v1pv.PersistentVolumeClaimSpec.DeepCopy(), | |
| }) | |
| } | |
| return vs, vms, pvcs | |
| } | |
| // PersistentVolumeArrayBuildVolumesVolumeMountsAndPVCs use array of PersistentVolume, the new config from 25.2.x | |
| func (d *DisaggregatedSubDefaultController) PersistentVolumeArrayBuildVolumesVolumeMountsAndPVCs(commonSpec *v1.CommonSpec, confMap map[string]interface{}, componentType v1.DisaggregatedComponentType) ([]corev1.Volume, []corev1.VolumeMount, []corev1.PersistentVolumeClaim) { | |
| var requiredPaths []string | |
| //find storage mountPaths. | |
| switch componentType { | |
| case v1.DisaggregatedFE: | |
| metaPath := d.getFEMetaPath(confMap) | |
| requiredPaths = append(requiredPaths, metaPath) | |
| case v1.DisaggregatedBE: | |
| cachePaths, _ := d.getCacheMaxSizeAndPaths(confMap) | |
| requiredPaths = append(requiredPaths, cachePaths...) | |
| default: | |
| } | |
| //check logNotStore, if true should not generate log pvc. | |
| logNotStore := false | |
| for _, v1pv := range commonSpec.PersistentVolumes { | |
| if len(v1pv.MountPaths) != 0 { | |
| requiredPaths = append(requiredPaths, v1pv.MountPaths...) | |
| } | |
| logNotStore = logNotStore || v1pv.LogNotStore | |
| } | |
| //the last check logNotStore, fist check config in any one of persistentVolumes. | |
| if !logNotStore && !commonSpec.LogNotStore { | |
| logPath := d.getLogPath(confMap, componentType) | |
| requiredPaths = append(requiredPaths, logPath) | |
| } | |
| //generate name of persistentVolumeClaim use the mountPath | |
| namePath := map[string]string{} | |
| pathName := map[string]string{} | |
| for _, path := range requiredPaths { | |
| //use unix path separator. | |
| sp := strings.Split(path, "/") | |
| name := "" | |
| for i := 1; i <= len(sp); i++ { | |
| if sp[len(sp)-i] == "" { | |
| continue | |
| } | |
| if name == "" { | |
| name = sp[len(sp)-i] | |
| } else { | |
| name = sp[len(sp)-i] + "-" + name | |
| } | |
| if _, ok := namePath[name]; !ok { | |
| break | |
| } | |
| } | |
| namePath[name] = path | |
| pathName[path] = name | |
| } | |
| pathPV := map[string]*v1.PersistentVolume{} | |
| //the template index. | |
| ti := -1 | |
| for i, v1pv := range commonSpec.PersistentVolumes { | |
| if len(v1pv.MountPaths) == 0 { | |
| ti = i | |
| continue | |
| } | |
| for _, mp := range v1pv.MountPaths { | |
| pathPV[mp] = &commonSpec.PersistentVolumes[i] | |
| } | |
| } | |
| var vs []corev1.Volume | |
| var vms []corev1.VolumeMount | |
| var pvcs []corev1.PersistentVolumeClaim | |
| //generate pvc from the last path in requiredPaths, the mountPath that configured by user is the highest wight, so first use the v1pv to generate pvc not template v1pv. | |
| ss := set.NewSetString() | |
| for i := len(requiredPaths); i > 0; i-- { | |
| path := requiredPaths[i-1] | |
| //if the path have build volume, vm, pvc, skip it. | |
| if ss.Find(path) { | |
| continue | |
| } | |
| ss.Add(path) | |
| pv, ok := pathPV[path] | |
| name := pathName[path] | |
| metadataName := strings.ReplaceAll(name, "_", "-") | |
| //use specific PersistentVolume generate volume, vm, pvc | |
| if ok { | |
| vs = append(vs, corev1.Volume{Name: metadataName, VolumeSource: corev1.VolumeSource{ | |
| PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ | |
| ClaimName: metadataName, | |
| }}}) | |
| vms = append(vms, corev1.VolumeMount{Name: metadataName, MountPath: path}) | |
| pvcs = append(pvcs, corev1.PersistentVolumeClaim{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: metadataName, | |
| Annotations: resource.BuildDisaggregatedPVCAnnotations(*pv), | |
| }, | |
| Spec: *pv.PersistentVolumeClaimSpec.DeepCopy(), | |
| }) | |
| } | |
| //use template PersistentVolume generate volume, vm, pvc | |
| if !ok && ti != -1 { | |
| vs = append(vs, corev1.Volume{Name: metadataName, VolumeSource: corev1.VolumeSource{ | |
| PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ | |
| ClaimName: metadataName, | |
| }}}) | |
| vms = append(vms, corev1.VolumeMount{Name: metadataName, MountPath: path}) | |
| pvcs = append(pvcs, corev1.PersistentVolumeClaim{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: metadataName, | |
| Annotations: resource.BuildDisaggregatedPVCAnnotations(commonSpec.PersistentVolumes[ti]), | |
| }, | |
| Spec: *commonSpec.PersistentVolumes[ti].PersistentVolumeClaimSpec.DeepCopy(), | |
| }) | |
| } | |
| } | |
| return vs, vms, pvcs | |
| } | |
| func (d *DisaggregatedSubDefaultController) getEmptyDirVolumesVolumeMounts(confMap map[string]interface{}, componentType v1.DisaggregatedComponentType) ([]corev1.Volume, []corev1.VolumeMount) { | |
| switch componentType { | |
| case v1.DisaggregatedMS: | |
| return d.getMSEmptyDirVolumesVolumeMounts(confMap) | |
| case v1.DisaggregatedFE: | |
| return d.getFEEmptyDirVolumesVolumeMounts(confMap) | |
| case v1.DisaggregatedBE: | |
| return d.getBEEmptyDirVolumesVolumeMounts(confMap) | |
| default: | |
| return nil, nil | |
| } | |
| } | |
| func (d *DisaggregatedSubDefaultController) getBEEmptyDirVolumesVolumeMounts(confMap map[string]interface{}) ([]corev1.Volume, []corev1.VolumeMount) { | |
| vs := []corev1.Volume{ | |
| { | |
| Name: BELogStoreName, | |
| VolumeSource: corev1.VolumeSource{ | |
| EmptyDir: &corev1.EmptyDirVolumeSource{}, | |
| }, | |
| }, | |
| } | |
| vms := []corev1.VolumeMount{ | |
| { | |
| Name: BELogStoreName, | |
| MountPath: d.getLogPath(confMap, v1.DisaggregatedBE), | |
| }, | |
| } | |
| cachePaths, _ := d.getCacheMaxSizeAndPaths(confMap) | |
| for i, path := range cachePaths { | |
| vs = append(vs, corev1.Volume{ | |
| Name: BECacheStorePreName + strconv.Itoa(i), | |
| VolumeSource: corev1.VolumeSource{ | |
| EmptyDir: &corev1.EmptyDirVolumeSource{}, | |
| }, | |
| }) | |
| vms = append(vms, corev1.VolumeMount{ | |
| Name: BECacheStorePreName + strconv.Itoa(i), | |
| MountPath: path, | |
| }) | |
| } | |
| return vs, vms | |
| } | |
| func (d *DisaggregatedSubDefaultController) getCacheMaxSizeAndPaths(cvs map[string]interface{}) ([]string, int64) { | |
| v := cvs[FileCachePathKey] | |
| if v == nil { | |
| return []string{DefaultCacheRootPath}, DefaultCacheSize | |
| } | |
| var paths []string | |
| var maxCacheSize int64 | |
| vbys := v.(string) | |
| var pa []map[string]interface{} | |
| err := json.Unmarshal([]byte(vbys), &pa) | |
| if err != nil { | |
| klog.Errorf("disaggregatedComputeGroupsController getStorageMaxSizeAndPaths json unmarshal file_cache_path failed, err=%s", err.Error()) | |
| return []string{}, 0 | |
| } | |
| for i, mp := range pa { | |
| pv := mp[FileCacheSubConfigPathKey] | |
| pv_str, ok := pv.(string) | |
| if !ok { | |
| klog.Errorf("disaggregatedComputeGroupsController getStorageMaxSizeAndPaths index %d have not path config.", i) | |
| continue | |
| } | |
| paths = append(paths, pv_str) | |
| cache_v := mp[FileCacheSubConfigTotalSizeKey] | |
| fc_size, ok := cache_v.(float64) | |
| cache_size := int64(fc_size) | |
| if !ok { | |
| klog.Errorf("disaggregatedComputeGroupsController getStorageMaxSizeAndPaths index %d total_size is not number.", i) | |
| continue | |
| } | |
| if maxCacheSize < cache_size { | |
| maxCacheSize = cache_size | |
| } | |
| } | |
| return paths, maxCacheSize | |
| } | |
| // use emptyDir mode generate metaservice use volume and volumeMount. | |
| func (d *DisaggregatedSubDefaultController) getMSEmptyDirVolumesVolumeMounts(confMap map[string]interface{}) ([]corev1.Volume, []corev1.VolumeMount) { | |
| vs := []corev1.Volume{ | |
| { | |
| Name: MSLogStoreName, | |
| VolumeSource: corev1.VolumeSource{ | |
| EmptyDir: &corev1.EmptyDirVolumeSource{}, | |
| }, | |
| }, | |
| } | |
| vms := []corev1.VolumeMount{ | |
| { | |
| Name: MSLogStoreName, | |
| MountPath: d.getLogPath(confMap, v1.DisaggregatedMS), | |
| }, | |
| } | |
| return vs, vms | |
| } | |
| // use emptyDir mode generate fe use volume and volumeMount. | |
| func (d *DisaggregatedSubDefaultController) getFEEmptyDirVolumesVolumeMounts(confMap map[string]interface{}) ([]corev1.Volume, []corev1.VolumeMount) { | |
| vs := []corev1.Volume{ | |
| { | |
| Name: FELogStoreName, | |
| VolumeSource: corev1.VolumeSource{ | |
| EmptyDir: &corev1.EmptyDirVolumeSource{}, | |
| }, | |
| }, | |
| { | |
| Name: FEMetaStoreName, | |
| VolumeSource: corev1.VolumeSource{ | |
| EmptyDir: &corev1.EmptyDirVolumeSource{}, | |
| }, | |
| }, | |
| } | |
| vms := []corev1.VolumeMount{ | |
| { | |
| Name: FELogStoreName, | |
| MountPath: d.getLogPath(confMap, v1.DisaggregatedFE), | |
| }, | |
| { | |
| Name: FEMetaStoreName, | |
| MountPath: d.getFEMetaPath(confMap), | |
| }, | |
| } | |
| return vs, vms | |
| } | |
| // confMap, convert use viper, the viper will convert key to lowercase. | |
| func (d *DisaggregatedSubDefaultController) getLogPath(confMap map[string]interface{}, componentType v1.DisaggregatedComponentType) string { | |
| v := confMap[oldLogPathKey] | |
| if v != nil { | |
| return v.(string) | |
| } | |
| v = confMap[newLogPathKey] | |
| if v != nil { | |
| return v.(string) | |
| } | |
| //return default log path. | |
| switch componentType { | |
| case v1.DisaggregatedMS: | |
| return resource.DEFAULT_ROOT_PATH + "/ms/log" | |
| case v1.DisaggregatedFE: | |
| return resource.DEFAULT_ROOT_PATH + "/fe/log" | |
| case v1.DisaggregatedBE: | |
| return resource.DEFAULT_ROOT_PATH + "/be/log" | |
| default: | |
| return "" | |
| } | |
| } | |
| func (d *DisaggregatedSubDefaultController) getFEMetaPath(confMap map[string]interface{}) string { | |
| v := confMap[FEMetaPathKey] | |
| if v == nil { | |
| return resource.DEFAULT_ROOT_PATH + "/fe/doris-meta" | |
| } | |
| return v.(string) | |
| } | |
| func (d *DisaggregatedSubDefaultController) FindSecretTLSConfig(feConfMap map[string]interface{}, ddc *v1.DorisDisaggregatedCluster) (*mysql.TLSConfig, string /*secret name*/) { | |
| enableTLS := resource.GetString(feConfMap, resource.ENABLE_TLS_KEY) | |
| if enableTLS == "" { | |
| return nil, "" | |
| } | |
| caCertFile := resource.GetString(feConfMap, resource.TLS_CA_CERTIFICATE_PATH_KEY) | |
| clientCertFile := resource.GetString(feConfMap, resource.TLS_CERTIFICATE_PATH_KEY) | |
| clientKeyFile := resource.GetString(feConfMap, resource.TLS_PRIVATE_KEY_PATH_KEY) | |
| caFileName := path.Base(caCertFile) | |
| clientCertFileName := path.Base(clientCertFile) | |
| clientKeyFileName := path.Base(clientKeyFile) | |
| caCertDir := filepath.Dir(caCertFile) | |
| secretName := "" | |
| for _, sn := range ddc.Spec.FeSpec.Secrets { | |
| if sn.MountPath == caCertDir { | |
| secretName = sn.SecretName | |
| break | |
| } | |
| } | |
| tlsConfig := &mysql.TLSConfig{ | |
| CAFileName: caFileName, | |
| ClientCertFileName: clientCertFileName, | |
| ClientKeyFileName: clientKeyFileName, | |
| } | |
| return tlsConfig, secretName | |
| } |