blob: 12088c76de3c959602158df470ae3f8e879d62da [file]
//
// 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.backup;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.to.DataTO;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
import com.cloud.storage.Upload;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.utils.ReflectionUse;
import com.cloud.utils.component.ComponentLifecycleBase;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.VmWork;
import com.cloud.vm.VmWorkDeleteBackup;
import com.cloud.vm.VmWorkJobHandler;
import com.cloud.vm.VmWorkJobHandlerProxy;
import com.cloud.vm.VmWorkRestoreBackup;
import com.cloud.vm.VmWorkRestoreVolumeBackupAndAttach;
import com.cloud.vm.VmWorkTakeBackup;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.VMSnapshot;
import org.apache.cloudstack.api.response.ExtractResponse;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupDetailsDao;
import org.apache.cloudstack.backup.dao.InternalBackupJoinDao;
import org.apache.cloudstack.backup.dao.InternalBackupStoragePoolDao;
import org.apache.cloudstack.backup.to.BackupScreenshotObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.jobs.JobInfo;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.RevertSnapshotCommand;
import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadVO;
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
import org.apache.cloudstack.backup.to.BackupScreenshotTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class InternalBackupServiceImpl extends ComponentLifecycleBase implements InternalBackupService, VmWorkJobHandler {
protected Logger logger = LogManager.getLogger(getClass());
@Inject
private InternalBackupStoragePoolDao internalBackupStoragePoolDao;
@Inject
private BackupManager backupManager;
@Inject
private BackupDao backupDao;
@Inject
private AsyncJobManager jobManager;
@Inject
private UserVmDao userVmDao;
@Inject
private VirtualMachineManager virtualMachineManager;
@Inject
private VolumeDao volumeDao;
@Inject
private InternalBackupJoinDao internalBackupJoinDao;
@Inject
private BackupDetailsDao backupDetailDao;
@Inject
private ImageStoreObjectDownloadDao imageStoreObjectDownloadDao;
@Inject
private DataStoreManager dataStoreMgr;
private VmWorkJobHandlerProxy jobHandlerProxy = new VmWorkJobHandlerProxy(this);
private HashMap<String, InternalBackupProvider> internalBackupProviderMap = new HashMap<>();
private List<InternalBackupProvider> internalBackupProviders;
public void setInternalBackupProviders(final List<InternalBackupProvider> internalBackupProviders) {
this.internalBackupProviders = internalBackupProviders;
}
@Override
public boolean start() {
super.start();
if (internalBackupProviders != null) {
for (InternalBackupProvider internalBackupProvider : internalBackupProviders) {
internalBackupProviderMap.put(internalBackupProvider.getName().toLowerCase(), internalBackupProvider);
}
}
return true;
}
@Override
public void configureChainInfo(DataTO volumeTo, Command cmd) {
if (!(volumeTo instanceof VolumeObjectTO)) {
return;
}
VolumeObjectTO volumeObjectTO = (VolumeObjectTO) volumeTo;
InternalBackupStoragePoolVO backupDelta = internalBackupStoragePoolDao.findOneByVolumeId(volumeObjectTO.getVolumeId());
if (backupDelta == null) {
return;
}
volumeObjectTO.setChainInfo(backupDelta.getBackupDeltaParentPath());
if (cmd instanceof DeleteCommand) {
((DeleteCommand) cmd).setDeleteChain(true);
}
if (cmd instanceof RevertSnapshotCommand) {
((RevertSnapshotCommand) cmd).setDeleteChain(true);
}
logger.debug("Configured chain info for volume [{}]. Set it as [{}].", volumeObjectTO.getUuid(), volumeObjectTO.getChainInfo());
}
@Override
public void cleanupBackupMetadata(long volumeId) {
logger.debug("Cleaning up backup metadata for volume [{}].", volumeId);
InternalBackupStoragePoolVO delta = internalBackupStoragePoolDao.findOneByVolumeId(volumeId);
if (delta == null) {
return;
}
internalBackupStoragePoolDao.expungeByVolumeId(volumeId);
if (CollectionUtils.isNotEmpty(internalBackupStoragePoolDao.listByBackupId(delta.getBackupId()))) {
return;
}
InternalBackupJoinVO joinVO = internalBackupJoinDao.findById(delta.getBackupId());
logger.debug("Volume [{}] was the last volume with deltas in backup [{}]. Setting the backup as not current and not END_OF_CHAIN.", volumeId, joinVO.getUuid());
backupDetailDao.removeDetail(joinVO.getId(), BackupDetailsDao.CURRENT);
if (!joinVO.getEndOfChain()) {
backupDetailDao.persist(new BackupDetailVO(joinVO.getId(), BackupDetailsDao.END_OF_CHAIN, Boolean.TRUE.toString(), true));
}
}
@Override
public void prepareVolumeForDetach(Volume volume, VirtualMachine virtualMachine) {
if (isBackupFrameworkDisabled(virtualMachine)) {
return;
}
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(virtualMachine.getDataCenterId());
if (internalBackupProvider == null) {
return;
}
internalBackupProvider.prepareVolumeForDetach(volume, virtualMachine);
}
@Override
public void prepareVolumeForMigration(Volume volume) {
if (volume.getInstanceId() == null) {
return;
}
VirtualMachine virtualMachine = virtualMachineManager.findById(volume.getInstanceId());
if (isBackupFrameworkDisabled(virtualMachine)) {
return;
}
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(volume.getDataCenterId());
if (internalBackupProvider == null) {
return;
}
internalBackupProvider.prepareVolumeForMigration(volume, virtualMachine);
}
@Override
public void updateVolumeId(long oldVolumeId, long newVolumeId) {
VolumeVO volumeVO = volumeDao.findById(newVolumeId);
if (volumeVO.getInstanceId() == null) {
return;
}
VirtualMachine virtualMachine = virtualMachineManager.findById(volumeVO.getInstanceId());
if (isBackupFrameworkDisabled(virtualMachine)) {
return;
}
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(virtualMachine.getDataCenterId());
if (internalBackupProvider == null) {
return;
}
internalBackupProvider.updateVolumeId(virtualMachine, oldVolumeId, newVolumeId);
}
@Override
public void prepareVmForSnapshotRevert(VMSnapshot vmSnapshot) {
VirtualMachine virtualMachine = virtualMachineManager.findById(vmSnapshot.getVmId());
if (isBackupFrameworkDisabled(virtualMachine)) {
return;
}
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(virtualMachine.getDataCenterId());
if (internalBackupProvider == null) {
return;
}
internalBackupProvider.prepareVmForSnapshotRevert(vmSnapshot, virtualMachine);
}
/**
* Ask the backup provider to get the necessary secondary storages that must be mounted at VM start.
* <br/>
* Note: This is currently only used for Backup Validation VMs. As they are created with backing files that are on secondary storage.
* */
@Override
public Set<String> getSecondaryStorageUrls(UserVm userVm) {
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(userVm.getDataCenterId());
if (internalBackupProvider == null) {
return Set.of();
}
return internalBackupProvider.getSecondaryStorageUrls(userVm);
}
@Override
public boolean startBackupCompression(long backupId, long hostId, long zoneId) {
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(zoneId);
if (internalBackupProvider == null) {
return false;
}
return internalBackupProvider.startBackupCompression(backupId, hostId);
}
@Override
public boolean finalizeBackupCompression(long backupId, long hostId, long zoneId) {
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(zoneId);
if (internalBackupProvider == null) {
return false;
}
return internalBackupProvider.finalizeBackupCompression(backupId, hostId);
}
@Override
public boolean validateBackup(long backupId, long hostId, long zoneId) {
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(zoneId);
if (internalBackupProvider == null) {
return false;
}
return internalBackupProvider.validateBackup(backupId, hostId);
}
@Override
public ExtractResponse downloadScreenshot(long backupId) {
BackupDetailVO screenshotPathDetail = backupDetailDao.findDetail(backupId, BackupDetailsDao.SCREENSHOT_PATH);
ExtractResponse response = new ExtractResponse();
if (screenshotPathDetail == null) {
response.setState(Upload.Status.DOWNLOAD_URL_NOT_CREATED.toString());
return response;
}
BackupDetailVO imageStoreId = backupDetailDao.findDetail(backupId, BackupDetailsDao.IMAGE_STORE_ID);
ImageStoreEntity imageStore = (ImageStoreEntity) dataStoreMgr.getDataStore(Long.parseLong(imageStoreId.getValue()), DataStoreRole.Image);
String screenshotPath = screenshotPathDetail.getValue();
ImageStoreObjectDownloadVO imageStoreObj = imageStoreObjectDownloadDao.findByStoreIdAndPath(Long.parseLong(imageStoreId.getValue()), screenshotPath);
if (imageStoreObj == null) {
BackupScreenshotTO dataTo = new BackupScreenshotTO(imageStore.getTO(), Hypervisor.HypervisorType.KVM, screenshotPath);
BackupScreenshotObject objectTo = new BackupScreenshotObject(dataTo, imageStore);
String downloadUrl = imageStore.createEntityExtractUrl(screenshotPath, Storage.ImageFormat.PNG, objectTo);
imageStoreObj = imageStoreObjectDownloadDao.persist(new ImageStoreObjectDownloadVO(imageStore.getId(), screenshotPath, downloadUrl));
}
if (imageStoreObj != null) {
response.setUrl(imageStoreObj.getDownloadUrl());
response.setName(screenshotPath.substring(screenshotPath.lastIndexOf("/") + 1));
response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString());
} else {
response.setState(Upload.Status.DOWNLOAD_URL_NOT_CREATED.toString());
}
return response;
}
@Override
public boolean finishBackupChain(long vmId) {
VirtualMachine vm = virtualMachineManager.findById(vmId);
if (vm == null) {
throw new InvalidParameterValueException(String.format("Unable to find VM with ID [%s].", vmId));
}
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(vm.getDataCenterId());
if (internalBackupProvider == null) {
return false;
}
return internalBackupProvider.finishBackupChain(vm);
}
@Override
public Pair<JobInfo.Status, String> handleVmWorkJob(VmWork work) throws Exception {
return jobHandlerProxy.handleVmWorkJob(work);
}
@ReflectionUse
public Pair<JobInfo.Status, String> orchestrateTakeBackup(VmWorkTakeBackup work) {
BackupVO backupVO = backupDao.findById(work.getBackupId());
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(backupVO.getZoneId());
if (internalBackupProvider == null) {
return new Pair<>(JobInfo.Status.FAILED, jobManager.marshallResultObject(Boolean.FALSE));
}
return new Pair<>(JobInfo.Status.SUCCEEDED, jobManager.marshallResultObject(internalBackupProvider.orchestrateTakeBackup(backupVO, work.isQuiesceVm(), work.isIsolated())));
}
@ReflectionUse
public Pair<JobInfo.Status, String> orchestrateDeleteBackup(VmWorkDeleteBackup work) {
BackupVO backupVO = backupDao.findById(work.getBackupId());
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(backupVO.getZoneId());
if (internalBackupProvider == null) {
return new Pair<>(JobInfo.Status.FAILED, jobManager.marshallResultObject(Boolean.FALSE));
}
return new Pair<>(JobInfo.Status.SUCCEEDED, jobManager.marshallResultObject(internalBackupProvider.orchestrateDeleteBackup(backupVO, work.isForced())));
}
@ReflectionUse
public Pair<JobInfo.Status, String> orchestrateRestoreVMFromBackup(VmWorkRestoreBackup work) {
BackupVO backupVO = backupDao.findById(work.getBackupId());
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(backupVO.getZoneId());
if (internalBackupProvider == null) {
return new Pair<>(JobInfo.Status.FAILED, jobManager.marshallResultObject(Boolean.FALSE));
}
return new Pair<>(JobInfo.Status.SUCCEEDED, jobManager.marshallResultObject(internalBackupProvider.orchestrateRestoreVMFromBackup(backupVO,
userVmDao.findById(work.getVmId()), work.isQuickRestore(), work.getHostId(), true)));
}
@ReflectionUse
public Pair<JobInfo.Status, String> orchestrateRestoreBackupVolumeAndAttachToVM(VmWorkRestoreVolumeBackupAndAttach work) {
BackupVO backupVO = backupDao.findById(work.getBackupId());
InternalBackupProvider internalBackupProvider = getInternalBackupProviderForZone(backupVO.getZoneId());
if (internalBackupProvider == null) {
return new Pair<>(JobInfo.Status.FAILED, jobManager.marshallResultObject(Boolean.FALSE));
}
return new Pair<>(JobInfo.Status.SUCCEEDED, jobManager.marshallResultObject(internalBackupProvider.orchestrateRestoreBackedUpVolume(backupVO, userVmDao.findById(work.getVmId()),
work.getBackupVolumeInfo(), work.getHostIp(), work.isQuickRestore())));
}
protected InternalBackupProvider getInternalBackupProviderForZone(long zoneId) {
return Transaction.execute(TransactionLegacy.CLOUD_DB, (TransactionCallback<InternalBackupProvider>)status -> {
BackupProvider backupProvider = backupManager.getBackupProvider(zoneId);
return internalBackupProviderMap.get(backupProvider.getName());
});
}
protected boolean isBackupFrameworkDisabled(VirtualMachine virtualMachine) {
return !BackupManager.BackupFrameworkEnabled.valueIn(virtualMachine.getDataCenterId());
}
}