| // | |
| // 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()); | |
| } | |
| } |