Add ability to archive snapshots on primary storage

This commit is contained in:
Syed Ahmed 2018-04-19 11:22:01 -04:00 committed by Syed
parent 0afba54cd5
commit cd70ede3c2
7 changed files with 148 additions and 1 deletions

View File

@ -100,6 +100,13 @@ public interface SnapshotApiService {
*/
Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner);
/**
* Archives a snapshot from primary storage to secondary storage.
* @param id Snapshot ID
* @return Archived Snapshot object
*/
Snapshot archiveSnapshot(Long id);
/**
* @param vol
* @return

View File

@ -0,0 +1,92 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.snapshot;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.storage.Snapshot;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;
@APICommand(name = "archiveSnapshot", description = "Archives (moves) a snapshot on primary storage to secondary storage",
responseObject = SnapshotResponse.class, entityType = {Snapshot.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class ArchiveSnapshotCmd extends BaseAsyncCmd {
public static final Logger s_logger = Logger.getLogger(CreateSnapshotCmd.class.getName());
private static final String s_name = "createsnapshotresponse";
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType = SnapshotResponse.class,
required=true, description="The ID of the snapshot")
private Long id;
@Override
public String getEventType() {
return EventTypes.EVENT_SNAPSHOT_CREATE;
}
@Override
public String getEventDescription() {
return "Archiving snapshot " + id + " to secondary storage";
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class,getId()));
Snapshot snapshot = _snapshotService.archiveSnapshot(getId());
if (snapshot != null) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to archive snapshot");
}
}
@Override
public String getCommandName() {
return s_name;
}
@Override
public long getEntityOwnerId() {
Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId());
if (snapshot != null) {
return snapshot.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
public Long getId() {
return id;
}
}

View File

@ -262,6 +262,7 @@ public class SnapshotServiceImpl implements SnapshotService {
SnapshotObject snapObj = (SnapshotObject)snapshot;
AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
SnapshotResult result = new SnapshotResult(snapshot, null);
Snapshot.State origState = snapObj.getState();
try {
snapObj.processEvent(Snapshot.Event.BackupToSecondary);
@ -281,7 +282,13 @@ public class SnapshotServiceImpl implements SnapshotService {
s_logger.debug("Failed to copy snapshot", e);
result.setResult("Failed to copy snapshot:" + e.toString());
try {
snapObj.processEvent(Snapshot.Event.OperationFailed);
// When error archiving an already existing snapshot, emit OperationNotPerformed.
// This will ensure that the original snapshot does not get deleted
if (origState.equals(Snapshot.State.BackedUp)) {
snapObj.processEvent(Snapshot.Event.OperationNotPerformed);
} else {
snapObj.processEvent(Snapshot.Event.OperationFailed);
}
} catch (NoTransitionException e1) {
s_logger.debug("Failed to change state: " + e1.toString());
}

View File

@ -42,6 +42,7 @@ public class SnapshotStateMachineManagerImpl implements SnapshotStateMachineMana
stateMachine.addTransition(Snapshot.State.CreatedOnPrimary, Event.OperationNotPerformed, Snapshot.State.BackedUp);
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationSucceeded, Snapshot.State.BackedUp);
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationFailed, Snapshot.State.Error);
stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationNotPerformed, State.BackedUp);
stateMachine.addTransition(Snapshot.State.BackedUp, Event.DestroyRequested, Snapshot.State.Destroying);
stateMachine.addTransition(Snapshot.State.BackedUp, Event.CopyingRequested, Snapshot.State.Copying);
stateMachine.addTransition(Snapshot.State.BackedUp, Event.BackupToSecondary, Snapshot.State.BackingUp);

View File

@ -418,6 +418,7 @@ import org.apache.cloudstack.api.command.user.securitygroup.DeleteSecurityGroupC
import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupEgressCmd;
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
import org.apache.cloudstack.api.command.user.snapshot.ArchiveSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
@ -2820,6 +2821,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(CreateSnapshotCmd.class);
cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
cmdList.add(DeleteSnapshotCmd.class);
cmdList.add(ArchiveSnapshotCmd.class);
cmdList.add(CreateSnapshotPolicyCmd.class);
cmdList.add(UpdateSnapshotPolicyCmd.class);
cmdList.add(DeleteSnapshotPoliciesCmd.class);

View File

@ -381,6 +381,30 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
return snapshot;
}
@Override
public Snapshot archiveSnapshot(Long snapshotId) {
SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
if (snapshotOnPrimary == null || !snapshotOnPrimary.getStatus().equals(ObjectInDataStoreStateMachine.State.Ready)) {
throw new CloudRuntimeException("Can only archive snapshots present on primary storage. " +
"Cannot find snapshot " + snapshotId + " on primary storage");
}
SnapshotInfo snapshotOnSecondary = snapshotSrv.backupSnapshot(snapshotOnPrimary);
SnapshotVO snapshotVO = _snapshotDao.findById(snapshotOnSecondary.getId());
snapshotVO.setLocationType(Snapshot.LocationType.SECONDARY);
_snapshotDao.persist(snapshotVO);
try {
snapshotSrv.deleteSnapshot(snapshotOnPrimary);
} catch (Exception e) {
throw new CloudRuntimeException("Snapshot archived to Secondary Storage but there was an error deleting " +
" the snapshot on Primary Storage. Please manually delete the primary snapshot " + snapshotId, e);
}
return snapshotOnSecondary;
}
@Override
public Snapshot backupSnapshot(Long snapshotId) {
SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image);

View File

@ -23,6 +23,7 @@ import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
@ -336,4 +337,17 @@ public class SnapshotManagerTest {
Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
Assert.assertNull(snapshot);
}
@Test(expected = CloudRuntimeException.class)
public void testArchiveSnapshotSnapshotNotOnPrimary() {
when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(null);
_snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
}
@Test(expected = CloudRuntimeException.class)
public void testArchiveSnapshotSnapshotNotReady() {
when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(snapshotInfoMock);
when(snapshotInfoMock.getStatus()).thenReturn(ObjectInDataStoreStateMachine.State.Destroyed);
_snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
}
}