Skip to content

Commit c0df0d6

Browse files
committed
snapshot: reject a duplicate snapshot name for a volume
Two snapshots of the same volume with the same name map to the same file on the snapshot store, so creating the second overwrites the first, and later deleting either one removes the shared file and leaves the other snapshot pointing at nothing. Reject creating a snapshot when an active (non-destroyed) snapshot with the same name already exists for the volume. Auto-generated names already carry a timestamp so they are unaffected. Fixes: #13051
1 parent d87c804 commit c0df0d6

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

‎server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,12 @@ public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName,
20422042
if (snapshotName == null)
20432043
snapshotName = vmDisplayName + "_" + volume.getName() + "_" + timeString;
20442044

2045+
for (SnapshotVO existingSnapshot : _snapshotDao.listByStatusNotIn(volumeId, Snapshot.State.Destroyed, Snapshot.State.Error)) {
2046+
if (snapshotName.equals(existingSnapshot.getName())) {
2047+
throw new InvalidParameterValueException(String.format("A snapshot with name [%s] already exists for volume %s.", snapshotName, volume));
2048+
}
2049+
}
2050+
20452051
HypervisorType hypervisorType = HypervisorType.None;
20462052
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getDataStore().getId());
20472053
if (storagePool.getScope() == ScopeType.ZONE) {

‎server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import com.cloud.exception.PermissionDeniedException;
2525
import com.cloud.exception.ResourceUnavailableException;
2626
import com.cloud.org.Grouping;
27+
import com.cloud.hypervisor.Hypervisor.HypervisorType;
2728
import com.cloud.storage.DataStoreRole;
29+
import com.cloud.storage.ScopeType;
2830
import com.cloud.storage.Snapshot;
2931
import com.cloud.storage.Storage.StoragePoolType;
3032
import com.cloud.storage.SnapshotPolicyVO;
@@ -41,6 +43,7 @@
4143
import com.cloud.user.ResourceLimitService;
4244
import com.cloud.user.User;
4345
import com.cloud.user.dao.AccountDao;
46+
import com.cloud.vm.dao.UserVmDao;
4447
import com.cloud.utils.Pair;
4548

4649
import com.cloud.utils.db.SearchBuilder;
@@ -56,6 +59,8 @@
5659
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult;
5760
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
5861
import org.apache.cloudstack.framework.async.AsyncCallFuture;
62+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
63+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
5964
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
6065
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
6166
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@@ -110,6 +115,10 @@ public class SnapshotManagerImplTest {
110115
TaggedResourceService taggedResourceService;
111116
@Mock
112117
PrimaryDataStoreDao primaryDataStoreDao;
118+
@Mock
119+
VolumeDataFactory volFactory;
120+
@Mock
121+
UserVmDao userVmDao;
113122
@InjectMocks
114123
SnapshotManagerImpl snapshotManager = new SnapshotManagerImpl();
115124

@@ -669,4 +678,29 @@ public void testRemoveClvmPrimarySnapshotStoreRefIfNeeded_NonClvmPool() {
669678
Mockito.verify(snapshotStoreDao, Mockito.never()).removeBySnapshotStore(
670679
Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
671680
}
681+
682+
@Test(expected = InvalidParameterValueException.class)
683+
public void testAllocSnapshotRejectsDuplicateNameForVolume() throws Exception {
684+
long volumeId = 1L;
685+
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
686+
687+
VolumeInfo volume = Mockito.mock(VolumeInfo.class);
688+
Mockito.when(volFactory.getVolume(volumeId)).thenReturn(volume);
689+
DataStore dataStore = Mockito.mock(DataStore.class);
690+
Mockito.when(volume.getDataStore()).thenReturn(dataStore);
691+
Mockito.when(dataStore.getId()).thenReturn(10L);
692+
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
693+
Mockito.when(primaryDataStoreDao.findById(10L)).thenReturn(pool);
694+
Mockito.when(pool.getScope()).thenReturn(ScopeType.ZONE);
695+
Mockito.when(pool.getHypervisor()).thenReturn(HypervisorType.None);
696+
Mockito.when(volume.getInstanceId()).thenReturn(null);
697+
Mockito.when(volume.getAccountId()).thenReturn(2L);
698+
699+
SnapshotVO existing = Mockito.mock(SnapshotVO.class);
700+
Mockito.when(existing.getName()).thenReturn("dup");
701+
Mockito.when(snapshotDao.listByStatusNotIn(volumeId, Snapshot.State.Destroyed, Snapshot.State.Error))
702+
.thenReturn(List.of(existing));
703+
704+
snapshotManager.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, "dup", null);
705+
}
672706
}

0 commit comments

Comments
 (0)