diff --git a/api/src/com/cloud/storage/Volume.java b/api/src/com/cloud/storage/Volume.java index 9f5f502b17f..f70ead93718 100644 --- a/api/src/com/cloud/storage/Volume.java +++ b/api/src/com/cloud/storage/Volume.java @@ -39,6 +39,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"), + RevertSnapshotting("There is a snapshot created on this volume, the volume is being reverting from snapshot"), Resizing("The volume is being resized"), Expunging("The volume is being expunging"), Expunged("The volume has been expunged"), @@ -91,6 +92,9 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba s_fsm.addTransition(new StateMachine2.Transition(Ready, Event.SnapshotRequested, Snapshotting, null)); s_fsm.addTransition(new StateMachine2.Transition(Snapshotting, Event.OperationSucceeded, Ready, null)); s_fsm.addTransition(new StateMachine2.Transition(Snapshotting, Event.OperationFailed, Ready,null)); + s_fsm.addTransition(new StateMachine2.Transition(Ready, Event.RevertSnapshotRequested, RevertSnapshotting, null)); + s_fsm.addTransition(new StateMachine2.Transition(RevertSnapshotting, Event.OperationSucceeded, Ready, null)); + s_fsm.addTransition(new StateMachine2.Transition(RevertSnapshotting, Event.OperationFailed, Ready,null)); s_fsm.addTransition(new StateMachine2.Transition(Allocated, Event.MigrationCopyRequested, Creating, null)); s_fsm.addTransition(new StateMachine2.Transition(Creating, Event.MigrationCopyFailed, Allocated, null)); s_fsm.addTransition(new StateMachine2.Transition(Creating, Event.MigrationCopySucceeded, Ready, Arrays.asList(new StateMachine2.Transition.Impact[]{StateMachine2.Transition.Impact.USAGE}))); @@ -131,6 +135,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba MigrationCopySucceeded, MigrationCopyFailed, SnapshotRequested, + RevertSnapshotRequested, DestroyRequested, ExpungingRequested, ResizeRequested, diff --git a/api/src/com/cloud/storage/snapshot/SnapshotApiService.java b/api/src/com/cloud/storage/snapshot/SnapshotApiService.java index a86ef37b452..fb48f477454 100644 --- a/api/src/com/cloud/storage/snapshot/SnapshotApiService.java +++ b/api/src/com/cloud/storage/snapshot/SnapshotApiService.java @@ -106,7 +106,7 @@ public interface SnapshotApiService { */ Long getHostIdForSnapshotOperation(Volume vol); - boolean revertSnapshot(Long snapshotId); + Snapshot revertSnapshot(Long snapshotId); SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd); } diff --git a/api/src/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java index 0c79f81564d..b8fbb02ad22 100644 --- a/api/src/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java @@ -29,8 +29,8 @@ import org.apache.cloudstack.api.BaseCmd; 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; import com.cloud.event.EventTypes; import com.cloud.storage.Snapshot; @@ -39,17 +39,27 @@ import com.cloud.user.Account; @APICommand(name = "revertSnapshot", description = "revert a volume snapshot.", responseObject = SnapshotResponse.class, entityType = {Snapshot.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class RevertSnapshotCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(RevertSnapshotCmd.class.getName()); private static final String s_name = "revertsnapshotresponse"; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// @ACL(accessType = AccessType.OperateEntry) @Parameter(name= ApiConstants.ID, type= BaseCmd.CommandType.UUID, entityType = SnapshotResponse.class, required=true, description="The ID of the snapshot") private Long id; + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// public Long getId() { return id; } + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// @Override public String getCommandName() { return s_name; @@ -88,9 +98,9 @@ public class RevertSnapshotCmd extends BaseAsyncCmd { @Override public void execute() { CallContext.current().setEventDetails("Snapshot Id: " + getId()); - boolean result = _snapshotService.revertSnapshot(getId()); - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); + Snapshot snapshot = _snapshotService.revertSnapshot(getId()); + if (snapshot != null) { + SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot); response.setResponseName(getCommandName()); setResponseObject(response); } else { diff --git a/core/src/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java b/core/src/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java new file mode 100644 index 00000000000..1a4403b3baf --- /dev/null +++ b/core/src/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java @@ -0,0 +1,49 @@ +/* + * 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.storage.command; + +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +public final class RevertSnapshotCommand extends StorageSubSystemCommand { + private SnapshotObjectTO data; + private boolean _executeInSequence = false; + + public RevertSnapshotCommand(SnapshotObjectTO data) { + super(); + this.data = data; + } + + protected RevertSnapshotCommand() { + super(); + } + + public SnapshotObjectTO getData() { + return this.data; + } + + @Override + public void setExecuteInSequence(final boolean executeInSequence) { + _executeInSequence = executeInSequence; + } + + @Override + public boolean executeInSequence() { + return _executeInSequence; + } +} diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index 5e7090d321d..0c6bd93982a 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -48,5 +48,5 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver { public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); - public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); + public void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback); } diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java index 4edeb55ab12..322de77503a 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java @@ -24,7 +24,7 @@ public interface SnapshotService { boolean deleteSnapshot(SnapshotInfo snapshot); - boolean revertSnapshot(Long snapshotId); + boolean revertSnapshot(SnapshotInfo snapshot); void syncVolumeSnapshotsToRegionStore(long volumeId, DataStore store); diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java index 0af9b0971a6..fbf60041ac9 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java @@ -29,7 +29,7 @@ public interface SnapshotStrategy { boolean deleteSnapshot(Long snapshotId); - boolean revertSnapshot(Long snapshotId); + boolean revertSnapshot(SnapshotInfo snapshot); StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op); } diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java index 231b241408b..4b83e312c7b 100644 --- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java +++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java @@ -60,4 +60,7 @@ public interface SnapshotDataStoreDao extends GenericDao future = new AsyncCallFuture(); RevertSnapshotContext context = new RevertSnapshotContext(null, snapshot, future); AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().revertSnapshotCallback(null, null)).setContext(context); - ((PrimaryDataStoreDriver)store.getDriver()).revertSnapshot(snapshot, caller); + ((PrimaryDataStoreDriver)store.getDriver()).revertSnapshot(snapshot, snapshotOnPrimaryStore, caller); SnapshotResult result = null; try { diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java index 6db8343214b..b08a8377f95 100644 --- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java @@ -37,7 +37,7 @@ public abstract class SnapshotStrategyBase implements SnapshotStrategy { } @Override - public boolean revertSnapshot(Long snapshotId) { - return snapshotSvr.revertSnapshot(snapshotId); + public boolean revertSnapshot(SnapshotInfo snapshot) { + return snapshotSvr.revertSnapshot(snapshot); } } diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java index deec7fe5f55..2c71525aad5 100644 --- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer; import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -56,6 +57,8 @@ import com.cloud.storage.DataStoreRole; import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDao; @@ -153,8 +156,44 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { } @Override - public boolean revertSnapshot(Long snapshotId) { - throw new UnsupportedOperationException("Reverting not supported. Create a template or volume based on the snapshot instead."); + public boolean revertSnapshot(SnapshotInfo snapshot) { + if (canHandle(snapshot,SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) { + throw new UnsupportedOperationException("Reverting not supported. Create a template or volume based on the snapshot instead."); + } + + SnapshotVO snapshotVO = _snapshotDao.acquireInLockTable(snapshot.getId()); + if (snapshotVO == null) { + throw new CloudRuntimeException("Failed to get lock on snapshot:" + snapshot.getId()); + } + + try { + VolumeInfo volumeInfo = snapshot.getBaseVolume(); + StoragePool store = (StoragePool)volumeInfo.getDataStore(); + if (store != null && store.getStatus() != StoragePoolStatus.Up) { + snapshot.processEvent(Event.OperationFailed); + throw new CloudRuntimeException("store is not in up state"); + } + volumeInfo.stateTransit(Volume.Event.RevertSnapshotRequested); + boolean result = false; + try { + result = snapshotSvr.revertSnapshot(snapshot); + if (! result) { + s_logger.debug("Failed to revert snapshot: " + snapshot.getId()); + throw new CloudRuntimeException("Failed to revert snapshot: " + snapshot.getId()); + } + } finally { + if (result) { + volumeInfo.stateTransit(Volume.Event.OperationSucceeded); + } else { + volumeInfo.stateTransit(Volume.Event.OperationFailed); + } + } + return result; + } finally { + if (snapshotVO != null) { + _snapshotDao.releaseFromLockTable(snapshot.getId()); + } + } } @Override @@ -401,10 +440,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { @Override public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) { - if (SnapshotOperation.REVERT.equals(op)) { - return StrategyPriority.CANT_HANDLE; - } - long volumeId = snapshot.getVolumeId(); VolumeVO volumeVO = _volumeDao.findById(volumeId); @@ -424,6 +459,13 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { storagePoolId = volumeVO.getPoolId(); } + if (SnapshotOperation.REVERT.equals(op)) { + if (volumeVO != null && ImageFormat.QCOW2.equals(volumeVO.getFormat())) + return StrategyPriority.DEFAULT; + else + return StrategyPriority.CANT_HANDLE; + } + DataStore dataStore = _dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); Map mapCapabilities = dataStore.getDriver().getCapabilities(); diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java index 8ed77df9aaf..96ea7bbc0c5 100644 --- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java @@ -278,7 +278,7 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase { } @Override - public boolean revertSnapshot(Long snapshotId) { + public boolean revertSnapshot(SnapshotInfo snapshot) { throw new CloudRuntimeException("revert Snapshot is not supported"); } diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/db/SnapshotDataStoreDaoImpl.java b/engine/storage/src/org/apache/cloudstack/storage/image/db/SnapshotDataStoreDaoImpl.java index 142cd669cb3..fea0b77942b 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/db/SnapshotDataStoreDaoImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/db/SnapshotDataStoreDaoImpl.java @@ -55,6 +55,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase storeSnapshotSearch; private SearchBuilder snapshotIdSearch; private SearchBuilder volumeIdSearch; + private SearchBuilder volumeSearch; private final String parentSearch = "select store_id, store_role, snapshot_id from cloud.snapshot_store_ref where store_id = ? " + " and store_role = ? and volume_id = ? and state = 'Ready'" + " order by created DESC " + " limit 1"; @@ -119,6 +120,11 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase sc = volumeSearch.create(); + sc.setParameters("volume_id", volumeId); + sc.setParameters("store_role", role); + return findOneBy(sc); + } + @Override public List findBySnapshotId(long snapshotId) { SearchCriteria sc = snapshotIdSearch.create(); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java new file mode 100644 index 00000000000..22edf814fc8 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java @@ -0,0 +1,95 @@ +// +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; + +import org.apache.cloudstack.storage.command.RevertSnapshotCommand; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.storage.to.SnapshotObjectTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.NfsTO; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; + +@ResourceWrapper(handles = RevertSnapshotCommand.class) +public final class LibvirtRevertSnapshotCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtRevertSnapshotCommandWrapper.class); + + @Override + public Answer execute(final RevertSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) { + SnapshotObjectTO snapshot = command.getData(); + VolumeObjectTO volume = snapshot.getVolume(); + PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO) volume.getDataStore(); + DataStoreTO snapshotImageStore = snapshot.getDataStore(); + if (!(snapshotImageStore instanceof NfsTO)) { + return new Answer(command, false, "revert snapshot on object storage is not implemented yet"); + } + NfsTO nfsImageStore = (NfsTO) snapshotImageStore; + + String secondaryStoragePoolUrl = nfsImageStore.getUrl(); + + String volumePath = volume.getPath(); + String snapshotPath = null; + String snapshotRelPath = null; + KVMStoragePool secondaryStoragePool = null; + try { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl); + String ssPmountPath = secondaryStoragePool.getLocalPath(); + snapshotRelPath = snapshot.getPath(); + snapshotPath = ssPmountPath + File.separator + snapshotRelPath; + + KVMPhysicalDisk snapshotDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), + primaryStore.getUuid(), volumePath); + KVMStoragePool primaryPool = snapshotDisk.getPool(); + + if (primaryPool.getType() == StoragePoolType.RBD) { + return new Answer(command, false, "revert snapshot to RBD is not implemented yet"); + } else { + Script cmd = new Script(libvirtComputingResource.manageSnapshotPath(), libvirtComputingResource.getCmdsTimeout(), s_logger); + cmd.add("-v", snapshotPath); + cmd.add("-n", snapshotDisk.getName()); + cmd.add("-p", snapshotDisk.getPath()); + String result = cmd.execute(); + if (result != null) { + s_logger.debug("Failed to revert snaptshot: " + result); + return new Answer(command, false, result); + } + } + + return new Answer(command, true, "RevertSnapshotCommand executes successfully"); + } catch (CloudRuntimeException e) { + return new Answer(command, false, e.toString()); + } + } +} diff --git a/plugins/storage/volume/cloudbyte/src/org/apache/cloudstack/storage/datastore/driver/ElastistorPrimaryDataStoreDriver.java b/plugins/storage/volume/cloudbyte/src/org/apache/cloudstack/storage/datastore/driver/ElastistorPrimaryDataStoreDriver.java index cc7c8efafbc..b7ae4d43ffd 100644 --- a/plugins/storage/volume/cloudbyte/src/org/apache/cloudstack/storage/datastore/driver/ElastistorPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/cloudbyte/src/org/apache/cloudstack/storage/datastore/driver/ElastistorPrimaryDataStoreDriver.java @@ -393,7 +393,7 @@ public class ElastistorPrimaryDataStoreDriver extends CloudStackPrimaryDataStore } @Override - public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { throw new UnsupportedOperationException(); } diff --git a/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java index e416bf86e64..e92e8f6e04f 100644 --- a/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -46,6 +46,7 @@ import org.apache.cloudstack.storage.command.CopyCmdAnswer; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.CreateObjectCommand; import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.RevertSnapshotCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.SnapshotObjectTO; @@ -323,7 +324,28 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri } @Override - public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { + SnapshotObjectTO snapshotTO = (SnapshotObjectTO)snapshot.getTO(); + RevertSnapshotCommand cmd = new RevertSnapshotCommand(snapshotTO); + + CommandResult result = new CommandResult(); + try { + EndPoint ep = epSelector.select(snapshotOnPrimaryStore); + if ( ep == null ){ + String errMsg = "No remote endpoint to send RevertSnapshotCommand, check if host or ssvm is down?"; + s_logger.error(errMsg); + result.setResult(errMsg); + } else { + Answer answer = ep.sendMessage(cmd); + if (answer != null && !answer.getResult()) { + result.setResult(answer.getDetails()); + } + } + } catch (Exception ex) { + s_logger.debug("Unable to revert snapshot " + snapshot.getId(), ex); + result.setResult(ex.toString()); + } + callback.complete(result); } @Override diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java index 7ce46a2c511..752470316d3 100644 --- a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java @@ -130,7 +130,7 @@ public class NexentaPrimaryDataStoreDriver implements PrimaryDataStoreDriver { public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) {} @Override - public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) {} + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) {} @Override public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback callback) { diff --git a/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java b/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java index 7c989d26ac3..0a4cfd6c080 100644 --- a/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java @@ -210,7 +210,7 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver } @Override - public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { } @Override diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java index a19aa6b2908..e9344265ae1 100644 --- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java @@ -688,7 +688,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { } @Override - public void revertSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback callback) { + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { throw new UnsupportedOperationException("Reverting not supported. Create a template or volume based on the snapshot instead."); } diff --git a/scripts/storage/qcow2/managesnapshot.sh b/scripts/storage/qcow2/managesnapshot.sh index 4225407caab..b8caa2e03bd 100755 --- a/scripts/storage/qcow2/managesnapshot.sh +++ b/scripts/storage/qcow2/managesnapshot.sh @@ -235,12 +235,21 @@ backup_snapshot() { fi return 0 } + +revert_snapshot() { + local snapshotPath=$1 + local destPath=$2 + ${qemu_img} convert -f qcow2 -O qcow2 "$snapshotPath" "$destPath" || \ + ( printf "${qemu_img} failed to revert snapshot ${snapshotPath} to disk ${destPath}.\n" >&2; return 2 ) + return 0 +} #set -x cflag= dflag= rflag= bflag= +vflag= nflag= pathval= snapshot= @@ -249,7 +258,7 @@ deleteDir= dmsnapshot=no dmrollback=no -while getopts 'c:d:r:n:b:p:t:f' OPTION +while getopts 'c:d:r:n:b:v:p:t:f' OPTION do case $OPTION in c) cflag=1 @@ -264,6 +273,9 @@ do b) bflag=1 pathval="$OPTARG" ;; + v) vflag=1 + pathval="$OPTARG" + ;; n) nflag=1 snapshot="$OPTARG" ;; @@ -304,6 +316,10 @@ elif [ "$rflag" == "1" ] then rollback_snapshot $pathval "$snapshot" $destPath exit $? +elif [ "$vflag" == "1" ] +then + revert_snapshot $pathval $destPath + exit $? fi diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java index bc661305d2a..c622c552710 100644 --- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -246,13 +246,17 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, } @Override - public boolean revertSnapshot(Long snapshotId) { - Snapshot snapshot = _snapshotDao.findById(snapshotId); + public Snapshot revertSnapshot(Long snapshotId) { + SnapshotVO snapshot = _snapshotDao.findById(snapshotId); if (snapshot == null) { throw new InvalidParameterValueException("No such snapshot"); } - Volume volume = _volsDao.findById(snapshot.getVolumeId()); + VolumeVO volume = _volsDao.findById(snapshot.getVolumeId()); + if (volume.getState() != Volume.State.Ready) { + throw new InvalidParameterValueException("The volume is not in Ready state."); + } + Long instanceId = volume.getInstanceId(); // If this volume is attached to an VM, then the VM needs to be in the stopped state @@ -264,14 +268,28 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, } } + SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image); + if (snapshotInfo == null) { + throw new CloudRuntimeException("snapshot:" + snapshotId + " not exist in data store"); + } + SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.REVERT); if (snapshotStrategy == null) { s_logger.error("Unable to find snaphot strategy to handle snapshot with id '" + snapshotId + "'"); - return false; + return null; } - return snapshotStrategy.revertSnapshot(snapshotId); + boolean result = snapshotStrategy.revertSnapshot(snapshotInfo); + if (result) { + // update volume size and primary storage count + _resourceLimitMgr.decrementResourceCount(snapshot.getAccountId(), ResourceType.primary_storage, + new Long(volume.getSize() - snapshot.getSize())); + volume.setSize(snapshot.getSize()); + _volsDao.update(volume.getId(), volume); + return snapshotInfo; + } + return null; } @Override diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 60ac9ca4d64..c170bde1c93 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12675,6 +12675,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -167px -677px; } +.revertSnapshot .icon, .restoreVM .icon, .restore .icon, .recover .icon { @@ -12690,6 +12691,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -167px -66px; } +.revertSnapshot:hover .icon, .restoreVM:hover .icon, .restore:hover .icon { background-position: -168px -613px; diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index ce4f07782e7..77bd00284b1 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -1862,6 +1862,9 @@ volumename: { label: 'label.volume' }, + name: { + label: 'label.name' + }, intervaltype: { label: 'label.interval.type' },