Skip to content

Commit cd70ede

Browse files
Syed Ahmedsyed
authored andcommitted
Add ability to archive snapshots on primary storage
1 parent 0afba54 commit cd70ede

7 files changed

Lines changed: 148 additions & 1 deletion

File tree

api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ public interface SnapshotApiService {
100100
*/
101101
Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner);
102102

103+
/**
104+
* Archives a snapshot from primary storage to secondary storage.
105+
* @param id Snapshot ID
106+
* @return Archived Snapshot object
107+
*/
108+
Snapshot archiveSnapshot(Long id);
109+
103110
/**
104111
* @param vol
105112
* @return
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.user.snapshot;
18+
19+
import com.cloud.event.EventTypes;
20+
import com.cloud.exception.ConcurrentOperationException;
21+
import com.cloud.exception.InsufficientCapacityException;
22+
import com.cloud.exception.NetworkRuleConflictException;
23+
import com.cloud.exception.ResourceAllocationException;
24+
import com.cloud.exception.ResourceUnavailableException;
25+
import com.cloud.storage.Snapshot;
26+
import com.cloud.user.Account;
27+
import org.apache.cloudstack.acl.SecurityChecker;
28+
import org.apache.cloudstack.api.ACL;
29+
import org.apache.cloudstack.api.APICommand;
30+
import org.apache.cloudstack.api.ApiConstants;
31+
import org.apache.cloudstack.api.ApiErrorCode;
32+
import org.apache.cloudstack.api.BaseAsyncCmd;
33+
import org.apache.cloudstack.api.Parameter;
34+
import org.apache.cloudstack.api.ServerApiException;
35+
import org.apache.cloudstack.api.response.SnapshotResponse;
36+
import org.apache.cloudstack.api.response.SuccessResponse;
37+
import org.apache.cloudstack.context.CallContext;
38+
import org.apache.log4j.Logger;
39+
40+
@APICommand(name = "archiveSnapshot", description = "Archives (moves) a snapshot on primary storage to secondary storage",
41+
responseObject = SnapshotResponse.class, entityType = {Snapshot.class},
42+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
43+
public class ArchiveSnapshotCmd extends BaseAsyncCmd {
44+
public static final Logger s_logger = Logger.getLogger(CreateSnapshotCmd.class.getName());
45+
private static final String s_name = "createsnapshotresponse";
46+
47+
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
48+
@Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType = SnapshotResponse.class,
49+
required=true, description="The ID of the snapshot")
50+
private Long id;
51+
52+
@Override
53+
public String getEventType() {
54+
return EventTypes.EVENT_SNAPSHOT_CREATE;
55+
}
56+
57+
@Override
58+
public String getEventDescription() {
59+
return "Archiving snapshot " + id + " to secondary storage";
60+
}
61+
62+
@Override
63+
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
64+
CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class,getId()));
65+
Snapshot snapshot = _snapshotService.archiveSnapshot(getId());
66+
if (snapshot != null) {
67+
SuccessResponse response = new SuccessResponse(getCommandName());
68+
setResponseObject(response);
69+
} else {
70+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to archive snapshot");
71+
}
72+
}
73+
74+
@Override
75+
public String getCommandName() {
76+
return s_name;
77+
}
78+
79+
@Override
80+
public long getEntityOwnerId() {
81+
Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId());
82+
if (snapshot != null) {
83+
return snapshot.getAccountId();
84+
}
85+
86+
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
87+
}
88+
89+
public Long getId() {
90+
return id;
91+
}
92+
}

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
262262
SnapshotObject snapObj = (SnapshotObject)snapshot;
263263
AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
264264
SnapshotResult result = new SnapshotResult(snapshot, null);
265+
Snapshot.State origState = snapObj.getState();
265266
try {
266267
snapObj.processEvent(Snapshot.Event.BackupToSecondary);
267268

@@ -281,7 +282,13 @@ public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
281282
s_logger.debug("Failed to copy snapshot", e);
282283
result.setResult("Failed to copy snapshot:" + e.toString());
283284
try {
284-
snapObj.processEvent(Snapshot.Event.OperationFailed);
285+
// When error archiving an already existing snapshot, emit OperationNotPerformed.
286+
// This will ensure that the original snapshot does not get deleted
287+
if (origState.equals(Snapshot.State.BackedUp)) {
288+
snapObj.processEvent(Snapshot.Event.OperationNotPerformed);
289+
} else {
290+
snapObj.processEvent(Snapshot.Event.OperationFailed);
291+
}
285292
} catch (NoTransitionException e1) {
286293
s_logger.debug("Failed to change state: " + e1.toString());
287294
}

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public SnapshotStateMachineManagerImpl() {
4242
stateMachine.addTransition(Snapshot.State.CreatedOnPrimary, Event.OperationNotPerformed, Snapshot.State.BackedUp);
4343
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationSucceeded, Snapshot.State.BackedUp);
4444
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationFailed, Snapshot.State.Error);
45+
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationNotPerformed, State.BackedUp);
4546
stateMachine.addTransition(Snapshot.State.BackedUp, Event.DestroyRequested, Snapshot.State.Destroying);
4647
stateMachine.addTransition(Snapshot.State.BackedUp, Event.CopyingRequested, Snapshot.State.Copying);
4748
stateMachine.addTransition(Snapshot.State.BackedUp, Event.BackupToSecondary, Snapshot.State.BackingUp);

server/src/main/java/com/cloud/server/ManagementServerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@
418418
import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
419419
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupEgressCmd;
420420
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
421+
import org.apache.cloudstack.api.command.user.snapshot.ArchiveSnapshotCmd;
421422
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
422423
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd;
423424
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
@@ -2820,6 +2821,7 @@ public List<Class<?>> getCommands() {
28202821
cmdList.add(CreateSnapshotCmd.class);
28212822
cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
28222823
cmdList.add(DeleteSnapshotCmd.class);
2824+
cmdList.add(ArchiveSnapshotCmd.class);
28232825
cmdList.add(CreateSnapshotPolicyCmd.class);
28242826
cmdList.add(UpdateSnapshotPolicyCmd.class);
28252827
cmdList.add(DeleteSnapshotPoliciesCmd.class);

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,30 @@ public Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Ac
381381
return snapshot;
382382
}
383383

384+
@Override
385+
public Snapshot archiveSnapshot(Long snapshotId) {
386+
SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
387+
388+
if (snapshotOnPrimary == null || !snapshotOnPrimary.getStatus().equals(ObjectInDataStoreStateMachine.State.Ready)) {
389+
throw new CloudRuntimeException("Can only archive snapshots present on primary storage. " +
390+
"Cannot find snapshot " + snapshotId + " on primary storage");
391+
}
392+
393+
SnapshotInfo snapshotOnSecondary = snapshotSrv.backupSnapshot(snapshotOnPrimary);
394+
SnapshotVO snapshotVO = _snapshotDao.findById(snapshotOnSecondary.getId());
395+
snapshotVO.setLocationType(Snapshot.LocationType.SECONDARY);
396+
_snapshotDao.persist(snapshotVO);
397+
398+
try {
399+
snapshotSrv.deleteSnapshot(snapshotOnPrimary);
400+
} catch (Exception e) {
401+
throw new CloudRuntimeException("Snapshot archived to Secondary Storage but there was an error deleting " +
402+
" the snapshot on Primary Storage. Please manually delete the primary snapshot " + snapshotId, e);
403+
}
404+
405+
return snapshotOnSecondary;
406+
}
407+
384408
@Override
385409
public Snapshot backupSnapshot(Long snapshotId) {
386410
SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image);

server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
2424
import org.apache.cloudstack.context.CallContext;
2525
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
26+
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
2627
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
2728
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
2829
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
@@ -336,4 +337,17 @@ public void testBackupSnapshotFromVmSnapshotF3() {
336337
Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
337338
Assert.assertNull(snapshot);
338339
}
340+
341+
@Test(expected = CloudRuntimeException.class)
342+
public void testArchiveSnapshotSnapshotNotOnPrimary() {
343+
when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(null);
344+
_snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
345+
}
346+
347+
@Test(expected = CloudRuntimeException.class)
348+
public void testArchiveSnapshotSnapshotNotReady() {
349+
when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(snapshotInfoMock);
350+
when(snapshotInfoMock.getStatus()).thenReturn(ObjectInDataStoreStateMachine.State.Destroyed);
351+
_snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
352+
}
339353
}

0 commit comments

Comments
 (0)