mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch 'pr-2081'
This commit is contained in:
commit
4c89b5b97a
@ -79,6 +79,8 @@ public interface Snapshot extends ControlledEntity, Identity, InternalIdentity,
|
||||
|
||||
String getName();
|
||||
|
||||
long getSnapshotId();
|
||||
|
||||
Date getCreated();
|
||||
|
||||
Type getRecurringType();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -31,6 +31,10 @@ public interface SnapshotInfo extends DataObject, Snapshot {
|
||||
|
||||
Object getPayload();
|
||||
|
||||
void setFullBackup(Boolean fullBackup);
|
||||
|
||||
Boolean getFullBackup();
|
||||
|
||||
Long getDataCenterId();
|
||||
|
||||
ObjectInDataStoreStateMachine.State getStatus();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +162,11 @@ public class SnapshotVO implements Snapshot {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnapshotId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getsnapshotType() {
|
||||
return snapshotType;
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@ -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}; }
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
177
test/integration/component/test_separate_backup_from_snapshot.py
Normal file
177
test/integration/component/test_separate_backup_from_snapshot.py
Normal 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
|
||||
@ -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):
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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, {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user