Implementing snapshot-strategy support for reverting a volume to a snapshot

This commit is contained in:
Mike Tutkowski 2014-10-14 17:04:35 -06:00
parent 340bcc60c0
commit e70dde09c9
5 changed files with 190 additions and 8 deletions

View File

@ -38,6 +38,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
Ready("The volume is ready to be used."),
Migrating("The volume is migrating to other storage pool"),
Snapshotting("There is a snapshot created on this volume, not backed up to secondary storage yet"),
Reverting("Replace the existing volume on a storage system with a snapshot of it"),
Resizing("The volume is being resized"),
Expunging("The volume is being expunging"),
Expunged("The volume is being expunging"),
@ -85,8 +86,11 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
s_fsm.addTransition(Expunging, Event.OperationSucceeded, Expunged);
s_fsm.addTransition(Expunging, Event.OperationFailed, Destroy);
s_fsm.addTransition(Ready, Event.SnapshotRequested, Snapshotting);
s_fsm.addTransition(Ready, Event.RevertRequested, Reverting);
s_fsm.addTransition(Snapshotting, Event.OperationSucceeded, Ready);
s_fsm.addTransition(Snapshotting, Event.OperationFailed, Ready);
s_fsm.addTransition(Reverting, Event.OperationSucceeded, Ready);
s_fsm.addTransition(Reverting, Event.OperationFailed, Ready);
s_fsm.addTransition(Ready, Event.MigrationRequested, Migrating);
s_fsm.addTransition(Migrating, Event.OperationSucceeded, Ready);
s_fsm.addTransition(Migrating, Event.OperationFailed, Ready);
@ -111,6 +115,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
UploadRequested,
MigrationRequested,
SnapshotRequested,
RevertRequested,
DestroyRequested,
ExpungingRequested,
ResizeRequested;

View File

@ -33,23 +33,35 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Volume;
import com.cloud.storage.Volume.Type;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.VirtualMachine.State;
@Component
public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
private static final Logger s_logger = Logger.getLogger(StorageSystemSnapshotStrategy.class);
@Inject private ClusterDao _clusterDao;
@Inject private DataStoreManager _dataStoreMgr;
@Inject private EntityManager _entityMgr;
@Inject private HostDao _hostDao;
@Inject private SnapshotDao _snapshotDao;
@Inject private SnapshotDataFactory _snapshotDataFactory;
@Inject private SnapshotDataStoreDao _snapshotStoreDao;
@ -121,6 +133,75 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
@Override
public boolean revertSnapshot(Long snapshotId) {
// verify the following:
// if root disk, the VM cannot be running (don't allow this at all for ESX)
// if data disk, the disk cannot be in the attached state
SnapshotInfo snapshotInfo = _snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
if (volume.getVolumeType() == Type.DATADISK) {
if (volume.getAttached() != null) {
throw new CloudRuntimeException("A data disk must be in the detached state in order to perform a revert.");
}
}
else if (volume.getVolumeType() == Type.ROOT) {
Long instanceId = volume.getInstanceId();
UserVm vm = _entityMgr.findById(UserVm.class, instanceId);
Long hostId = vm.getHostId();
HostVO hostVO = _hostDao.findById(hostId);
Long clusterId = hostVO.getClusterId();
ClusterVO clusterVO = _clusterDao.findById(clusterId);
if (clusterVO.getHypervisorType() != HypervisorType.XenServer && clusterVO.getHypervisorType() != HypervisorType.KVM) {
throw new CloudRuntimeException("Unsupported hypervisor type for root disk revert. Create a template from this disk and use it instead.");
}
if (vm.getState() != State.Stopped) {
throw new CloudRuntimeException("A root disk cannot be reverted unless the VM it's attached to is in the stopped state.");
}
}
else {
throw new CloudRuntimeException("Unsupported volume type");
}
SnapshotVO snapshotVO = _snapshotDao.acquireInLockTable(snapshotId);
if (snapshotVO == null) {
throw new CloudRuntimeException("Failed to acquire lock on the following snapshot: " + snapshotId);
}
try {
volumeInfo.stateTransit(Volume.Event.RevertRequested);
boolean result = false;
try {
result = snapshotSvr.revertSnapshot(snapshotId);
}
finally {
if (result) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
}
else {
String msg = "Failed to revert the volume to a snapshot";
s_logger.debug(msg);
volumeInfo.stateTransit(Volume.Event.OperationFailed);
throw new CloudRuntimeException("Failed to revert the volume to a snapshot");
}
}
}
finally {
if (snapshotVO != null) {
_snapshotDao.releaseFromLockTable(snapshotId);
}
}
return true;
}
@ -189,9 +270,9 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
long volumeId = snapshot.getVolumeId();
VolumeVO volume = _volumeDao.findById(volumeId);
VolumeVO volumeVO = _volumeDao.findById(volumeId);
long storagePoolId = volume.getPoolId();
long storagePoolId = volumeVO.getPoolId();
DataStore dataStore = _dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();

View File

@ -609,8 +609,31 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
@Override
public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CommandResult> callback) {
throw new UnsupportedOperationException();
public void revertSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback<CommandResult> callback) {
String errMsg = null;
try {
VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
long storagePoolId = volumeInfo.getPoolId();
long sfVolumeId = Long.parseLong(volumeInfo.getFolder());
long sfSnapshotId = getSolidFireSnapshotId(snapshotInfo.getId());
SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, _storagePoolDetailsDao);
SolidFireUtil.rollBackVolumeToSnapshot(sfConnection, sfVolumeId, sfSnapshotId);
}
catch (Exception ex) {
s_logger.debug(SolidFireUtil.LOG_PREFIX + "Failed to take CloudStack snapshot: " + snapshotInfo.getId(), ex);
errMsg = ex.getMessage();
}
CommandResult result = new CommandResult();
result.setResult(errMsg);
callback.complete(result);
}
@Override

View File

@ -689,6 +689,20 @@ public class SolidFireUtil {
executeJsonRpc(sfConnection, strSnapshotToDeleteJson);
}
public static void rollBackVolumeToSnapshot(SolidFireConnection sfConnection, long volumeId, long snapshotId) {
final Gson gson = new GsonBuilder().create();
RollbackToInitiate rollbackToInitiate = new RollbackToInitiate(volumeId, snapshotId);
String strRollbackToInitiateJson = gson.toJson(rollbackToInitiate);
String strRollbackInitiatedResultJson = executeJsonRpc(sfConnection, strRollbackToInitiateJson);
RollbackInitiatedResult rollbackInitiatedResult = gson.fromJson(strRollbackInitiatedResultJson, RollbackInitiatedResult.class);
verifyResult(rollbackInitiatedResult.result, strRollbackInitiatedResultJson, gson);
}
public static long createSolidFireClone(SolidFireConnection sfConnection, long lVolumeId, String cloneName) {
final Gson gson = new GsonBuilder().create();
@ -1293,6 +1307,26 @@ public class SolidFireUtil {
}
}
@SuppressWarnings("unused")
private static final class RollbackToInitiate {
private final String method = "RollbackToSnapshot";
private final RollbackToInitiateParams params;
private RollbackToInitiate(final long lVolumeId, final long lSnapshotId) {
params = new RollbackToInitiateParams(lVolumeId, lSnapshotId);
}
private static final class RollbackToInitiateParams {
private long volumeID;
private long snapshotID;
private RollbackToInitiateParams(final long lVolumeId, final long lSnapshotId) {
volumeID = lVolumeId;
snapshotID = lSnapshotId;
}
}
}
@SuppressWarnings("unused")
private static final class CloneToCreate {
private final String method = "CloneVolume";
@ -1539,6 +1573,15 @@ public class SolidFireUtil {
}
}
@SuppressWarnings("unused")
private static final class RollbackInitiatedResult {
private Result result;
private static final class Result {
private long snapshotID;
}
}
private static final class CloneCreateResult {
private Result result;

View File

@ -138,6 +138,9 @@ import org.apache.cloudstack.api.response.VpnUsersResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.framework.jobs.AsyncJob;
@ -278,6 +281,7 @@ import com.cloud.storage.UploadVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.snapshot.SnapshotPolicy;
import com.cloud.storage.snapshot.SnapshotSchedule;
import com.cloud.template.VirtualMachineTemplate;
@ -307,13 +311,13 @@ public class ApiResponseHelper implements ResponseGenerator {
private static final Logger s_logger = Logger.getLogger(ApiResponseHelper.class);
private static final DecimalFormat s_percentFormat = new DecimalFormat("##.##");
@Inject
private EntityManager _entityMgr;
@Inject
private UsageService _usageSvc;
@Inject
NetworkModel _ntwkModel;
@Inject
protected AccountManager _accountMgr;
@Inject
@ -322,6 +326,10 @@ public class ApiResponseHelper implements ResponseGenerator {
ConfigurationManager _configMgr;
@Inject
SnapshotDataFactory snapshotfactory;
@Inject
private VolumeDao _volumeDao;
@Inject
private DataStoreManager _dataStoreMgr;
@Override
public UserResponse createUserResponse(User user) {
@ -459,10 +467,13 @@ public class ApiResponseHelper implements ResponseGenerator {
snapshotResponse.setState(snapshot.getState());
SnapshotInfo snapshotInfo = null;
if (!(snapshot instanceof SnapshotInfo)) {
snapshotInfo = snapshotfactory.getSnapshot(snapshot.getId(), DataStoreRole.Image);
} else {
if (snapshot instanceof SnapshotInfo) {
snapshotInfo = (SnapshotInfo)snapshot;
} else {
DataStoreRole dataStoreRole = getDataStoreRole(snapshot);
snapshotInfo = snapshotfactory.getSnapshot(snapshot.getId(), dataStoreRole);
}
if (snapshotInfo == null) {
@ -485,6 +496,25 @@ public class ApiResponseHelper implements ResponseGenerator {
return snapshotResponse;
}
private DataStoreRole getDataStoreRole(Snapshot snapshot) {
long volumeId = snapshot.getVolumeId();
VolumeVO volumeVO = _volumeDao.findById(volumeId);
long storagePoolId = volumeVO.getPoolId();
DataStore dataStore = _dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
Boolean supportsStorageSystemSnapshots = new Boolean(value);
if (supportsStorageSystemSnapshots) {
return DataStoreRole.Primary;
}
return DataStoreRole.Image;
}
@Override
public VMSnapshotResponse createVMSnapshotResponse(VMSnapshot vmSnapshot) {
VMSnapshotResponse vmSnapshotResponse = new VMSnapshotResponse();