@@ -20,18 +20,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2020package operations
2121
2222import (
23+ "context"
2324 "fmt"
25+ "strings"
26+ "testing"
2427 "time"
2528
2629 . "github.com/onsi/ginkgo/v2"
2730 . "github.com/onsi/gomega"
2831
2932 corev1 "k8s.io/api/core/v1"
3033 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+ "k8s.io/apimachinery/pkg/runtime"
3135 "k8s.io/apimachinery/pkg/types"
3236 "k8s.io/apimachinery/pkg/util/sets"
3337 "k8s.io/utils/pointer"
3438 "sigs.k8s.io/controller-runtime/pkg/client"
39+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
3540
3641 appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
3742 dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
@@ -998,3 +1003,101 @@ func mockConsensusCompToRunning(opsRes *OpsResource) {
9981003 compStatus .Phase = appsv1 .RunningComponentPhase
9991004 opsRes .Cluster .Status .Components [defaultCompName ] = compStatus
10001005}
1006+
1007+ func TestHorizontalScalingCreateRestoreReturnsFatalWhenNoRestoreBuilt (t * testing.T ) {
1008+ testCases := []struct {
1009+ name string
1010+ backupMethod * dpv1alpha1.BackupMethod
1011+ }{{
1012+ // logical backups (e.g. TiDB BR) have no targetVolumes at all
1013+ name : "backup method without target volumes" ,
1014+ backupMethod : & dpv1alpha1.BackupMethod {Name : "br" },
1015+ }, {
1016+ // targetVolumes exist but none match the component's volume claim templates
1017+ name : "backup method target volumes match no component volume" ,
1018+ backupMethod : & dpv1alpha1.BackupMethod {
1019+ Name : "br" ,
1020+ TargetVolumes : & dpv1alpha1.TargetVolumeInfo {
1021+ Volumes : []string {"other-data" },
1022+ },
1023+ },
1024+ }}
1025+ for _ , tc := range testCases {
1026+ t .Run (tc .name , func (t * testing.T ) {
1027+ ctx := context .Background ()
1028+ scheme := runtime .NewScheme ()
1029+ for _ , addToScheme := range []func (* runtime.Scheme ) error {
1030+ corev1 .AddToScheme ,
1031+ appsv1 .AddToScheme ,
1032+ dpv1alpha1 .AddToScheme ,
1033+ opsv1alpha1 .AddToScheme ,
1034+ } {
1035+ if err := addToScheme (scheme ); err != nil {
1036+ t .Fatalf ("add scheme: %v" , err )
1037+ }
1038+ }
1039+
1040+ cluster := & appsv1.Cluster {
1041+ ObjectMeta : metav1.ObjectMeta {
1042+ Name : "tidb" ,
1043+ Namespace : "default" ,
1044+ UID : types .UID ("cluster1" ),
1045+ },
1046+ }
1047+ opsRequest := & opsv1alpha1.OpsRequest {
1048+ ObjectMeta : metav1.ObjectMeta {
1049+ Name : "scale-out-from-backup" ,
1050+ Namespace : "default" ,
1051+ UID : types .UID ("opsreq1" ),
1052+ },
1053+ }
1054+ backup := & dpv1alpha1.Backup {
1055+ ObjectMeta : metav1.ObjectMeta {
1056+ Name : "br-full" ,
1057+ Namespace : "default" ,
1058+ },
1059+ Status : dpv1alpha1.BackupStatus {
1060+ Phase : dpv1alpha1 .BackupPhaseCompleted ,
1061+ BackupMethod : tc .backupMethod ,
1062+ },
1063+ }
1064+ cli := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (cluster , opsRequest , backup ).Build ()
1065+ opsRes := & OpsResource {
1066+ Cluster : cluster ,
1067+ OpsRequest : opsRequest ,
1068+ }
1069+ synthesizedComponent := & component.SynthesizedComponent {
1070+ Name : "tikv" ,
1071+ VolumeClaimTemplates : []corev1.PersistentVolumeClaimTemplate {{
1072+ ObjectMeta : metav1.ObjectMeta {Name : "data" },
1073+ Spec : corev1.PersistentVolumeClaimSpec {
1074+ AccessModes : []corev1.PersistentVolumeAccessMode {corev1 .ReadWriteOnce },
1075+ },
1076+ }},
1077+ }
1078+ restoreMGR := plan .NewRestoreManager (ctx , cli , cluster , scheme , map [string ]string {
1079+ constant .OpsRequestNameLabelKey : opsRequest .Name ,
1080+ }, 1 , 3 )
1081+
1082+ err := horizontalScalingOpsHandler {}.createRestore (intctrlutil.RequestCtx {Ctx : ctx }, cli , opsRes ,
1083+ synthesizedComponent , restoreMGR , & appsv1.ClusterComponentSpec {Name : "tikv" }, backup , "" )
1084+ if err == nil {
1085+ t .Fatal ("expected fatal error when backup method cannot build prepareData restore" )
1086+ }
1087+ if ! intctrlutil .IsTargetError (err , intctrlutil .ErrorTypeFatal ) {
1088+ t .Fatalf ("expected fatal error, got %T: %v" , err , err )
1089+ }
1090+ if ! strings .Contains (err .Error (), "has no target volumes matching component" ) {
1091+ t .Fatalf ("unexpected error: %v" , err )
1092+ }
1093+
1094+ restoreList := & dpv1alpha1.RestoreList {}
1095+ if err := cli .List (ctx , restoreList , client .InNamespace ("default" )); err != nil {
1096+ t .Fatalf ("list restores: %v" , err )
1097+ }
1098+ if len (restoreList .Items ) != 0 {
1099+ t .Fatalf ("expected no restore to be created, got %d" , len (restoreList .Items ))
1100+ }
1101+ })
1102+ }
1103+ }
0 commit comments