CLOUDSTACK-5841:Snapshots taken before migration NFS to S3 can not be

used cross-zone.
This commit is contained in:
Min Chen 2014-01-08 17:43:06 -08:00
parent 649d9d927f
commit 3223028fd6
10 changed files with 164 additions and 13 deletions

View File

@ -30,4 +30,6 @@ public interface SnapshotDataFactory {
SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role);
List<SnapshotInfo> listSnapshotOnCache(long snapshotId);
SnapshotInfo getReadySnapshotOnCache(long snapshotId);
}

View File

@ -25,4 +25,6 @@ public interface SnapshotService {
boolean deleteSnapshot(SnapshotInfo snapshot);
boolean revertSnapshot(Long snapshotId);
void syncVolumeSnapshotsToRegionStore(long volumeId, DataStore store);
}

View File

@ -30,12 +30,15 @@ import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
@ -52,7 +55,6 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.log4j.Logger;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
@ -140,6 +142,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
SnapshotDataFactory snapshotFactory;
@Inject
ConfigDepot _configDepot;
@Inject
SnapshotService _snapshotSrv;
private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
protected List<StoragePoolAllocator> _storagePoolAllocators;
@ -149,7 +153,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
}
public void setStoragePoolAllocators(List<StoragePoolAllocator> storagePoolAllocators) {
this._storagePoolAllocators = storagePoolAllocators;
_storagePoolAllocators = storagePoolAllocators;
}
protected List<PodAllocator> _podAllocators;
@ -159,7 +163,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
}
public void setPodAllocators(List<PodAllocator> podAllocators) {
this._podAllocators = podAllocators;
_podAllocators = podAllocators;
}
protected VolumeOrchestrator() {
@ -326,6 +330,16 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
VolumeInfo vol = volFactory.getVolume(volume.getId());
DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), DataStoreRole.Image);
// sync snapshot to region store if necessary
DataStore snapStore = snapInfo.getDataStore();
long snapVolId = snapInfo.getVolumeId();
try {
_snapshotSrv.syncVolumeSnapshotsToRegionStore(snapVolId, snapStore);
} catch (Exception ex) {
// log but ignore the sync error to avoid any potential S3 down issue, it should be sync next time
s_logger.warn(ex.getMessage(), ex);
}
// create volume on primary from snapshot
AsyncCallFuture<VolumeApiResult> future = volService.createVolumeFromSnapshot(vol, store, snapInfo);
try {
VolumeApiResult result = future.get();

View File

@ -49,6 +49,8 @@ public interface SnapshotDataStoreDao extends GenericDao<SnapshotDataStoreVO, Lo
// delete the snapshot entry on primary data store to make sure that next snapshot will be full snapshot
void deleteSnapshotRecordsOnPrimary();
SnapshotDataStoreVO findReadyOnCache(long snapshotId);
List<SnapshotDataStoreVO> listOnCache(long snapshotId);
void updateStoreRoleToCache(long storeId);

View File

@ -689,6 +689,9 @@ public class TemplateServiceImpl implements TemplateService {
@Override
public void syncTemplateToRegionStore(long templateId, DataStore store) {
if (_storeMgr.isRegionStore(store)) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Sync template " + templateId + " from cache to object store...");
}
// if template is on region wide object store, check if it is really downloaded there (by checking install_path). Sync template to region
// wide store if it is not there physically.
TemplateInfo tmplOnStore = _templateFactory.getTemplate(templateId, store);

View File

@ -79,6 +79,18 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
return so;
}
@Override
public SnapshotInfo getReadySnapshotOnCache(long snapshotId) {
SnapshotDataStoreVO snapStore = snapshotStoreDao.findReadyOnCache(snapshotId);
if (snapStore != null) {
DataStore store = storeMgr.getDataStore(snapStore.getDataStoreId(), DataStoreRole.ImageCache);
return getSnapshot(snapshotId, store);
} else {
return null;
}
}
@Override
public List<SnapshotInfo> listSnapshotOnCache(long snapshotId) {
List<SnapshotDataStoreVO> cacheSnapshots = snapshotStoreDao.listOnCache(snapshotId);

View File

@ -78,6 +78,7 @@ public class SnapshotObject implements SnapshotInfo {
SnapshotDataStoreDao snapshotStoreDao;
@Inject
StorageStrategyFactory storageStrategyFactory;
private String installPath; // temporarily set installPath before passing to resource for entries with empty installPath for object store migration case
public SnapshotObject() {
@ -198,6 +199,9 @@ public class SnapshotObject implements SnapshotInfo {
@Override
public String getPath() {
if (installPath != null)
return installPath;
DataObjectInStore objectInStore = objectInStoreMgr.findObject(this, getDataStore());
if (objectInStore != null) {
return objectInStore.getInstallPath();
@ -205,6 +209,10 @@ public class SnapshotObject implements SnapshotInfo {
return null;
}
public void setPath(String installPath) {
this.installPath = installPath;
}
@Override
public String getName() {
return snapshot.getName();
@ -354,12 +362,12 @@ public class SnapshotObject implements SnapshotInfo {
@Override
public void addPayload(Object data) {
this.payload = data;
payload = data;
}
@Override
public Object getPayload() {
return this.payload;
return payload;
}
@Override

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.storage.snapshot;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
@ -37,6 +38,7 @@ 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.SnapshotResult;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
@ -49,6 +51,9 @@ import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
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.template.TemplateConstants;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@ -56,13 +61,17 @@ import com.cloud.utils.fsm.NoTransitionException;
public class SnapshotServiceImpl implements SnapshotService {
private static final Logger s_logger = Logger.getLogger(SnapshotServiceImpl.class);
@Inject
protected SnapshotDao _snapshotDao;
@Inject
protected SnapshotDataStoreDao _snapshotStoreDao;
@Inject
SnapshotDataFactory snapshotfactory;
SnapshotDataFactory _snapshotFactory;
@Inject
DataStoreManager dataStoreMgr;
@Inject
DataMotionService motionSrv;
@Inject
StorageCacheManager _cacheMgr;
static private class CreateSnapshotContext<T> extends AsyncRpcContext<T> {
final SnapshotInfo snapshot;
@ -244,7 +253,7 @@ public class SnapshotServiceImpl implements SnapshotService {
try {
snapObj.processEvent(Snapshot.Event.BackupToSecondary);
DataStore imageStore = this.findSnapshotImageStore(snapshot);
DataStore imageStore = findSnapshotImageStore(snapshot);
if (imageStore == null) {
throw new CloudRuntimeException("can not find an image stores");
}
@ -255,7 +264,7 @@ public class SnapshotServiceImpl implements SnapshotService {
CopySnapshotContext<CommandResult> context = new CopySnapshotContext<CommandResult>(null, snapshot, snapshotOnImageStore, future);
AsyncCallbackDispatcher<SnapshotServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copySnapshotAsyncCallback(null, null)).setContext(context);
this.motionSrv.copyAsync(snapshot, snapshotOnImageStore, caller);
motionSrv.copyAsync(snapshot, snapshotOnImageStore, caller);
} catch (Exception e) {
s_logger.debug("Failed to copy snapshot", e);
result.setResult("Failed to copy snapshot:" + e.toString());
@ -306,7 +315,7 @@ public class SnapshotServiceImpl implements SnapshotService {
CopyCmdAnswer answer = (CopyCmdAnswer)result.getAnswer();
destSnapshot.processEvent(Event.OperationSuccessed, result.getAnswer());
srcSnapshot.processEvent(Snapshot.Event.OperationSucceeded);
snapResult = new SnapshotResult(this.snapshotfactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), answer);
snapResult = new SnapshotResult(_snapshotFactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), answer);
future.complete(snapResult);
} catch (Exception e) {
s_logger.debug("Failed to update snapshot state", e);
@ -391,7 +400,7 @@ public class SnapshotServiceImpl implements SnapshotService {
@Override
public boolean revertSnapshot(Long snapshotId) {
SnapshotInfo snapshot = snapshotfactory.getSnapshot(snapshotId, DataStoreRole.Primary);
SnapshotInfo snapshot = _snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
PrimaryDataStore store = (PrimaryDataStore)snapshot.getDataStore();
AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
@ -417,4 +426,92 @@ public class SnapshotServiceImpl implements SnapshotService {
return false;
}
// This routine is used to push snapshots currently on cache store, but not in region store to region store.
// used in migrating existing NFS secondary storage to S3. We chose to push all volume related snapshots to handle delta snapshots smoothly.
@Override
public void syncVolumeSnapshotsToRegionStore(long volumeId, DataStore store) {
if (dataStoreMgr.isRegionStore(store)) {
// list all backed up snapshots for the given volume
List<SnapshotVO> snapshots = _snapshotDao.listByStatus(volumeId, Snapshot.State.BackedUp);
if (snapshots != null) {
for (SnapshotVO snapshot : snapshots) {
syncSnapshotToRegionStore(snapshot.getId(), store);
}
}
}
}
// push one individual snapshots currently on cache store to region store if it is not there already
private void syncSnapshotToRegionStore(long snapshotId, DataStore store) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("sync snapshot " + snapshotId + " from cache to object store...");
}
// if snapshot is already on region wide object store, check if it is really downloaded there (by checking install_path). Sync snapshot to region
// wide store if it is not there physically.
SnapshotInfo snapOnStore = _snapshotFactory.getSnapshot(snapshotId, store);
if (snapOnStore == null) {
throw new CloudRuntimeException("Cannot find an entry in snapshot_store_ref for snapshot " + snapshotId + " on region store: " + store.getName());
}
if (snapOnStore.getPath() == null || snapOnStore.getPath().length() == 0) {
// snapshot is not on region store yet, sync to region store
SnapshotInfo srcSnapshot = _snapshotFactory.getReadySnapshotOnCache(snapshotId);
if (srcSnapshot == null) {
throw new CloudRuntimeException("Cannot find snapshot " + snapshotId + " on cache store");
}
AsyncCallFuture<SnapshotResult> future = syncToRegionStoreAsync(srcSnapshot, store);
try {
SnapshotResult result = future.get();
if (result.isFailed()) {
throw new CloudRuntimeException("sync snapshot from cache to region wide store failed for image store " + store.getName() + ":"
+ result.getResult());
}
_cacheMgr.releaseCacheObject(srcSnapshot); // reduce reference count for template on cache, so it can recycled by schedule
} catch (Exception ex) {
throw new CloudRuntimeException("sync snapshot from cache to region wide store failed for image store " + store.getName());
}
}
}
private AsyncCallFuture<SnapshotResult> syncToRegionStoreAsync(SnapshotInfo snapshot, DataStore store) {
AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
// no need to create entry on snapshot_store_ref here, since entries are already created when updateCloudToUseObjectStore is invoked.
// But we need to set default install path so that sync can be done in the right s3 path
SnapshotInfo snapshotOnStore = _snapshotFactory.getSnapshot(snapshot, store);
String installPath = TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR + "/"
+ snapshot.getAccountId() + "/" + snapshot.getVolumeId();
((SnapshotObject)snapshotOnStore).setPath(installPath);
CopySnapshotContext<CommandResult> context = new CopySnapshotContext<CommandResult>(null, snapshot,
snapshotOnStore, future);
AsyncCallbackDispatcher<SnapshotServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher
.create(this);
caller.setCallback(caller.getTarget().syncSnapshotCallBack(null, null)).setContext(context);
motionSrv.copyAsync(snapshot, snapshotOnStore, caller);
return future;
}
protected Void syncSnapshotCallBack(AsyncCallbackDispatcher<SnapshotServiceImpl, CopyCommandResult> callback,
CopySnapshotContext<CommandResult> context) {
CopyCommandResult result = callback.getResult();
SnapshotInfo destSnapshot = context.destSnapshot;
SnapshotResult res = new SnapshotResult(destSnapshot, null);
AsyncCallFuture<SnapshotResult> future = context.future;
try {
if (result.isFailed()) {
res.setResult(result.getResult());
// no change to existing snapshot_store_ref, will try to re-sync later if other call triggers this sync operation
} else {
// this will update install path properly, next time it will not sync anymore.
destSnapshot.processEvent(Event.OperationSuccessed, result.getAnswer());
}
future.complete(res);
} catch (Exception e) {
s_logger.debug("Failed to process sync snapshot callback", e);
res.setResult(e.toString());
future.complete(res);
}
return null;
}
}

View File

@ -27,6 +27,9 @@ import java.util.List;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
@ -38,8 +41,6 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.LocalHostEndpoint;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
@ -211,7 +212,7 @@ public class DefaultEndPointSelector implements EndPointSelector {
@Override
public EndPoint select(DataObject srcData, DataObject destData, StorageAction action) {
if (action == StorageAction.BACKUPSNAPSHOT) {
if (action == StorageAction.BACKUPSNAPSHOT && srcData.getDataStore().getRole() == DataStoreRole.Primary) {
SnapshotInfo srcSnapshot = (SnapshotInfo)srcData;
if (srcSnapshot.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
VolumeInfo volumeInfo = srcSnapshot.getBaseVolume();

View File

@ -103,6 +103,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
storeSnapshotSearch.and("snapshot_id", storeSnapshotSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
storeSnapshotSearch.and("store_id", storeSnapshotSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
storeSnapshotSearch.and("store_role", storeSnapshotSearch.entity().getRole(), SearchCriteria.Op.EQ);
storeSnapshotSearch.and("state", storeSnapshotSearch.entity().getState(), SearchCriteria.Op.EQ);
storeSnapshotSearch.done();
snapshotIdSearch = createSearchBuilder();
@ -334,6 +335,15 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
}
@Override
public SnapshotDataStoreVO findReadyOnCache(long snapshotId) {
SearchCriteria<SnapshotDataStoreVO> sc = storeSnapshotSearch.create();
sc.setParameters("snapshot_id", snapshotId);
sc.setParameters("store_role", DataStoreRole.ImageCache);
sc.setParameters("state", ObjectInDataStoreStateMachine.State.Ready);
return findOneIncludingRemovedBy(sc);
}
@Override
public List<SnapshotDataStoreVO> listOnCache(long snapshotId) {
SearchCriteria<SnapshotDataStoreVO> sc = storeSnapshotSearch.create();