2424import com .cloud .exception .InvalidParameterValueException ;
2525import com .cloud .exception .PermissionDeniedException ;
2626import com .cloud .exception .ResourceUnavailableException ;
27+ import com .cloud .hypervisor .Hypervisor .HypervisorType ;
2728import com .cloud .org .Grouping ;
2829import com .cloud .storage .DataStoreRole ;
30+ import com .cloud .storage .ScopeType ;
2931import com .cloud .storage .Snapshot ;
3032import com .cloud .storage .SnapshotPolicyVO ;
3133import com .cloud .storage .SnapshotVO ;
4244import com .cloud .user .User ;
4345import com .cloud .user .dao .AccountDao ;
4446import com .cloud .utils .Pair ;
45-
4647import com .cloud .utils .db .SearchBuilder ;
4748import com .cloud .utils .db .SearchCriteria ;
49+ import com .cloud .vm .dao .UserVmDao ;
50+
4851import org .apache .cloudstack .api .command .user .snapshot .DeleteSnapshotPoliciesCmd ;
4952import org .apache .cloudstack .api .command .user .snapshot .ListSnapshotPoliciesCmd ;
5053import org .apache .cloudstack .context .CallContext ;
5558import org .apache .cloudstack .engine .subsystem .api .storage .SnapshotInfo ;
5659import org .apache .cloudstack .engine .subsystem .api .storage .SnapshotResult ;
5760import org .apache .cloudstack .engine .subsystem .api .storage .SnapshotService ;
61+ import org .apache .cloudstack .engine .subsystem .api .storage .VolumeDataFactory ;
62+ import org .apache .cloudstack .engine .subsystem .api .storage .VolumeInfo ;
5863import org .apache .cloudstack .framework .async .AsyncCallFuture ;
64+ import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
5965import org .apache .cloudstack .storage .datastore .db .SnapshotDataStoreDao ;
6066import org .apache .cloudstack .storage .datastore .db .SnapshotDataStoreVO ;
67+ import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
6168
6269import org .junit .Assert ;
6370import org .junit .After ;
@@ -106,6 +113,13 @@ public class SnapshotManagerImplTest {
106113 SnapshotScheduler snapshotScheduler ;
107114 @ Mock
108115 TaggedResourceService taggedResourceService ;
116+ @ Mock
117+ PrimaryDataStoreDao primaryDataStoreDao ;
118+ @ Mock
119+ VolumeDataFactory volFactory ;
120+ @ Mock
121+ UserVmDao userVmDao ;
122+
109123 @ InjectMocks
110124 SnapshotManagerImpl snapshotManager = new SnapshotManagerImpl ();
111125
@@ -123,6 +137,69 @@ public void tearDown() {
123137 CallContext .unregister ();
124138 }
125139
140+ @ Test
141+ public void testAllocSnapshotRejectsDuplicateNameForVolume () {
142+ long volumeId = 1L ;
143+ CallContext .register (Mockito .mock (User .class ), Mockito .mock (Account .class ));
144+ try {
145+ VolumeInfo volume = Mockito .mock (VolumeInfo .class );
146+ Mockito .when (volFactory .getVolume (volumeId )).thenReturn (volume );
147+ DataStore dataStore = Mockito .mock (DataStore .class );
148+ Mockito .when (volume .getDataStore ()).thenReturn (dataStore );
149+ Mockito .when (dataStore .getId ()).thenReturn (10L );
150+ StoragePoolVO pool = Mockito .mock (StoragePoolVO .class );
151+ Mockito .when (primaryDataStoreDao .findById (10L )).thenReturn (pool );
152+ Mockito .when (pool .getScope ()).thenReturn (ScopeType .ZONE );
153+ Mockito .when (pool .getHypervisor ()).thenReturn (HypervisorType .None );
154+ Mockito .when (volume .getInstanceId ()).thenReturn (null );
155+ Mockito .when (volume .getAccountId ()).thenReturn (2L );
156+
157+ Mockito .when (snapshotDao .findByVolumeIdAndNameNotInStatus (volumeId , "dup" , Snapshot .State .Destroyed , Snapshot .State .Error ))
158+ .thenReturn (Mockito .mock (SnapshotVO .class ));
159+
160+ Assert .assertThrows (InvalidParameterValueException .class , () ->
161+ snapshotManager .allocSnapshot (volumeId , Snapshot .MANUAL_POLICY_ID , "dup" , null ));
162+ } finally {
163+ CallContext .unregister ();
164+ }
165+ }
166+
167+ @ Test
168+ public void testAllocSnapshotDoesNotCheckDuplicateForAutoGeneratedName () {
169+ long volumeId = 1L ;
170+ CallContext .register (Mockito .mock (User .class ), Mockito .mock (Account .class ));
171+ try {
172+ VolumeInfo volume = Mockito .mock (VolumeInfo .class );
173+ Mockito .when (volFactory .getVolume (volumeId )).thenReturn (volume );
174+ DataStore dataStore = Mockito .mock (DataStore .class );
175+ Mockito .when (volume .getDataStore ()).thenReturn (dataStore );
176+ Mockito .lenient ().when (dataStore .getId ()).thenReturn (10L );
177+ StoragePoolVO pool = Mockito .mock (StoragePoolVO .class );
178+ Mockito .lenient ().when (primaryDataStoreDao .findById (10L )).thenReturn (pool );
179+ Mockito .lenient ().when (pool .getScope ()).thenReturn (ScopeType .ZONE );
180+ Mockito .lenient ().when (pool .getHypervisor ()).thenReturn (HypervisorType .None );
181+ Mockito .when (volume .getInstanceId ()).thenReturn (null );
182+ Mockito .when (volume .getAccountId ()).thenReturn (2L );
183+
184+ // A recurring snapshot carries no caller-supplied name, so the auto-generated name must not be checked.
185+ Mockito .lenient ().when (snapshotDao .findByVolumeIdAndNameNotInStatus (Mockito .eq (volumeId ), Mockito .anyString (),
186+ Mockito .eq (Snapshot .State .Destroyed ), Mockito .eq (Snapshot .State .Error )))
187+ .thenReturn (Mockito .mock (SnapshotVO .class ));
188+
189+ try {
190+ snapshotManager .allocSnapshot (volumeId , Snapshot .MANUAL_POLICY_ID , null , null );
191+ } catch (Exception e ) {
192+ // The rest of allocSnapshot is not stubbed here; this test only asserts the duplicate-name guard is
193+ // not applied to an auto-generated name.
194+ }
195+
196+ Mockito .verify (snapshotDao , Mockito .never ()).findByVolumeIdAndNameNotInStatus (Mockito .eq (volumeId ),
197+ Mockito .anyString (), Mockito .eq (Snapshot .State .Destroyed ), Mockito .eq (Snapshot .State .Error ));
198+ } finally {
199+ CallContext .unregister ();
200+ }
201+ }
202+
126203 @ Test
127204 public void testGetSnapshotZoneImageStoreValid () {
128205 final long snapshotId = 1L ;
0 commit comments