| // 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 resource |
| |
| import ( |
| dv1 "github.com/apache/doris-operator/api/disaggregated/v1" |
| v1 "github.com/apache/doris-operator/api/doris/v1" |
| corev1 "k8s.io/api/core/v1" |
| kr "k8s.io/apimachinery/pkg/api/resource" |
| "k8s.io/utils/pointer" |
| "testing" |
| ) |
| |
| func Test_NewPodTemplateSpec(t *testing.T) { |
| for _, ct := range []v1.ComponentType{"fe", "be", "cn", "broker"} { |
| config := map[string]interface{}{} |
| pt := NewPodTemplateSpec(dcr, config, ct) |
| t.Log(pt) |
| } |
| } |
| |
| func Test_NewContainerWithCommonSpec(t *testing.T) { |
| cs := &dv1.CommonSpec{ |
| Replicas: pointer.Int32(1), |
| Image: "selectdb/doris.be-ubuntu:latest", |
| ContainerSecurityContext: &corev1.SecurityContext{}, |
| ResourceRequirements: corev1.ResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "cpu": kr.MustParse("4"), |
| "memory": kr.MustParse("8Gi"), |
| }, |
| }, |
| } |
| c := NewContainerWithCommonSpec(cs) |
| t.Log(c) |
| } |
| |
| func Test_NewPodTemplateSpecWithCommonSpec(t *testing.T) { |
| tm := make(map[dv1.DisaggregatedComponentType]*dv1.CommonSpec) |
| ccs := &dv1.CommonSpec{ |
| Replicas: pointer.Int32(1), |
| Image: "selectdb/doris.be-ubuntu:latest", |
| ResourceRequirements: corev1.ResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "cpu": kr.MustParse("4"), |
| "memory": kr.MustParse("8Gi"), |
| }, |
| }, |
| SystemInitialization: &dv1.SystemInitialization{ |
| InitImage: "selectdb/doris.alpine:latest", |
| }, |
| PersistentVolume: &dv1.PersistentVolume{ |
| MountPaths: []string{"/opt/apache-doris/be/storage"}, |
| LogNotStore: true, |
| Annotations: map[string]string{ |
| "name": "test", |
| "namespace": "default", |
| }, |
| PersistentVolumeClaimSpec: corev1.PersistentVolumeClaimSpec{ |
| Resources: corev1.VolumeResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "storage": kr.MustParse("200Gi"), |
| }, |
| }, |
| AccessModes: []corev1.PersistentVolumeAccessMode{"ReadAndWriteOnce"}, |
| }, |
| }, |
| } |
| tm[dv1.DisaggregatedBE] = ccs |
| fcs := &dv1.CommonSpec{ |
| Replicas: pointer.Int32(1), |
| Image: "selectdb/doris.fe-ubuntu:latest", |
| ResourceRequirements: corev1.ResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "cpu": kr.MustParse("4"), |
| "memory": kr.MustParse("8Gi"), |
| }, |
| }, |
| PersistentVolume: &dv1.PersistentVolume{ |
| MountPaths: []string{"/opt/apache-doris/fe/doris-meta"}, |
| LogNotStore: false, |
| Annotations: map[string]string{ |
| "name": "test", |
| "namespace": "default", |
| }, |
| PersistentVolumeClaimSpec: corev1.PersistentVolumeClaimSpec{ |
| Resources: corev1.VolumeResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "storage": kr.MustParse("200Gi"), |
| }, |
| }, |
| AccessModes: []corev1.PersistentVolumeAccessMode{"ReadAndWriteOnce"}, |
| }, |
| }, |
| } |
| tm[dv1.DisaggregatedFE] = fcs |
| mcs := &dv1.CommonSpec{ |
| Replicas: pointer.Int32(1), |
| Image: "selectdb/doris.ms-ubuntu:latest", |
| ResourceRequirements: corev1.ResourceRequirements{ |
| Requests: map[corev1.ResourceName]kr.Quantity{ |
| "cpu": kr.MustParse("4"), |
| "memory": kr.MustParse("8Gi"), |
| }, |
| }, |
| } |
| tm[dv1.DisaggregatedMS] = mcs |
| |
| for dct, cs := range tm { |
| pts := NewPodTemplateSpecWithCommonSpec(false, cs, dct) |
| t.Log(pts) |
| } |
| } |
| |
| func Test_NewBaseMainContainer(t *testing.T) { |
| for _, dct := range []v1.ComponentType{v1.Component_FE, v1.Component_BE, v1.Component_CN, v1.Component_Broker} { |
| c := NewBaseMainContainer(dcr, cm, dct) |
| t.Log(c) |
| } |
| } |
| |
| func Test_NewBaseMainContainer_ImagePullPolicy(t *testing.T) { |
| // Unset keeps the previous behaviour, so existing clusters are unaffected by the new field. |
| for _, dct := range []v1.ComponentType{v1.Component_FE, v1.Component_BE, v1.Component_CN, v1.Component_Broker} { |
| c := NewBaseMainContainer(dcr, cm, dct) |
| if c.ImagePullPolicy != corev1.PullIfNotPresent { |
| t.Errorf("%s: default imagePullPolicy = %q, want %q", dct, c.ImagePullPolicy, corev1.PullIfNotPresent) |
| } |
| } |
| |
| always := dcr.DeepCopy() |
| always.Spec.FeSpec.ImagePullPolicy = corev1.PullAlways |
| c := NewBaseMainContainer(always, cm, v1.Component_FE) |
| if c.ImagePullPolicy != corev1.PullAlways { |
| t.Errorf("imagePullPolicy = %q, want %q", c.ImagePullPolicy, corev1.PullAlways) |
| } |
| } |
| |
| func Test_LifeCycleWithPreStopScript(t *testing.T) { |
| lcs := []*corev1.Lifecycle{nil, {}} |
| for i, _ := range lcs { |
| lc := LifeCycleWithPreStopScript(lcs[i], "/opt/apache-doris/prestop.sh") |
| if lc.PreStop == nil { |
| t.Errorf("build lifeCycleWithPreStopScript failed. %d", i) |
| } |
| } |
| } |
| |
| func Test_AddTerminationGracePeriodSeconds_Default(t *testing.T) { |
| pts := &corev1.PodTemplateSpec{} |
| AddTerminationGracePeriodSeconds(pts, map[string]interface{}{}, DEFAULT_BE_TERMINATION_GRACE_PERIOD_SECONDS) |
| if pts.Spec.TerminationGracePeriodSeconds == nil { |
| t.Fatalf("expected terminationGracePeriodSeconds") |
| } |
| if *pts.Spec.TerminationGracePeriodSeconds != DEFAULT_BE_TERMINATION_GRACE_PERIOD_SECONDS { |
| t.Errorf("expected default terminationGracePeriodSeconds=%d, got %d", DEFAULT_BE_TERMINATION_GRACE_PERIOD_SECONDS, *pts.Spec.TerminationGracePeriodSeconds) |
| } |
| } |
| |
| func Test_AddTerminationGracePeriodSeconds_ConfigOverride(t *testing.T) { |
| pts := &corev1.PodTemplateSpec{} |
| AddTerminationGracePeriodSeconds(pts, map[string]interface{}{GRACE_SHUTDOWN_WAIT_SECONDS: "60"}, DEFAULT_BE_TERMINATION_GRACE_PERIOD_SECONDS) |
| if pts.Spec.TerminationGracePeriodSeconds == nil { |
| t.Fatalf("expected terminationGracePeriodSeconds") |
| } |
| if *pts.Spec.TerminationGracePeriodSeconds != 60 { |
| t.Errorf("expected configured terminationGracePeriodSeconds=60, got %d", *pts.Spec.TerminationGracePeriodSeconds) |
| } |
| } |
| |
| func Test_BuildDisaggregatedProbe(t *testing.T) { |
| c := &corev1.Container{} |
| cs := &dv1.CommonSpec{ |
| StartTimeout: 600, |
| LiveTimeout: 30, |
| } |
| BuildDisaggregatedProbe(c, cs, dv1.DisaggregatedBE) |
| if c.StartupProbe == nil { |
| t.Errorf("startupProbe not build") |
| } |
| fts := 600 / 5 |
| if c.StartupProbe.FailureThreshold != int32(fts) { |
| t.Errorf("startupProbe failureThreshold build failed.") |
| } |
| if c.LivenessProbe.TimeoutSeconds != int32(30) { |
| t.Errorf("livenessProbe TimeoutSeconds build failed.") |
| } |
| } |
| |
| func Test_ReadinessProbePolicy_NilPolicy(t *testing.T) { |
| // nil policy should produce original defaults |
| probe := readinessProbe(8040, HEALTH_API_PATH, nil, HttpGet, nil) |
| if probe.PeriodSeconds != 5 { |
| t.Errorf("expected PeriodSeconds=5, got %d", probe.PeriodSeconds) |
| } |
| if probe.FailureThreshold != 3 { |
| t.Errorf("expected FailureThreshold=3, got %d", probe.FailureThreshold) |
| } |
| if probe.TimeoutSeconds != 0 { |
| t.Errorf("expected TimeoutSeconds=0 (k8s default), got %d", probe.TimeoutSeconds) |
| } |
| } |
| |
| func Test_ReadinessProbePolicy_WithPolicy(t *testing.T) { |
| policy := &v1.ReadinessProbePolicy{ |
| TimeoutSeconds: 5, |
| FailureThreshold: 5, |
| PeriodSeconds: 10, |
| } |
| probe := readinessProbe(8040, HEALTH_API_PATH, nil, HttpGet, policy) |
| if probe.PeriodSeconds != 10 { |
| t.Errorf("expected PeriodSeconds=10, got %d", probe.PeriodSeconds) |
| } |
| if probe.FailureThreshold != 5 { |
| t.Errorf("expected FailureThreshold=5, got %d", probe.FailureThreshold) |
| } |
| if probe.TimeoutSeconds != 5 { |
| t.Errorf("expected TimeoutSeconds=5, got %d", probe.TimeoutSeconds) |
| } |
| } |
| |
| func Test_ReadinessProbePolicy_PartialOverride(t *testing.T) { |
| // only set TimeoutSeconds, others should keep defaults |
| policy := &v1.ReadinessProbePolicy{ |
| TimeoutSeconds: 5, |
| } |
| probe := readinessProbe(8040, HEALTH_API_PATH, nil, HttpGet, policy) |
| if probe.PeriodSeconds != 5 { |
| t.Errorf("expected default PeriodSeconds=5, got %d", probe.PeriodSeconds) |
| } |
| if probe.FailureThreshold != 3 { |
| t.Errorf("expected default FailureThreshold=3, got %d", probe.FailureThreshold) |
| } |
| if probe.TimeoutSeconds != 5 { |
| t.Errorf("expected TimeoutSeconds=5, got %d", probe.TimeoutSeconds) |
| } |
| } |
| |
| func Test_BuildDisaggregatedProbe_NilCommonSpec(t *testing.T) { |
| c := &corev1.Container{} |
| BuildDisaggregatedProbe(c, nil, dv1.DisaggregatedBE) |
| if c.ReadinessProbe != nil { |
| t.Errorf("expected nil ReadinessProbe when cs is nil") |
| } |
| } |
| |
| func Test_BuildDisaggregatedProbe_ReadinessProbePolicy_PartialOverride(t *testing.T) { |
| c := &corev1.Container{} |
| cs := &dv1.CommonSpec{ |
| StartTimeout: 600, |
| LiveTimeout: 30, |
| ReadinessProbePolicy: &dv1.ReadinessProbePolicy{ |
| TimeoutSeconds: 8, |
| }, |
| } |
| BuildDisaggregatedProbe(c, cs, dv1.DisaggregatedBE) |
| if c.ReadinessProbe.PeriodSeconds != 5 { |
| t.Errorf("expected default PeriodSeconds=5, got %d", c.ReadinessProbe.PeriodSeconds) |
| } |
| if c.ReadinessProbe.FailureThreshold != 3 { |
| t.Errorf("expected default FailureThreshold=3, got %d", c.ReadinessProbe.FailureThreshold) |
| } |
| if c.ReadinessProbe.TimeoutSeconds != 8 { |
| t.Errorf("expected TimeoutSeconds=8, got %d", c.ReadinessProbe.TimeoutSeconds) |
| } |
| } |
| |
| func Test_BuildDisaggregatedProbe_ReadinessProbePolicy_Nil(t *testing.T) { |
| c := &corev1.Container{} |
| cs := &dv1.CommonSpec{ |
| StartTimeout: 600, |
| LiveTimeout: 30, |
| } |
| BuildDisaggregatedProbe(c, cs, dv1.DisaggregatedBE) |
| if c.ReadinessProbe.PeriodSeconds != 5 { |
| t.Errorf("expected default PeriodSeconds=5, got %d", c.ReadinessProbe.PeriodSeconds) |
| } |
| if c.ReadinessProbe.FailureThreshold != 3 { |
| t.Errorf("expected default FailureThreshold=3, got %d", c.ReadinessProbe.FailureThreshold) |
| } |
| } |
| |
| func Test_BuildDisaggregatedProbe_ReadinessProbePolicy_WithPolicy(t *testing.T) { |
| c := &corev1.Container{} |
| cs := &dv1.CommonSpec{ |
| StartTimeout: 600, |
| LiveTimeout: 30, |
| ReadinessProbePolicy: &dv1.ReadinessProbePolicy{ |
| TimeoutSeconds: 10, |
| FailureThreshold: 6, |
| PeriodSeconds: 15, |
| }, |
| } |
| BuildDisaggregatedProbe(c, cs, dv1.DisaggregatedBE) |
| if c.ReadinessProbe.PeriodSeconds != 15 { |
| t.Errorf("expected PeriodSeconds=15, got %d", c.ReadinessProbe.PeriodSeconds) |
| } |
| if c.ReadinessProbe.FailureThreshold != 6 { |
| t.Errorf("expected FailureThreshold=6, got %d", c.ReadinessProbe.FailureThreshold) |
| } |
| if c.ReadinessProbe.TimeoutSeconds != 10 { |
| t.Errorf("expected TimeoutSeconds=10, got %d", c.ReadinessProbe.TimeoutSeconds) |
| } |
| } |