blob: 55d691f33e0ba66ef43bbcda9b927098eeb449d5 [file] [log] [blame]
// 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 java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
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.ObjectInDataStoreStateMachine.Event;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
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.SnapshotStrategy;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.SnapshotZoneDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@Component
public class StorPoolSnapshotStrategy implements SnapshotStrategy {
private static final Logger log = Logger.getLogger(StorPoolSnapshotStrategy.class);
@Inject
private SnapshotDao _snapshotDao;
@Inject
private PrimaryDataStoreDao _primaryDataStoreDao;
@Inject
private VolumeDao _volumeDao;
@Inject
private SnapshotDataStoreDao _snapshotStoreDao;
@Inject
private SnapshotDetailsDao _snapshotDetailsDao;
@Inject
private SnapshotService snapshotSvr;
@Inject
private SnapshotDataFactory snapshotDataFactory;
@Inject
private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
DataStoreManager dataStoreMgr;
@Inject
SnapshotZoneDao snapshotZoneDao;
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
SnapshotObject snapshotObj = (SnapshotObject) snapshotInfo;
try {
snapshotObj.processEvent(Snapshot.Event.BackupToSecondary);
snapshotObj.processEvent(Snapshot.Event.OperationSucceeded);
} catch (NoTransitionException ex) {
log.debug("Failed to change state: " + ex.toString());
try {
snapshotObj.processEvent(Snapshot.Event.OperationFailed);
} catch (NoTransitionException ex2) {
log.debug("Failed to change state: " + ex2.toString());
}
}
return snapshotInfo;
}
@Override
public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
final SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
VolumeVO volume = _volumeDao.findByIdIncludingRemoved(snapshotVO.getVolumeId());
String name = StorPoolHelper.getSnapshotName(snapshotId, snapshotVO.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
boolean res = false;
// clean-up snapshot from Storpool storage pools
StoragePoolVO storage = _primaryDataStoreDao.findById(volume.getPoolId());
if (storage.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
try {
SpConnectionDesc conn = StorPoolUtil.getSpConnection(storage.getUuid(), storage.getId(), storagePoolDetailsDao, _primaryDataStoreDao);
SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn);
if (resp.getError() != null) {
final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError());
StorPoolUtil.spLog(err);
} else {
res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId);
StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name);
}
} catch (Exception e) {
String errMsg = String.format("Cannot delete snapshot due to %s", e.getMessage());
throw new CloudRuntimeException(errMsg);
}
}
return res;
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
log.debug(String.format("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op));
if (op != SnapshotOperation.DELETE) {
return StrategyPriority.CANT_HANDLE;
}
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotOnPrimary == null) {
return StrategyPriority.CANT_HANDLE;
}
if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store
StoragePoolVO storagePoolVO = _primaryDataStoreDao.findById(snapshotOnPrimary.getDataStoreId());
if (!zoneId.equals(storagePoolVO.getDataCenterId())) {
return StrategyPriority.CANT_HANDLE;
}
}
String name = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
if (name != null) {
StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: globalId=%s", name);
return StrategyPriority.HIGHEST;
}
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshot.getId(), snapshot.getUuid());
if (snapshotDetails != null) {
_snapshotDetailsDao.remove(snapshotDetails.getId());
}
return StrategyPriority.CANT_HANDLE;
}
private boolean deleteSnapshotChain(SnapshotInfo snapshot) {
log.debug("delete snapshot chain for snapshot: " + snapshot.getId());
final SnapshotInfo snapOnImage = snapshot;
boolean result = false;
boolean resultIsSet = false;
try {
while (snapshot != null &&
(snapshot.getState() == Snapshot.State.Destroying || snapshot.getState() == Snapshot.State.Destroyed || snapshot.getState() == Snapshot.State.Error)) {
SnapshotInfo child = snapshot.getChild();
if (child != null) {
log.debug("the snapshot has child, can't delete it on the storage");
break;
}
log.debug("Snapshot: " + snapshot.getId() + " doesn't have children, so it's ok to delete it and its parents");
SnapshotInfo parent = snapshot.getParent();
boolean deleted = false;
if (parent != null) {
if (parent.getPath() != null && parent.getPath().equalsIgnoreCase(snapshot.getPath())) {
log.debug("for empty delta snapshot, only mark it as destroyed in db");
snapshot.processEvent(Event.DestroyRequested);
snapshot.processEvent(Event.OperationSuccessed);
deleted = true;
if (!resultIsSet) {
result = true;
resultIsSet = true;
}
}
}
if (!deleted) {
if (StorPoolStorageAdaptor.getVolumeNameFromPath(snapOnImage.getPath(), true) == null) {
try {
boolean r = snapshotSvr.deleteSnapshot(snapshot);
if (r) {
List<SnapshotInfo> cacheSnaps = snapshotDataFactory.listSnapshotOnCache(snapshot.getId());
for (SnapshotInfo cacheSnap : cacheSnaps) {
log.debug("Delete snapshot " + snapshot.getId() + " from image cache store: " + cacheSnap.getDataStore().getName());
cacheSnap.delete();
}
}
if (!resultIsSet) {
result = r;
resultIsSet = true;
}
} catch (Exception e) {
log.debug("Failed to delete snapshot on storage. ", e);
}
}
} else {
result = true;
}
snapshot = parent;
}
} catch (Exception e) {
log.debug("delete snapshot failed: ", e);
}
return result;
}
protected boolean areLastSnapshotRef(long snapshotId) {
List<SnapshotDataStoreVO> snapshotStoreRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
if (CollectionUtils.isEmpty(snapshotStoreRefs) || snapshotStoreRefs.size() == 1) {
return true;
}
return snapshotStoreRefs.size() == 2 && DataStoreRole.Primary.equals(snapshotStoreRefs.get(1).getRole());
}
protected boolean deleteSnapshotOnImageAndPrimary(long snapshotId, DataStore store) {
SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, store);
SnapshotObject obj = (SnapshotObject)snapshotOnImage;
boolean areLastSnapshotRef = areLastSnapshotRef(snapshotId);
try {
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.DestroyRequested);
}
} catch (NoTransitionException e) {
log.debug("Failed to set the state to destroying: ", e);
return false;
}
try {
boolean result = deleteSnapshotChain(snapshotOnImage);
_snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotId, store.getId(), store.getRole(), false);
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.OperationSucceeded);
}
if (result) {
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotOnImage.getSnapshotId(), DataStoreRole.Primary);
if (snapshotOnPrimary != null) {
snapshotOnPrimary.setState(State.Destroyed);
_snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
}
}
} catch (Exception e) {
log.debug("Failed to delete snapshot: ", e);
try {
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.OperationFailed);
}
} catch (NoTransitionException e1) {
log.debug("Failed to change snapshot state: " + e.toString());
}
return false;
}
return true;
}
private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) {
final long snapshotId = snapshotVO.getId();
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
if (snapshotDetails != null) {
_snapshotDetailsDao.removeDetails(snapshotId);
}
if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) {
throw new InvalidParameterValueException(String.format("Snapshot in %s can not be deleted for a zone", snapshotVO.getState()));
}
if (snapshotVO.getState() == Snapshot.State.Allocated) {
_snapshotDao.remove(snapshotId);
return true;
}
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
return true;
}
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
List<Long> deletedRefs = new ArrayList<>();
for (SnapshotDataStoreVO ref : storeRefs) {
boolean refZoneIdMatch = false;
if (zoneId != null) {
Long refZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
refZoneIdMatch = zoneId.equals(refZoneId);
}
if (zoneId == null || refZoneIdMatch) {
_snapshotStoreDao.expunge(ref.getId());
deletedRefs.add(ref.getId());
}
}
if (deletedRefs.size() == storeRefs.size()) {
_snapshotDao.remove(snapshotId);
}
return true;
}
if (snapshotVO.getState() == Snapshot.State.CreatedOnPrimary) {
snapshotVO.setState(Snapshot.State.Destroyed);
_snapshotDao.update(snapshotId, snapshotVO);
return true;
}
if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) && !Snapshot.State.Error.equals(snapshotVO.getState()) &&
!Snapshot.State.Destroying.equals(snapshotVO.getState())) {
throw new InvalidParameterValueException("Can't delete snapshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
}
List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
if (zoneId != null) {
storeRefs.removeIf(ref -> !zoneId.equals(dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())));
}
for (SnapshotDataStoreVO ref : storeRefs) {
if (!deleteSnapshotOnImageAndPrimary(snapshotId, dataStoreMgr.getDataStore(ref.getDataStoreId(), ref.getRole()))) {
return false;
}
}
if (zoneId != null) {
snapshotZoneDao.removeSnapshotFromZone(snapshotVO.getId(), zoneId);
} else {
snapshotZoneDao.removeSnapshotFromZones(snapshotVO.getId());
}
return true;
}
@Override
public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
return null;
}
@Override
public boolean revertSnapshot(SnapshotInfo snapshot) {
return false;
}
@Override
public void postSnapshotCreation(SnapshotInfo snapshot) {
}
}