@@ -13,7 +13,9 @@ import (
1313 "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/cloud"
1414 "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/disk/batcher"
1515 "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/disk/desc"
16+ "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/disk/waitstatus"
1617 "github.com/stretchr/testify/assert"
18+ "github.com/stretchr/testify/require"
1719 "k8s.io/apimachinery/pkg/util/sets"
1820 "k8s.io/klog/v2/ktesting"
1921 "k8s.io/utils/clock"
@@ -598,3 +600,149 @@ func Test_getDiskDescribeRequest(t *testing.T) {
598600 })
599601 }
600602}
603+
604+ func testAttachDetach (t * testing.T ) (context.Context , * cloud.MockECSInterface , * DiskAttachDetach ) {
605+ ctrl := gomock .NewController (t )
606+ ecs := cloud .NewMockECSInterface (ctrl )
607+ _ , ctx := ktesting .NewTestContext (t )
608+
609+ client := desc .Disk (ecs )
610+ b := batcher .NewPassthrough (client )
611+ return ctx , ecs , & DiskAttachDetach {
612+ slots : NewSlots (0 , 0 ),
613+ ecs : ecs ,
614+ waiter : waitstatus .NewSimple (client , clock.RealClock {}),
615+ batcher : b ,
616+ attachThrottler : defaultThrottler (),
617+ detachThrottler : defaultThrottler (),
618+ dev : DefaultDeviceManager ,
619+ }
620+ }
621+
622+ func disk (status string , node string ) ecs.Disk {
623+ disk := ecs.Disk {
624+ Status : status ,
625+ Category : "cloud_regional_disk_auto" , // only one support forceAttach
626+ MultiAttach : "Disabled" ,
627+ DiskId : "d-testdiskid" ,
628+ InstanceId : node ,
629+ SerialNumber : "fake-serial-number" ,
630+ }
631+ if node != "" {
632+ disk .Attachments .Attachment = []ecs.Attachment {
633+ {InstanceId : node },
634+ }
635+ }
636+ return disk
637+ }
638+
639+ func diskResp (disk ecs.Disk ) * ecs.DescribeDisksResponse {
640+ return & ecs.DescribeDisksResponse {
641+ Disks : ecs.DisksInDescribeDisks {
642+ Disk : []ecs.Disk {disk },
643+ },
644+ }
645+ }
646+
647+ func TestAttachDisk (t * testing.T ) {
648+ GlobalConfigVar .DetachBeforeAttach = true // This is the default
649+ cases := []struct {
650+ name string
651+ before , after ecs.Disk
652+ detaching bool
653+ detach bool
654+ detached ecs.Disk
655+ noAttach bool
656+ forceAttach bool
657+ expectErr bool
658+ }{
659+ {
660+ name : "already attached" ,
661+ before : disk ("In_use" , "i-testinstanceid" ),
662+ noAttach : true ,
663+ },
664+ {
665+ name : "attached to other" ,
666+ before : disk ("In_use" , "i-anotherinstance" ),
667+ detaching : true ,
668+ forceAttach : true ,
669+ after : disk ("In_use" , "i-testinstanceid" ),
670+ },
671+ {
672+ name : "attached to other (no force attach)" ,
673+ before : disk ("In_use" , "i-anotherinstance" ),
674+ detach : true ,
675+ detached : disk ("Available" , "" ),
676+ after : disk ("In_use" , "i-testinstanceid" ),
677+ },
678+ {
679+ name : "normal" ,
680+ before : disk ("Available" , "" ),
681+ after : disk ("In_use" , "i-testinstanceid" ),
682+ },
683+ {
684+ name : "attaching" ,
685+ before : disk ("Attaching" , "" ),
686+ noAttach : true ,
687+ expectErr : true ,
688+ },
689+ {
690+ name : "detaching from self" ,
691+ before : disk ("Detaching" , "i-testinstanceid" ),
692+ after : disk ("In_use" , "i-testinstanceid" ), // FIXME
693+ },
694+ {
695+ // This not supported by ECS, but desired by us to speed up failover. Hopes they will support it someday.
696+ name : "detaching from other" ,
697+ before : disk ("Detaching" , "i-anotherinstance" ),
698+ detaching : true ,
699+ forceAttach : true ,
700+ after : disk ("In_use" , "i-testinstanceid" ),
701+ },
702+ {
703+ // This is likely to fail in real env. But we try it anyway, in case the detach just finished after we checked
704+ name : "detaching from other (no force attach)" ,
705+ before : disk ("Detaching" , "i-anotherinstance" ),
706+ forceAttach : false ,
707+ after : disk ("In_use" , "i-testinstanceid" ),
708+ },
709+ }
710+ for _ , tc := range cases {
711+ t .Run (tc .name , func (t * testing.T ) {
712+ ctx , c , ad := testAttachDetach (t )
713+
714+ if tc .detaching {
715+ ad .detaching .Store ("d-testdiskid" , "i-anotherinstance" )
716+ }
717+
718+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .before ), nil )
719+ if tc .detach {
720+ detachCall := c .EXPECT ().DetachDisk (gomock .Any ()).Return (& ecs.DetachDiskResponse {}, nil )
721+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .detached ), nil ).After (detachCall )
722+ }
723+ force := false
724+ if ! tc .noAttach {
725+ attachCall := c .EXPECT ().AttachDisk (gomock .Any ()).Return (& ecs.AttachDiskResponse {}, nil ).
726+ Do (func (request * ecs.AttachDiskRequest ) {
727+ if request .Force .HasValue () {
728+ var err error
729+ force , err = request .Force .GetValue ()
730+ if err != nil {
731+ t .Fatalf ("unexpected error: %v" , err )
732+ }
733+ }
734+ })
735+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .after ), nil ).After (attachCall )
736+ }
737+ serial , err := ad .attachDisk (ctx , "d-testdiskid" , "i-testinstanceid" , false )
738+
739+ assert .Equal (t , tc .forceAttach , force )
740+ if tc .expectErr {
741+ require .Error (t , err )
742+ } else {
743+ require .NoError (t , err )
744+ assert .Equal (t , "fake-serial-number" , serial )
745+ }
746+ })
747+ }
748+ }
0 commit comments