@@ -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,148 @@ 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+ name : "detaching from other" ,
696+ before : disk ("Detaching" , "i-anotherinstance" ),
697+ detaching : true ,
698+ forceAttach : true ,
699+ after : disk ("In_use" , "i-testinstanceid" ),
700+ },
701+ {
702+ // This is likely to fail in real env. But we try it anyway, in case the detach just finished after we checked
703+ name : "detaching from other (no force attach)" ,
704+ before : disk ("Detaching" , "i-anotherinstance" ),
705+ forceAttach : false ,
706+ after : disk ("In_use" , "i-testinstanceid" ),
707+ },
708+ }
709+ for _ , tc := range cases {
710+ t .Run (tc .name , func (t * testing.T ) {
711+ ctx , c , ad := testAttachDetach (t )
712+
713+ if tc .detaching {
714+ ad .detaching .Store ("d-testdiskid" , "i-anotherinstance" )
715+ }
716+
717+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .before ), nil )
718+ if tc .detach {
719+ detachCall := c .EXPECT ().DetachDisk (gomock .Any ()).Return (& ecs.DetachDiskResponse {}, nil )
720+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .detached ), nil ).After (detachCall )
721+ }
722+ force := false
723+ if ! tc .noAttach {
724+ attachCall := c .EXPECT ().AttachDisk (gomock .Any ()).Return (& ecs.AttachDiskResponse {}, nil ).
725+ Do (func (request * ecs.AttachDiskRequest ) {
726+ if request .Force .HasValue () {
727+ var err error
728+ force , err = request .Force .GetValue ()
729+ if err != nil {
730+ t .Fatalf ("unexpected error: %v" , err )
731+ }
732+ }
733+ })
734+ c .EXPECT ().DescribeDisks (gomock .Any ()).Return (diskResp (tc .after ), nil ).After (attachCall )
735+ }
736+ serial , err := ad .attachDisk (ctx , "d-testdiskid" , "i-testinstanceid" , false )
737+
738+ assert .Equal (t , tc .forceAttach , force )
739+ if tc .expectErr {
740+ require .Error (t , err )
741+ } else {
742+ require .NoError (t , err )
743+ assert .Equal (t , "fake-serial-number" , serial )
744+ }
745+ })
746+ }
747+ }
0 commit comments