Merge branch 'pr-2081'

This commit is contained in:
Mike Tutkowski 2017-10-18 14:25:45 -06:00
commit 4c89b5b97a
33 changed files with 595 additions and 120 deletions

View File

@ -79,6 +79,8 @@ public interface Snapshot extends ControlledEntity, Identity, InternalIdentity,
String getName();
long getSnapshotId();
Date getCreated();
Type getRecurringType();

View File

@ -82,7 +82,7 @@ public interface VolumeApiService {
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType) throws ResourceAllocationException;
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup) throws ResourceAllocationException;
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;

View File

@ -27,6 +27,7 @@ public class ApiConstants {
public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
public static final String ASYNC_BACKUP = "asyncbackup";
public static final String USER_API_KEY = "userapikey";
public static final String APPLIED = "applied";
public static final String LIST_LB_VMIPS = "lbvmips";

View File

@ -79,6 +79,9 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the snapshot")
private String snapshotName;
@Parameter(name = ApiConstants.ASYNC_BACKUP, type = CommandType.BOOLEAN, required = false, description = "asynchronous backup if true")
private Boolean asyncBackup;
private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;
// ///////////////////////////////////////////////////
@ -200,7 +203,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
Snapshot snapshot;
try {
snapshot =
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType());
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup());
if (snapshot != null) {
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);
@ -246,4 +249,12 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
}
return null;
}
public Boolean getAsyncBackup() {
if (asyncBackup == null) {
return false;
} else {
return asyncBackup;
}
}
}

View File

@ -82,7 +82,7 @@ public class CreateSnapshotCmdTest extends TestCase {
try {
Mockito.when(volumeApiService.takeSnapshot(anyLong(), anyLong(), anyLong(),
any(Account.class), anyBoolean(), isNull(Snapshot.LocationType.class))).thenReturn(snapshot);
any(Account.class), anyBoolean(), isNull(Snapshot.LocationType.class), anyBoolean())).thenReturn(snapshot);
} catch (Exception e) {
Assert.fail("Received exception when success expected " + e.getMessage());
@ -115,7 +115,7 @@ public class CreateSnapshotCmdTest extends TestCase {
try {
Mockito.when(volumeApiService.takeSnapshot(anyLong(), anyLong(), anyLong(),
any(Account.class), anyBoolean(), isNull(Snapshot.LocationType.class))).thenReturn(null);
any(Account.class), anyBoolean(), isNull(Snapshot.LocationType.class), anyBoolean())).thenReturn(null);
} catch (Exception e) {
Assert.fail("Received exception when success expected " + e.getMessage());
}

View File

@ -31,6 +31,10 @@ public interface SnapshotInfo extends DataObject, Snapshot {
Object getPayload();
void setFullBackup(Boolean fullBackup);
Boolean getFullBackup();
Long getDataCenterId();
ObjectInDataStoreStateMachine.State getStatus();

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.engine.subsystem.api.storage;
import com.cloud.storage.Snapshot.Event;
public interface SnapshotService {
SnapshotResult takeSnapshot(SnapshotInfo snapshot);
@ -29,4 +31,8 @@ public interface SnapshotService {
void syncVolumeSnapshotsToRegionStore(long volumeId, DataStore store);
void cleanupVolumeDuringSnapshotFailure(Long volumeId, Long snapshotId);
void processEventOnSnapshotObject(SnapshotInfo snapshot, Event event);
void cleanupOnSnapshotBackupFailure(SnapshotInfo snapshot);
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage;
import com.cloud.storage.Snapshot;
public interface SnapshotStrategy {
enum SnapshotOperation {
TAKE, BACKUP, DELETE, REVERT
}
@ -32,4 +33,6 @@ public interface SnapshotStrategy {
boolean revertSnapshot(SnapshotInfo snapshot);
StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op);
void postSnapshotCreation(SnapshotInfo snapshot);
}

View File

@ -27,15 +27,17 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
private Long snapshotId;
private boolean quiesceVm;
private Snapshot.LocationType locationType;
private boolean asyncBackup;
public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String handlerName,
Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType) {
Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType, boolean asyncBackup) {
super(userId, accountId, vmId, handlerName);
this.volumeId = volumeId;
this.policyId = policyId;
this.snapshotId = snapshotId;
this.quiesceVm = quiesceVm;
this.locationType = locationType;
this.asyncBackup = asyncBackup;
}
public Long getVolumeId() {
@ -55,4 +57,8 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
}
public Snapshot.LocationType getLocationType() { return locationType; }
public boolean isAsyncBackup() {
return asyncBackup;
}
}

View File

@ -162,6 +162,11 @@ public class SnapshotVO implements Snapshot {
return name;
}
@Override
public long getSnapshotId() {
return id;
}
@Override
public short getsnapshotType() {
return snapshotType;

View File

@ -520,10 +520,10 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
DataObject cacheData = null;
SnapshotInfo snapshotInfo = (SnapshotInfo)srcData;
Object payload = snapshotInfo.getPayload();
Boolean snapshotFullBackup = snapshotInfo.getFullBackup();
Boolean fullSnapshot = true;
if (payload != null) {
fullSnapshot = (Boolean)payload;
if (snapshotFullBackup != null) {
fullSnapshot = snapshotFullBackup;
}
Map<String, String> options = new HashMap<String, String>();
options.put("fullSnapshot", fullSnapshot.toString());

View File

@ -0,0 +1,29 @@
/*
* 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.snapshot;
import com.cloud.utils.SerialVersionUID;
public class SnapshotBackupException extends Exception {
private static final long serialVersionUID = SerialVersionUID.SnapshotBackupException;
public SnapshotBackupException(String msg) {
super(msg);
}
}

View File

@ -62,6 +62,7 @@ public class SnapshotObject implements SnapshotInfo {
private SnapshotVO snapshot;
private DataStore store;
private Object payload;
private Boolean fullBackup;
@Inject
protected SnapshotDao snapshotDao;
@Inject
@ -230,6 +231,11 @@ public class SnapshotObject implements SnapshotInfo {
return snapshot.getName();
}
@Override
public long getSnapshotId() {
return snapshot.getSnapshotId();
}
@Override
public Date getCreated() {
return snapshot.getCreated();
@ -388,6 +394,16 @@ public class SnapshotObject implements SnapshotInfo {
return payload;
}
@Override
public void setFullBackup(Boolean data) {
fullBackup = data;
}
@Override
public Boolean getFullBackup() {
return fullBackup;
}
@Override
public boolean delete() {
if (store != null) {

View File

@ -24,7 +24,6 @@ import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
@ -44,16 +43,22 @@ import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.template.TemplateConstants;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@ -72,6 +77,8 @@ public class SnapshotServiceImpl implements SnapshotService {
DataMotionService motionSrv;
@Inject
StorageCacheManager _cacheMgr;
@Inject
private SnapshotDetailsDao _snapshotDetailsDao;
static private class CreateSnapshotContext<T> extends AsyncRpcContext<T> {
final SnapshotInfo snapshot;
@ -226,9 +233,9 @@ public class SnapshotServiceImpl implements SnapshotService {
// we are taking delta snapshot
private DataStore findSnapshotImageStore(SnapshotInfo snapshot) {
Boolean fullSnapshot = true;
Object payload = snapshot.getPayload();
if (payload != null) {
fullSnapshot = (Boolean)payload;
Boolean snapshotFullBackup = snapshot.getFullBackup();
if (snapshotFullBackup != null) {
fullSnapshot = snapshotFullBackup;
}
if (fullSnapshot) {
return dataStoreMgr.getImageStore(snapshot.getDataCenterId());
@ -300,19 +307,22 @@ public class SnapshotServiceImpl implements SnapshotService {
CopyCommandResult result = callback.getResult();
SnapshotInfo destSnapshot = context.destSnapshot;
SnapshotObject srcSnapshot = (SnapshotObject)context.srcSnapshot;
Object payload = srcSnapshot.getPayload();
CreateSnapshotPayload createSnapshotPayload = (CreateSnapshotPayload)payload;
AsyncCallFuture<SnapshotResult> future = context.future;
SnapshotResult snapResult = new SnapshotResult(destSnapshot, result.getAnswer());
if (result.isFailed()) {
try {
if (createSnapshotPayload.getAsyncBackup()) {
destSnapshot.processEvent(Event.OperationFailed);
throw new SnapshotBackupException("Failed in creating backup of snapshot with ID "+srcSnapshot.getId());
} else {
destSnapshot.processEvent(Event.OperationFailed);
//if backup snapshot failed, mark srcSnapshot in snapshot_store_ref as failed also
srcSnapshot.processEvent(Event.DestroyRequested);
srcSnapshot.processEvent(Event.OperationSuccessed);
srcSnapshot.processEvent(Snapshot.Event.OperationFailed);
_snapshotDao.remove(srcSnapshot.getId());
} catch (NoTransitionException e) {
s_logger.debug("Failed to update state: " + e.toString());
cleanupOnSnapshotBackupFailure(context.srcSnapshot);
}
} catch (SnapshotBackupException e) {
s_logger.debug("Failed to create backup: " + e.toString());
}
snapResult.setResult(result.getResult());
future.complete(snapResult);
@ -547,4 +557,38 @@ public class SnapshotServiceImpl implements SnapshotService {
return null;
}
@Override
public void processEventOnSnapshotObject(SnapshotInfo snapshot, Snapshot.Event event) {
SnapshotObject object = (SnapshotObject)snapshot;
try {
object.processEvent(event);
} catch (NoTransitionException e) {
s_logger.debug("Unable to update the state " + e.toString());
}
}
@Override
public void cleanupOnSnapshotBackupFailure(SnapshotInfo snapshot) {
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
SnapshotObject srcSnapshot = (SnapshotObject)snapshot;
srcSnapshot.processEvent(Event.DestroyRequested);
srcSnapshot.processEvent(Event.OperationSuccessed);
srcSnapshot.processEvent(Snapshot.Event.OperationFailed);
_snapshotDetailsDao.removeDetail(srcSnapshot.getId(), AsyncJob.Constants.MS_ID);
_snapshotDao.remove(srcSnapshot.getId());
} catch (NoTransitionException ex) {
s_logger.debug("Failed to create backup " + ex.toString());
throw new CloudRuntimeException("Failed to backup snapshot" + snapshot.getId());
}
}
});
}
}

View File

@ -53,6 +53,7 @@ public class SnapshotStateMachineManagerImpl implements SnapshotStateMachineMana
stateMachine.addTransition(Snapshot.State.Destroying, Event.OperationSucceeded, Snapshot.State.Destroyed);
stateMachine.addTransition(Snapshot.State.Destroying, Event.OperationFailed, State.BackedUp);
stateMachine.addTransition(Snapshot.State.Destroying, Event.DestroyRequested, Snapshot.State.Destroying);
stateMachine.addTransition(Snapshot.State.BackingUp, Event.BackupToSecondary, Snapshot.State.BackingUp);
stateMachine.registerListener(new SnapshotStateListener());
}

View File

@ -214,7 +214,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
SnapshotResult result = null;
SnapshotInfo snapshotOnPrimary = null;
SnapshotInfo backedUpSnapshot = null;
try {
volumeInfo.stateTransit(Volume.Event.SnapshotRequested);
@ -250,23 +249,10 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
}
snapshotOnPrimary = result.getSnapshot();
backedUpSnapshot = backupSnapshot(snapshotOnPrimary);
updateLocationTypeInDb(backedUpSnapshot);
}
finally {
if (result != null && result.isSuccess()) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
if (snapshotOnPrimary != null && snapshotInfo.getLocationType() == Snapshot.LocationType.SECONDARY) {
// remove the snapshot on primary storage
try {
snapshotSvr.deleteSnapshot(snapshotOnPrimary);
} catch (Exception e) {
s_logger.warn("Failed to clean up snapshot on primary Id:" + snapshotOnPrimary.getId() + " "
+ e.getMessage());
}
}
} else {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
@ -274,7 +260,22 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
snapshotDao.releaseFromLockTable(snapshotInfo.getId());
return backedUpSnapshot;
return snapshotOnPrimary;
}
@Override
public void postSnapshotCreation(SnapshotInfo snapshot) {
updateLocationTypeInDb(snapshot);
if (snapshot.getLocationType() == Snapshot.LocationType.SECONDARY) {
// remove the snapshot on primary storage
try {
snapshotSvr.deleteSnapshot(snapshot);
} catch (Exception e) {
s_logger.warn("Failed to clean up snapshot '" + snapshot.getId() + "' on primary storage: " + e.getMessage());
}
}
}
private void updateLocationTypeInDb(SnapshotInfo snapshotInfo) {
@ -604,4 +605,5 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
return StrategyPriority.CANT_HANDLE;
}
}

View File

@ -33,13 +33,15 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.dao.SyncQueueItemDao;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import com.cloud.configuration.Config;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
@ -52,7 +54,11 @@ import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
@ -81,6 +87,10 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
SnapshotDataFactory snapshotDataFactory;
@Inject
private SnapshotDao _snapshotDao;
@Inject
private SnapshotDetailsDao _snapshotDetailsDao;
@Inject
private SyncQueueItemDao _syncQueueItemDao;
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
@ -159,7 +169,7 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
}
}
snapshot.addPayload(fullBackup);
snapshot.setFullBackup(fullBackup);
return snapshotSvr.backupSnapshot(snapshot);
}
@ -356,9 +366,11 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
@Override
@DB
public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
SnapshotInfo snapshotOnPrimary = null;
Object payload = snapshot.getPayload();
CreateSnapshotPayload createSnapshotPayload = null;
if (payload != null) {
CreateSnapshotPayload createSnapshotPayload = (CreateSnapshotPayload)payload;
createSnapshotPayload = (CreateSnapshotPayload)payload;
if (createSnapshotPayload.getQuiescevm()) {
throw new InvalidParameterValueException("can't handle quiescevm equal true for volume snapshot");
}
@ -386,31 +398,30 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
}
snapshotOnPrimary = result.getSnapshot();
snapshotOnPrimary.addPayload(snapshot.getPayload());
snapshot = result.getSnapshot();
DataStore primaryStore = snapshot.getDataStore();
boolean backupFlag = Boolean.parseBoolean(configDao.getValue(Config.BackupSnapshotAfterTakingSnapshot.toString()));
SnapshotInfo backupedSnapshot;
if(backupFlag) {
backupedSnapshot = backupSnapshot(snapshot);
} else {
// Fake it to get the transitions to fire in the proper order
s_logger.debug("skipping backup of snapshot due to configuration "+Config.BackupSnapshotAfterTakingSnapshot.toString());
SnapshotObject snapObj = (SnapshotObject)snapshot;
try {
snapObj.processEvent(Snapshot.Event.OperationNotPerformed);
} catch (NoTransitionException e) {
s_logger.debug("Failed to change state: " + snapshot.getId() + ": " + e.toString());
throw new CloudRuntimeException(e.toString());
/*The Management Server ID is stored in snapshot_details table with the snapshot id and (name, value): (MS_ID, <ms_id>), to know which snapshots have not been completed in case of some failure situation like
* Mgmt server down etc. and by fetching the entries on restart the cleaning up of failed snapshots is done*/
_snapshotDetailsDao.addDetail(((SnapshotObject)snapshotOnPrimary).getId(), AsyncJob.Constants.MS_ID, Long.toString(ManagementServerNode.getManagementServerId()), false);
return snapshotOnPrimary;
} finally {
if (snapshotVO != null) {
snapshotDao.releaseFromLockTable(snapshot.getId());
}
}
backupedSnapshot = snapshot;
}
@Override
public void postSnapshotCreation(SnapshotInfo snapshotOnPrimary) {
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
_snapshotDetailsDao.removeDetail(((SnapshotObject)snapshotOnPrimary).getId(), AsyncJob.Constants.MS_ID);
DataStore primaryStore = snapshotOnPrimary.getDataStore();
try {
SnapshotInfo parent = snapshot.getParent();
if (backupedSnapshot != null && parent != null && primaryStore instanceof PrimaryDataStoreImpl) {
SnapshotInfo parent = snapshotOnPrimary.getParent();
if (parent != null && primaryStore instanceof PrimaryDataStoreImpl) {
if (((PrimaryDataStoreImpl)primaryStore).getPoolType() != StoragePoolType.RBD) {
Long parentSnapshotId = parent.getId();
while (parentSnapshotId != null && parentSnapshotId != 0L) {
@ -423,7 +434,7 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
}
}
}
SnapshotDataStoreVO snapshotDataStoreVO = snapshotStoreDao.findByStoreSnapshot(primaryStore.getRole(), primaryStore.getId(), snapshot.getId());
SnapshotDataStoreVO snapshotDataStoreVO = snapshotStoreDao.findByStoreSnapshot(primaryStore.getRole(), primaryStore.getId(), snapshotOnPrimary.getId());
if (snapshotDataStoreVO != null) {
snapshotDataStoreVO.setParentSnapshotId(0L);
snapshotStoreDao.update(snapshotDataStoreVO.getId(), snapshotDataStoreVO);
@ -432,12 +443,8 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
} catch (Exception e) {
s_logger.debug("Failed to clean up snapshots on primary storage", e);
}
return backupedSnapshot;
} finally {
if (snapshotVO != null) {
snapshotDao.releaseFromLockTable(snapshot.getId());
}
}
});
}
@Override
@ -455,4 +462,5 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
return StrategyPriority.DEFAULT;
}
}

View File

@ -94,6 +94,12 @@ public class SnapshotEntityImpl implements SnapshotEntity {
return null;
}
@Override
public long getSnapshotId() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Date getCreated() {
// TODO Auto-generated method stub

View File

@ -41,6 +41,8 @@ public interface AsyncJob extends JobInfo {
// is defined
public static final int SIGNAL_MASK_WAKEUP = 1;
public static final String MS_ID = "MS_ID";
public static final String SYNC_LOCK_NAME = "SyncLock";
}

View File

@ -39,6 +39,9 @@ import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.context.CallContext;
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.SnapshotService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.jobs.AsyncJob;
@ -60,6 +63,11 @@ import org.slf4j.MDC;
import com.cloud.cluster.ClusterManagerListener;
import com.cloud.cluster.ManagementServerHost;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Predicate;
@ -126,6 +134,14 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
private VolumeDetailsDao _volumeDetailsDao;
@Inject
private VolumeDao _volsDao;
@Inject
private SnapshotDao _snapshotDao;
@Inject
private SnapshotService snapshotSrv;
@Inject
private SnapshotDataFactory snapshotFactory;
@Inject
private SnapshotDetailsDao _snapshotDetailsDao;
private volatile long _executionRunNumber = 1;
@ -1029,6 +1045,12 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
}
}
}
List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
for (SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotDetailsVO.getResourceId(), DataStoreRole.Primary);
snapshotSrv.processEventOnSnapshotObject(snapshot, Snapshot.Event.OperationFailed);
_snapshotDetailsDao.removeDetail(snapshotDetailsVO.getResourceId(), AsyncJob.Constants.MS_ID);
}
}
});
} catch (Throwable e) {

View File

@ -492,14 +492,6 @@ public enum Config {
"The time interval in seconds when the management server polls for snapshots to be scheduled.",
null),
SnapshotDeltaMax("Snapshots", SnapshotManager.class, Integer.class, "snapshot.delta.max", "16", "max delta snapshots between two full snapshots.", null),
BackupSnapshotAfterTakingSnapshot(
"Snapshots",
SnapshotManager.class,
Boolean.class,
"snapshot.backup.rightafter",
"true",
"backup snapshot right after snapshot is taken",
null),
KVMSnapshotEnabled("Hidden", SnapshotManager.class, Boolean.class, "kvm.snapshot.enabled", "false", "whether snapshot is enabled for KVM hosts", null),
// Advanced

View File

@ -24,6 +24,7 @@ public class CreateSnapshotPayload {
private Account account;
private boolean quiescevm;
private Snapshot.LocationType locationType;
private boolean asyncBackup;
public Long getSnapshotPolicyId() {
return snapshotPolicyId;
@ -58,4 +59,12 @@ public class CreateSnapshotPayload {
public Snapshot.LocationType getLocationType() { return this.locationType; }
public void setLocationType(Snapshot.LocationType locationType) { this.locationType = locationType; }
public void setAsyncBackup(boolean asyncBackup) {
this.asyncBackup = asyncBackup;
}
public boolean getAsyncBackup() {
return this.asyncBackup;
}
}

View File

@ -109,6 +109,7 @@ import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
@ -163,6 +164,7 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import javax.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
@ -850,11 +852,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
boolean shrinkOk = cmd.getShrinkOk();
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
if (volume == null) {
throw new InvalidParameterValueException("No such volume");
}
// checking if there are any ongoing snapshots on the volume which is to be resized
List<SnapshotVO> ongoingSnapshots = _snapshotDao.listByStatus(cmd.getId(), Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp);
if (ongoingSnapshots.size() > 0) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on this volume, resize volume is not permitted, please try again later.");
}
/* Does the caller have authority to act on this volume? */
_accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
@ -2067,7 +2074,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Override
@ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "taking snapshot", async = true)
public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType)
public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
throws ResourceAllocationException {
VolumeInfo volume = volFactory.getVolume(volumeId);
if (volume == null) {
@ -2096,13 +2103,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
VmWorkJobVO placeHolder = null;
placeHolder = createPlaceHolderWork(vm.getId());
try {
return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, locationType);
return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup);
} finally {
_workJobDao.expunge(placeHolder.getId());
}
} else {
Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, snapshotId, account.getId(), quiescevm, locationType);
Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, snapshotId, account.getId(), quiescevm, locationType, asyncBackup);
try {
outcome.get();
@ -2130,13 +2137,14 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
payload.setSnapshotPolicyId(policyId);
payload.setAccount(account);
payload.setQuiescevm(quiescevm);
payload.setAsyncBackup(asyncBackup);
volume.addPayload(payload);
return volService.takeSnapshot(volume);
}
}
private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account,
boolean quiescevm, Snapshot.LocationType locationType)
boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
throws ResourceAllocationException {
VolumeInfo volume = volFactory.getVolume(volumeId);
@ -2156,7 +2164,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
payload.setAccount(account);
payload.setQuiescevm(quiescevm);
payload.setLocationType(locationType);
payload.setAsyncBackup(asyncBackup);
volume.addPayload(payload);
return volService.takeSnapshot(volume);
@ -2971,7 +2979,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
public Outcome<Snapshot> takeVolumeSnapshotThroughJobQueue(final Long vmId, final Long volumeId,
final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm, final Snapshot.LocationType locationType) {
final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm, final Snapshot.LocationType locationType, final boolean asyncBackup) {
final CallContext context = CallContext.current();
final User callingUser = context.getCallingUser();
@ -2994,7 +3002,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// save work context info (there are some duplications)
VmWorkTakeVolumeSnapshot workInfo = new VmWorkTakeVolumeSnapshot(
callingUser.getId(), accountId != null ? accountId : callingAccount.getId(), vm.getId(),
VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType);
VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType, asyncBackup);
workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
_jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
@ -3044,7 +3052,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
private Pair<JobInfo.Status, String> orchestrateTakeVolumeSnapshot(VmWorkTakeVolumeSnapshot work) throws Exception {
Account account = _accountDao.findById(work.getAccountId());
orchestrateTakeVolumeSnapshot(work.getVolumeId(), work.getPolicyId(), work.getSnapshotId(),
account, work.isQuiesceVm(), work.getLocationType());
account, work.isQuiesceVm(), work.getLocationType(), work.isAsyncBackup());
return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED,
_jobMgr.marshallResultObject(work.getSnapshotId()));
}
@ -3072,4 +3080,5 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return workJob;
}
}

View File

@ -18,6 +18,8 @@ package com.cloud.storage.snapshot;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
@ -25,13 +27,13 @@ import com.cloud.exception.ResourceAllocationException;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Volume;
import org.apache.cloudstack.framework.config.ConfigKey;
/**
*
*
*/
public interface SnapshotManager {
public interface SnapshotManager extends Configurable {
public static final int HOURLYMAX = 8;
public static final int DAILYMAX = 8;
public static final int WEEKLYMAX = 8;
@ -48,6 +50,12 @@ public interface SnapshotManager {
"Maximum recurring monthly snapshots to be retained for a volume. If the limit is reached, snapshots from the beginning of the month are deleted so that newer ones can be saved. This limit does not apply to manual snapshots. If set to 0, recurring monthly snapshots can not be scheduled.", false, ConfigKey.Scope.Global, null);
static final ConfigKey<Boolean> usageSnapshotSelection = new ConfigKey<Boolean>("Usage", Boolean.class, "usage.snapshot.virtualsize.select", "false",
"Set the value to true if snapshot usage need to consider virtual size, else physical size is considered ", false);
public static final ConfigKey<Integer> BackupRetryAttempts = new ConfigKey<Integer>(Integer.class, "backup.retry", "Advanced", "3",
"Number of times to retry in creating backup of snapshot on secondary", false, ConfigKey.Scope.Global, null);
public static final ConfigKey<Integer> BackupRetryInterval = new ConfigKey<Integer>(Integer.class, "backup.retry.interval", "Advanced", "300",
"Time in seconds between retries in backing up snapshot to secondary", false, ConfigKey.Scope.Global, null);
void deletePoliciesForVolume(Long volumeId);
/**

View File

@ -76,6 +76,7 @@ import com.cloud.utils.DateUtil.IntervalType;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.JoinBuilder;
@ -114,6 +115,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@ -128,6 +130,9 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Component
public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implements SnapshotManager, SnapshotApiService, Configurable {
@ -189,6 +194,19 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
private int _totalRetries;
private int _pauseInterval;
private int snapshotBackupRetries, snapshotBackupRetryInterval;
private ScheduledExecutorService backupSnapshotExecutor;
@Override
public String getConfigComponentName() {
return SnapshotManager.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {BackupRetryAttempts, BackupRetryInterval, SnapshotHourlyMax, SnapshotDailyMax, SnapshotMonthlyMax, SnapshotWeeklyMax, usageSnapshotSelection};
}
@Override
public Answer sendToPool(Volume vol, Command cmd) {
@ -1093,7 +1111,15 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
throw new CloudRuntimeException("Can't find snapshot strategy to deal with snapshot:" + snapshotId);
}
snapshotStrategy.takeSnapshot(snapshot);
SnapshotInfo snapshotOnPrimary = snapshotStrategy.takeSnapshot(snapshot);
if (payload.getAsyncBackup()) {
backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy), 0, TimeUnit.SECONDS);
} else {
SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshotOnPrimary);
if (backupedSnapshot != null) {
snapshotStrategy.postSnapshotCreation(snapshotOnPrimary);
}
}
try {
postCreateSnapshot(volume.getId(), snapshotId, payload.getSnapshotPolicyId());
@ -1134,6 +1160,39 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
return snapshot;
}
protected class BackupSnapshotTask extends ManagedContextRunnable {
SnapshotInfo snapshot;
int attempts;
SnapshotStrategy snapshotStrategy;
public BackupSnapshotTask(SnapshotInfo snap, int maxRetries, SnapshotStrategy strategy) {
snapshot = snap;
attempts = maxRetries;
snapshotStrategy = strategy;
}
@Override
protected void runInContext() {
try {
s_logger.debug("Value of attempts is " + (snapshotBackupRetries-attempts));
SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshot);
if (backupedSnapshot != null) {
snapshotStrategy.postSnapshotCreation(snapshot);
}
} catch (final Exception e) {
if (attempts >= 0) {
s_logger.debug("Backing up of snapshot failed, for snapshot with ID "+snapshot.getSnapshotId()+", left with "+attempts+" more attempts");
backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshot, --attempts, snapshotStrategy), snapshotBackupRetryInterval, TimeUnit.SECONDS);
} else {
s_logger.debug("Done with "+snapshotBackupRetries+" attempts in backing up of snapshot with ID "+snapshot.getSnapshotId());
snapshotSrv.cleanupOnSnapshotBackupFailure(snapshot);
}
}
}
}
private void updateSnapshotPayload(long storagePoolId, CreateSnapshotPayload payload) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
@ -1185,6 +1244,9 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
_totalRetries = NumbersUtil.parseInt(_configDao.getValue("total.retries"), 4);
_pauseInterval = 2 * NumbersUtil.parseInt(_configDao.getValue("ping.interval"), 60);
snapshotBackupRetries = BackupRetryAttempts.value();
snapshotBackupRetryInterval = BackupRetryInterval.value();
backupSnapshotExecutor = Executors.newScheduledThreadPool(10, new NamedThreadFactory("BackupSnapshotTask"));
s_logger.info("Snapshot Manager is configured.");
return true;
@ -1208,6 +1270,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
@Override
public boolean stop() {
backupSnapshotExecutor.shutdown();
return true;
}
@ -1330,14 +1393,4 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
_resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage, new Long(volume.getSize()));
return snapshot;
}
@Override
public String getConfigComponentName() {
return SnapshotManager.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] { SnapshotHourlyMax, SnapshotDailyMax, SnapshotMonthlyMax, SnapshotWeeklyMax, usageSnapshotSelection}; }
}

View File

@ -227,6 +227,7 @@ import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.Snapshot;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
@ -2677,10 +2678,22 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (expunge && !_accountMgr.isAdmin(ctx.getCallingAccount().getId()) && !AllowUserExpungeRecoverVm.valueIn(cmd.getEntityOwnerId())) {
throw new PermissionDeniedException("Parameter " + ApiConstants.EXPUNGE + " can be passed by Admin only. Or when the allow.user.expunge.recover.vm key is set.");
}
// check if VM exists
UserVmVO vm = _vmDao.findById(vmId);
if (vm == null) {
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
}
// check if there are active volume snapshots tasks
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on ROOT volume, vm destroy is not permitted, please try again later.");
}
s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId);
UserVm destroyedVm = destroyVm(vmId, expunge);
if (expunge) {
UserVmVO vm = _vmDao.findById(vmId);
if (!expunge(vm, ctx.getCallingUserId(), ctx.getCallingAccount())) {
throw new CloudRuntimeException("Failed to expunge vm " + destroyedVm);
}
@ -4915,6 +4928,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new VirtualMachineMigrationException("Destination host, hostId: " + destinationHost.getId()
+ " already has max Running VMs(count includes system VMs), cannot migrate to this host");
}
//check if there are any ongoing volume snapshots on the volumes associated with the VM.
s_logger.debug("Checking if there are any ongoing snapshots volumes associated with VM with ID " + vmId);
if (checkStatusOfVolumeSnapshots(vmId, null)) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on volume(s) attached to this VM, VM Migration is not permitted, please try again later.");
}
s_logger.debug("Found no ongoing snapshots on volumes associated with the vm with id " + vmId);
UserVmVO uservm = _vmDao.findById(vmId);
if (uservm != null) {
@ -5780,6 +5799,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
_accountMgr.checkAccess(caller, null, true, vm);
//check if there are any active snapshots on volumes associated with the VM
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on ROOT volume, Re-install VM is not permitted, please try again later.");
}
s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId);
return restoreVMInternal(caller, vm, newTemplateId);
}
@ -6124,4 +6149,28 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return true; // no info then default to true
}
private boolean checkStatusOfVolumeSnapshots(long vmId, Volume.Type type) {
List<VolumeVO> listVolumes = null;
if (type == Volume.Type.ROOT) {
listVolumes = _volsDao.findByInstanceAndType(vmId, type);
} else if (type == Volume.Type.DATADISK) {
listVolumes = _volsDao.findByInstanceAndType(vmId, type);
} else {
listVolumes = _volsDao.findByInstance(vmId);
}
s_logger.debug("Found "+listVolumes.size()+" no. of volumes of type "+type+" for vm with VM ID "+vmId);
for (VolumeVO volume : listVolumes) {
Long volumeId = volume.getId();
s_logger.debug("Checking status of snapshots for Volume with Volume Id: "+volumeId);
List<SnapshotVO> ongoingSnapshots = _snapshotDao.listByStatus(volumeId, Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp);
int ongoingSnapshotsCount = ongoingSnapshots.size();
s_logger.debug("The count of ongoing Snapshots for VM with ID "+vmId+" and disk type "+type+" is "+ongoingSnapshotsCount);
if (ongoingSnapshotsCount > 0) {
s_logger.debug("Found "+ongoingSnapshotsCount+" no. of snapshots, on volume of type "+type+", which snapshots are not yet backed up");
return true;
}
}
return false;
}
}

View File

@ -386,7 +386,7 @@ public class VolumeApiServiceImplTest {
when(_volFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
when(volumeInfoMock.getState()).thenReturn(Volume.State.Allocated);
when(volumeInfoMock.getPoolId()).thenReturn(1L);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false);
}
@Test
@ -396,7 +396,7 @@ public class VolumeApiServiceImplTest {
when(volumeInfoMock.getInstanceId()).thenReturn(null);
when(volumeInfoMock.getPoolId()).thenReturn(1L);
when (volService.takeSnapshot(Mockito.any(VolumeInfo.class))).thenReturn(snapshotInfoMock);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false);
}
@Test

View File

@ -19,5 +19,6 @@
-- Schema upgrade cleanup from 4.10.0.0 to 4.11.0.0
--;
DELETE FROM `cloud`.`configuration` WHERE name='snapshot.backup.rightafter';
-- CLOUDSTACK-9914: Alter quota_tariff to support currency values up to 5 decimal places
ALTER TABLE `cloud_usage`.`quota_tariff` MODIFY `currency_value` DECIMAL(15,5) not null

View File

@ -0,0 +1,177 @@
# 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.
""" P1 tests for Snapshots
"""
# Import Local Modules
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.config.test_data import test_data
from marvin.lib.base import (Snapshot,
VirtualMachine,
Account,
ServiceOffering,
DiskOffering)
from marvin.lib.common import (get_domain,
get_zone,
get_template,
list_volumes,
list_snapshots,
)
from marvin.lib.utils import (cleanup_resources,
get_hypervisor_type)
class TestSnapshots(cloudstackTestCase):
@classmethod
def setUpClass(cls):
cls.testClient = super(TestSnapshots, cls).getClsTestClient()
cls.api_client = cls.testClient.getApiClient()
cls.services = test_data
# Get Zone, Domain and templates
cls.domain = get_domain(cls.api_client)
cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
cls._cleanup = []
cls.unsupportedHypervisor = False
cls.hypervisor = str(get_hypervisor_type(cls.api_client)).lower()
if cls.hypervisor.lower() in ['hyperv', 'lxc']:
cls.unsupportedHypervisor = True
return
cls.disk_offering = DiskOffering.create(
cls.api_client,
cls.services["disk_offering"]
)
cls.template = get_template(
cls.api_client,
cls.zone.id,
cls.services["ostype"]
)
cls.services["small"]["zoneid"] = cls.zone.id
cls.services["small"]["diskoffering"] = cls.disk_offering.id
cls.service_offering = ServiceOffering.create(
cls.api_client,
cls.services["service_offering"]
)
cls._cleanup = [
cls.service_offering,
cls.disk_offering
]
return
@classmethod
def tearDownClass(cls):
try:
# Cleanup resources used
cleanup_resources(cls.api_client, cls._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()
self.cleanup = []
if self.unsupportedHypervisor:
self.skipTest("Skipping test because unsupported hypervisor: %s" %
self.hypervisor)
# Create VMs, NAT Rules etc
self.account = Account.create(
self.apiclient,
self.services["account"],
domainid=self.domain.id
)
self.cleanup.append(self.account)
self.virtual_machine_with_disk = VirtualMachine.create(
self.api_client,
self.services["small"],
templateid=self.template.id,
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
mode=self.zone.networktype
)
return
def tearDown(self):
try:
# Clean up, terminate the created instance, volumes and snapshots
cleanup_resources(self.apiclient, self.cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
@attr(speed="slow")
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
def test_01_snapshot_data_disk(self):
"""Test Snapshot Data Disk
"""
volume = list_volumes(
self.apiclient,
virtualmachineid=self.virtual_machine_with_disk.id,
type='DATADISK',
listall=True
)
self.assertEqual(
isinstance(volume, list),
True,
"Check list response returns a valid list"
)
self.debug("Creating a Snapshot from data volume: %s" % volume[0].id)
snapshot = Snapshot.create(
self.apiclient,
volume[0].id,
account=self.account.name,
domainid=self.account.domainid,
asyncbackup=True
)
snapshots = list_snapshots(
self.apiclient,
id=snapshot.id
)
self.assertEqual(
isinstance(snapshots, list),
True,
"Check list response returns a valid list"
)
self.assertNotEqual(
snapshots,
None,
"Check if result exists in list item call"
)
self.assertEqual(
snapshots[0].id,
snapshot.id,
"Check resource id in list resources call"
)
self.assertEqual(
snapshot.state,
"BackingUp",
"Check resource state in list resources call"
)
return

View File

@ -1105,7 +1105,7 @@ class Snapshot:
@classmethod
def create(cls, apiclient, volume_id, account=None,
domainid=None, projectid=None, locationtype=None):
domainid=None, projectid=None, locationtype=None, asyncbackup=None):
"""Create Snapshot"""
cmd = createSnapshot.createSnapshotCmd()
cmd.volumeid = volume_id
@ -1117,6 +1117,8 @@ class Snapshot:
cmd.projectid = projectid
if locationtype:
cmd.locationtype = locationtype
if asyncbackup:
cmd.asyncbackup = asyncbackup
return Snapshot(apiclient.createSnapshot(cmd).__dict__)
def delete(self, apiclient):

View File

@ -45,6 +45,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"image.directory":"Image Directory",
"inline":"Inline",
"instances.actions.reboot.label":"Reboot instance",
"label.async.backup":"Async Backup",
"label.CIDR.list":"CIDR list",
"label.CIDR.of.destination.network":"CIDR of destination network",
"label.CPU.cap":"CPU Cap",

View File

@ -813,13 +813,18 @@
},
name: {
label: 'label.name'
},
asyncBackup: {
label: 'label.async.backup',
isBoolean: true
}
}
},
action: function(args) {
var data = {
volumeId: args.context.volumes[0].id,
quiescevm: (args.data.quiescevm == 'on' ? true: false)
quiescevm: (args.data.quiescevm == 'on' ? true: false),
asyncBackup: (args.data.asyncBackup == 'on' ? true: false)
};
if (args.data.name != null && args.data.name.length > 0) {
$.extend(data, {

View File

@ -67,4 +67,5 @@ public interface SerialVersionUID {
public static final long AffinityConflictException = Base | 0x2a;
public static final long NioConnectionException = Base | 0x2c;
public static final long TaskExecutionException = Base | 0x2d;
public static final long SnapshotBackupException = Base | 0x2e;
}