blob: bbdf730e06d87bc189e79abd4b8758448e694e0f [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.backup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd;
import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd;
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.AssignVirtualMachineToBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd;
import org.apache.cloudstack.api.command.user.backup.DeleteBackupCmd;
import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd;
import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd;
import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd;
import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd;
import org.apache.cloudstack.api.command.user.backup.RemoveVirtualMachineFromBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd;
import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd;
import org.apache.cloudstack.api.command.user.backup.UpdateBackupScheduleCmd;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
import org.apache.cloudstack.poll.BackgroundPollManager;
import org.apache.cloudstack.poll.BackgroundPollTask;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.api.ApiDispatcher;
import com.cloud.api.ApiGsonHelper;
import com.cloud.dc.DataCenter;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventTypes;
import com.cloud.event.EventVO;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.projects.Project;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.JoinBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import com.google.gson.Gson;
public class BackupManagerImpl extends ManagerBase implements BackupManager {
private static final Logger LOG = Logger.getLogger(BackupManagerImpl.class);
@Inject
private BackupDao backupDao;
@Inject
private BackupScheduleDao backupScheduleDao;
@Inject
private BackupOfferingDao backupOfferingDao;
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private AccountService accountService;
@Inject
private AccountManager accountManager;
@Inject
private VolumeDao volumeDao;
@Inject
private DataCenterDao dataCenterDao;
@Inject
private BackgroundPollManager backgroundPollManager;
@Inject
private HostDao hostDao;
@Inject
private HypervisorGuruManager hypervisorGuruManager;
@Inject
private PrimaryDataStoreDao primaryDataStoreDao;
@Inject
private DiskOfferingDao diskOfferingDao;
@Inject
private ApiDispatcher apiDispatcher;
@Inject
private AsyncJobManager asyncJobManager;
private AsyncJobDispatcher asyncJobDispatcher;
private Timer backupTimer;
private Date currentTimestamp;
private static Map<String, BackupProvider> backupProvidersMap = new HashMap<>();
private List<BackupProvider> backupProviders;
public AsyncJobDispatcher getAsyncJobDispatcher() {
return asyncJobDispatcher;
}
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
asyncJobDispatcher = dispatcher;
}
@Override
public List<BackupOffering> listBackupProviderOfferings(final Long zoneId) {
if (zoneId == null || zoneId < 1) {
throw new CloudRuntimeException("Invalid zone ID passed");
}
validateForZone(zoneId);
final Account account = CallContext.current().getCallingAccount();
if (!accountService.isRootAdmin(account.getId())) {
throw new PermissionDeniedException("Parameter external can only be specified by a Root Admin, permission denied");
}
final BackupProvider backupProvider = getBackupProvider(zoneId);
LOG.debug("Listing external backup offerings for the backup provider configured for zone ID " + zoneId);
return backupProvider.listBackupOfferings(zoneId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_IMPORT_OFFERING, eventDescription = "importing backup offering", async = true)
public BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd) {
validateForZone(cmd.getZoneId());
final BackupOffering existingOffering = backupOfferingDao.findByExternalId(cmd.getExternalId(), cmd.getZoneId());
if (existingOffering != null) {
throw new CloudRuntimeException("A backup offering with external ID " + cmd.getExternalId() + " already exists");
}
if (backupOfferingDao.findByName(cmd.getName(), cmd.getZoneId()) != null) {
throw new CloudRuntimeException("A backup offering with the same name already exists in this zone");
}
final BackupProvider provider = getBackupProvider(cmd.getZoneId());
if (!provider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())) {
throw new CloudRuntimeException("Backup offering '" + cmd.getExternalId() + "' does not exist on provider " + provider.getName() + " on zone " + cmd.getZoneId());
}
final BackupOfferingVO offering = new BackupOfferingVO(cmd.getZoneId(), cmd.getExternalId(), provider.getName(),
cmd.getName(), cmd.getDescription(), cmd.getUserDrivenBackups());
final BackupOfferingVO savedOffering = backupOfferingDao.persist(offering);
if (savedOffering == null) {
throw new CloudRuntimeException("Unable to create backup offering: " + cmd.getExternalId() + ", name: " + cmd.getName());
}
LOG.debug("Successfully created backup offering " + cmd.getName() + " mapped to backup provider offering " + cmd.getExternalId());
return savedOffering;
}
@Override
public Pair<List<BackupOffering>, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd) {
final Long offeringId = cmd.getOfferingId();
final Long zoneId = cmd.getZoneId();
final String keyword = cmd.getKeyword();
if (offeringId != null) {
BackupOfferingVO offering = backupOfferingDao.findById(offeringId);
if (offering == null) {
throw new CloudRuntimeException("Offering ID " + offeringId + " does not exist");
}
return new Pair<>(Collections.singletonList(offering), 1);
}
final Filter searchFilter = new Filter(BackupOfferingVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
SearchBuilder<BackupOfferingVO> sb = backupOfferingDao.createSearchBuilder();
sb.and("zone_id", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
final SearchCriteria<BackupOfferingVO> sc = sb.create();
if (zoneId != null) {
sc.setParameters("zone_id", zoneId);
}
if (keyword != null) {
sc.setParameters("name", "%" + keyword + "%");
}
Pair<List<BackupOfferingVO>, Integer> result = backupOfferingDao.searchAndCount(sc, searchFilter);
return new Pair<>(new ArrayList<>(result.first()), result.second());
}
@Override
public boolean deleteBackupOffering(final Long offeringId) {
final BackupOfferingVO offering = backupOfferingDao.findById(offeringId);
if (offering == null) {
throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId);
}
if (vmInstanceDao.listByZoneWithBackups(offering.getZoneId(), offering.getId()).size() > 0) {
throw new CloudRuntimeException("Backup offering is assigned to VMs, remove the assignment(s) in order to remove the offering.");
}
validateForZone(offering.getZoneId());
return backupOfferingDao.remove(offering.getId());
}
private String createVolumeInfoFromVolumes(List<VolumeVO> vmVolumes) {
List<Backup.VolumeInfo> list = new ArrayList<>();
for (VolumeVO vol : vmVolumes) {
list.add(new Backup.VolumeInfo(vol.getUuid(), vol.getPath(), vol.getVolumeType(), vol.getSize()));
}
return new Gson().toJson(list.toArray(), Backup.VolumeInfo[].class);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true)
public boolean assignVMToBackupOffering(Long vmId, Long offeringId) {
final VMInstanceVO vm = findVmById(vmId);
if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped, VirtualMachine.State.Shutdown).contains(vm.getState())) {
throw new CloudRuntimeException("VM is not in running or stopped state");
}
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getBackupOfferingId() != null) {
throw new CloudRuntimeException("VM already is assigned to a backup offering, please remove the VM from its previous offering");
}
final BackupOfferingVO offering = backupOfferingDao.findById(offeringId);
if (offering == null) {
throw new CloudRuntimeException("Provided backup offering does not exist");
}
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
if (backupProvider == null) {
throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator");
}
return transactionAssignVMToBackupOffering(vm, offering, backupProvider) != null;
}
private VMInstanceVO transactionAssignVMToBackupOffering(VMInstanceVO vm, BackupOfferingVO offering, BackupProvider backupProvider) {
return Transaction.execute(TransactionLegacy.CLOUD_DB, new TransactionCallback<VMInstanceVO>() {
@Override
public VMInstanceVO doInTransaction(final TransactionStatus status) {
try {
long vmId = vm.getId();
vm.setBackupOfferingId(offering.getId());
vm.setBackupVolumes(createVolumeInfoFromVolumes(volumeDao.findByInstance(vmId)));
if (!backupProvider.assignVMToBackupOffering(vm, offering)) {
throw new CloudRuntimeException("Failed to assign the VM to the backup offering, please try removing the assignment and try again.");
}
if (!vmInstanceDao.update(vmId, vm)) {
backupProvider.removeVMFromBackupOffering(vm);
throw new CloudRuntimeException("Failed to update VM assignment to the backup offering in the DB, please try again.");
}
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vmId,
"Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid());
LOG.debug(String.format("VM [%s] successfully added to Backup Offering [%s].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm,
"uuid", "instanceName", "backupOfferingId", "backupVolumes"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering,
"uuid", "name", "externalId", "provider")));
} catch (Exception e) {
String msg = String.format("Failed to assign VM [%s] to the Backup Offering [%s], using provider [name: %s, class: %s], due to: [%s].",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm, "uuid", "instanceName", "backupOfferingId", "backupVolumes"),
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering, "uuid", "name", "externalId", "provider"),
backupProvider.getName(), backupProvider.getClass().getSimpleName(), e.getMessage());
LOG.error(msg);
LOG.debug(msg, e);
}
return vm;
}
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true)
public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) {
final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
if (vm == null) {
throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId));
}
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
final BackupOfferingVO offering = backupOfferingDao.findById(vm.getBackupOfferingId());
if (offering == null) {
throw new CloudRuntimeException("No previously configured backup offering found for the VM");
}
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
if (backupProvider == null) {
throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator");
}
if (!forced && backupProvider.willDeleteBackupsOnOfferingRemoval()) {
String message = String.format("To remove VM [id: %s, name: %s] from Backup Offering [id: %s, name: %s] using the provider [%s], please specify the "
+ "forced:true option to allow the deletion of all jobs and backups for this VM or remove the backups that this VM has with the backup "
+ "offering.", vm.getUuid(), vm.getInstanceName(), offering.getUuid(), offering.getName(), backupProvider.getClass().getSimpleName());
throw new CloudRuntimeException(message);
}
boolean result = false;
try {
vm.setBackupOfferingId(null);
vm.setBackupExternalId(null);
vm.setBackupVolumes(null);
result = backupProvider.removeVMFromBackupOffering(vm);
if (result && backupProvider.willDeleteBackupsOnOfferingRemoval()) {
final List<Backup> backups = backupDao.listByVmId(null, vm.getId());
for (final Backup backup : backups) {
backupDao.remove(backup.getId());
}
}
if ((result || forced) && vmInstanceDao.update(vm.getId(), vm)) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(),
"Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null,
Backup.class.getSimpleName(), vm.getUuid());
final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId());
if (backupSchedule != null) {
backupScheduleDao.remove(backupSchedule.getId());
}
result = true;
}
} catch (Exception e) {
LOG.error(String.format("Exception caught when trying to remove VM [uuid: %s, name: %s] from the backup offering [uuid: %s, name: %s] due to: [%s].",
vm.getUuid(), vm.getInstanceName(), offering.getUuid(), offering.getName(), e.getMessage()), e);
}
return result;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule")
public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) {
final Long vmId = cmd.getVmId();
final DateUtil.IntervalType intervalType = cmd.getIntervalType();
final String scheduleString = cmd.getSchedule();
final TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
if (intervalType == null) {
throw new CloudRuntimeException("Invalid interval type provided");
}
final VMInstanceVO vm = findVmById(vmId);
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getBackupOfferingId() == null) {
throw new CloudRuntimeException("Cannot configure backup schedule for the VM without having any backup offering");
}
final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId());
if (offering == null || !offering.isUserDrivenBackupAllowed()) {
throw new CloudRuntimeException("The selected backup offering does not allow user-defined backup schedule");
}
final String timezoneId = timeZone.getID();
if (!timezoneId.equals(cmd.getTimezone())) {
LOG.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone());
}
Date nextDateTime = null;
try {
nextDateTime = DateUtil.getNextRunTime(intervalType, cmd.getSchedule(), timezoneId, null);
} catch (Exception e) {
throw new InvalidParameterValueException("Invalid schedule: " + cmd.getSchedule() + " for interval type: " + cmd.getIntervalType());
}
final BackupScheduleVO schedule = backupScheduleDao.findByVM(vmId);
if (schedule == null) {
return backupScheduleDao.persist(new BackupScheduleVO(vmId, intervalType, scheduleString, timezoneId, nextDateTime));
}
schedule.setScheduleType((short) intervalType.ordinal());
schedule.setSchedule(scheduleString);
schedule.setTimezone(timezoneId);
schedule.setScheduledTimestamp(nextDateTime);
backupScheduleDao.update(schedule.getId(), schedule);
return backupScheduleDao.findByVM(vmId);
}
@Override
public BackupSchedule listBackupSchedule(final Long vmId) {
final VMInstanceVO vm = findVmById(vmId);
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
return backupScheduleDao.findByVM(vmId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule")
public boolean deleteBackupSchedule(final Long vmId) {
final VMInstanceVO vm = findVmById(vmId);
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
final BackupSchedule schedule = backupScheduleDao.findByVM(vmId);
if (schedule == null) {
throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything.");
}
return backupScheduleDao.remove(schedule.getId());
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true)
public boolean createBackup(final Long vmId) {
final VMInstanceVO vm = findVmById(vmId);
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getBackupOfferingId() == null) {
throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering");
}
final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId());
if (offering == null) {
throw new CloudRuntimeException("VM backup offering not found");
}
if (!offering.isUserDrivenBackupAllowed()) {
throw new CloudRuntimeException("The assigned backup offering does not allow ad-hoc user backup");
}
ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(),
EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(),
vmId, ApiCommandResourceType.VirtualMachine.toString(),
true, 0);
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
if (backupProvider != null && backupProvider.takeBackup(vm)) {
return true;
}
throw new CloudRuntimeException("Failed to create VM backup");
}
@Override
public Pair<List<Backup>, Integer> listBackups(final ListBackupsCmd cmd) {
final Long id = cmd.getId();
final Long vmId = cmd.getVmId();
final Long zoneId = cmd.getZoneId();
final Account caller = CallContext.current().getCallingAccount();
final String keyword = cmd.getKeyword();
List<Long> permittedAccounts = new ArrayList<Long>();
if (vmId != null) {
VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
if (vm != null) {
accountManager.checkAccess(caller, null, true, vm);
}
}
final Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, Project.ListProjectResourcesCriteria>(cmd.getDomainId(),
cmd.isRecursive(), null);
accountManager.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
final Long domainId = domainIdRecursiveListProject.first();
final Boolean isRecursive = domainIdRecursiveListProject.second();
final Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
final Filter searchFilter = new Filter(BackupVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
SearchBuilder<BackupVO> sb = backupDao.createSearchBuilder();
accountManager.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ);
sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
if (keyword != null) {
SearchBuilder<VMInstanceVO> vmSearch = vmInstanceDao.createSearchBuilder();
vmSearch.and("name", vmSearch.entity().getHostName(), SearchCriteria.Op.LIKE);
sb.groupBy(sb.entity().getId());
sb.join("vmSearch", vmSearch, sb.entity().getVmId(), vmSearch.entity().getId(), JoinBuilder.JoinType.INNER);
}
SearchCriteria<BackupVO> sc = sb.create();
accountManager.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
if (id != null) {
sc.setParameters("id", id);
}
if (vmId != null) {
sc.setParameters("vmId", vmId);
}
if (zoneId != null) {
sc.setParameters("zoneId", zoneId);
}
if (keyword != null) {
sc.setJoinParameters("vmSearch", "name", "%" + keyword + "%");
}
Pair<List<BackupVO>, Integer> result = backupDao.searchAndCount(sc, searchFilter);
return new Pair<>(new ArrayList<>(result.first()), result.second());
}
public boolean importRestoredVM(long zoneId, long domainId, long accountId, long userId,
String vmInternalName, Hypervisor.HypervisorType hypervisorType, Backup backup) {
VirtualMachine vm = null;
HypervisorGuru guru = hypervisorGuruManager.getGuru(hypervisorType);
try {
vm = guru.importVirtualMachineFromBackup(zoneId, domainId, accountId, userId, vmInternalName, backup);
} catch (final Exception e) {
LOG.error(String.format("Failed to import VM [vmInternalName: %s] from backup restoration [%s] with hypervisor [type: %s] due to: [%s].", vmInternalName,
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "id", "uuid", "vmId", "externalId", "backupType"), hypervisorType, e.getMessage()), e);
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
String.format("Failed to import VM %s from backup %s with hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage());
}
if (vm == null) {
String message = String.format("Failed to import restored VM %s with hypervisor type %s using backup of VM ID %s",
vmInternalName, hypervisorType, backup.getVmId());
LOG.error(message);
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
message, vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
} else {
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_RESTORE,
String.format("Restored VM %s from backup %s", vm.getUuid(), backup.getUuid()),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
}
return vm != null;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true)
public boolean restoreBackup(final Long backupId) {
final BackupVO backup = backupDao.findById(backupId);
if (backup == null) {
throw new CloudRuntimeException("Backup " + backupId + " does not exist");
}
validateForZone(backup.getZoneId());
final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
if (vm == null) {
throw new CloudRuntimeException("VM ID " + backup.getVmId() + " couldn't be found on existing or removed VMs");
}
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) &&
!vm.getState().equals(VirtualMachine.State.Destroyed)) {
throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup");
}
final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId());
if (offering == null) {
throw new CloudRuntimeException("Failed to find backup offering of the VM backup");
}
ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE,
String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),
true, 0);
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
if (!backupProvider.restoreVMFromBackup(vm, backup)) {
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid());
}
return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(),
vm.getInstanceName(), vm.getHypervisorType(), backup);
}
private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, String volumeUuid) {
for (Backup.VolumeInfo volInfo : backedUpVolumes) {
if (volInfo.getUuid().equals(volumeUuid)) {
return volInfo;
}
}
return null;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true)
public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception {
if (StringUtils.isEmpty(backedUpVolumeUuid)) {
throw new CloudRuntimeException("Invalid volume ID passed");
}
final BackupVO backup = backupDao.findById(backupId);
if (backup == null) {
throw new CloudRuntimeException("Provided backup not found");
}
validateForZone(backup.getZoneId());
final VMInstanceVO vm = findVmById(vmId);
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
if (vm.getBackupOfferingId() != null && !BackupEnableAttachDetachVolumes.value()) {
throw new CloudRuntimeException("The selected VM has backups, cannot restore and attach volume to the VM.");
}
if (backup.getZoneId() != vm.getDataCenterId()) {
throw new CloudRuntimeException("Cross zone backup restoration of volume is not allowed");
}
final VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
if (vmFromBackup == null) {
throw new CloudRuntimeException("VM reference for the provided VM backup not found");
}
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vmFromBackup);
Pair<HostVO, StoragePoolVO> restoreInfo = getRestoreVolumeHostAndDatastore(vm);
HostVO host = restoreInfo.first();
StoragePoolVO datastore = restoreInfo.second();
LOG.debug("Asking provider to restore volume " + backedUpVolumeUuid + " from backup " + backupId +
" (with external ID " + backup.getExternalId() + ") and attach it to VM: " + vm.getUuid());
final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId());
if (offering == null) {
throw new CloudRuntimeException("Failed to find VM backup offering");
}
BackupProvider backupProvider = getBackupProvider(offering.getProvider());
LOG.debug(String.format("Trying to restore volume using host private IP address: [%s].", host.getPrivateIpAddress()));
String[] hostPossibleValues = {host.getPrivateIpAddress(), host.getName()};
String[] datastoresPossibleValues = {datastore.getUuid(), datastore.getName()};
Pair<Boolean, String> result = restoreBackedUpVolume(backedUpVolumeUuid, backup, backupProvider, hostPossibleValues, datastoresPossibleValues);
if (BooleanUtils.isFalse(result.first())) {
throw new CloudRuntimeException(String.format("Error restoring volume [%s] of VM [%s] to host [%s] using backup provider [%s] due to: [%s].",
backedUpVolumeUuid, vm.getUuid(), host.getUuid(), backupProvider.getName(), result.second()));
}
if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), vmFromBackup.getBackupVolumeList(),
backedUpVolumeUuid, vm, datastore.getUuid(), backup)) {
throw new CloudRuntimeException(String.format("Error attaching volume [%s] to VM [%s]." + backedUpVolumeUuid, vm.getUuid()));
}
return true;
}
protected Pair<Boolean, String> restoreBackedUpVolume(final String backedUpVolumeUuid, final BackupVO backup, BackupProvider backupProvider, String[] hostPossibleValues,
String[] datastoresPossibleValues) {
Pair<Boolean, String> result = new Pair<>(false, "");
for (String hostData : hostPossibleValues) {
for (String datastoreData : datastoresPossibleValues) {
LOG.debug(String.format("Trying to restore volume [UUID: %s], using host [%s] and datastore [%s].",
backedUpVolumeUuid, hostData, datastoreData));
try {
result = backupProvider.restoreBackedUpVolume(backup, backedUpVolumeUuid, hostData, datastoreData);
if (BooleanUtils.isTrue(result.first())) {
return result;
}
} catch (Exception e) {
LOG.debug(String.format("Failed to restore volume [UUID: %s], using host [%s] and datastore [%s] due to: [%s].",
backedUpVolumeUuid, hostData, datastoreData, e.getMessage()), e);
}
}
}
return result;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true)
public boolean deleteBackup(final Long backupId, final Boolean forced) {
final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId);
if (backup == null) {
throw new CloudRuntimeException("Backup " + backupId + " does not exist");
}
final Long vmId = backup.getVmId();
final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
if (vm == null) {
throw new CloudRuntimeException("VM " + vmId + " does not exist");
}
validateForZone(vm.getDataCenterId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId());
if (offering == null) {
throw new CloudRuntimeException(String.format("Backup offering with ID [%s] does not exist.", backup.getBackupOfferingId()));
}
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
boolean result = backupProvider.deleteBackup(backup, forced);
if (result) {
return backupDao.remove(backup.getId());
}
throw new CloudRuntimeException("Failed to delete the backup");
}
/**
* Get the pair: hostIp, datastoreUuid in which to restore the volume, based on the VM to be attached information
*/
private Pair<HostVO, StoragePoolVO> getRestoreVolumeHostAndDatastore(VMInstanceVO vm) {
List<VolumeVO> rootVmVolume = volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), Volume.Type.ROOT);
Long poolId = rootVmVolume.get(0).getPoolId();
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(poolId);
HostVO hostVO = vm.getHostId() == null ?
getFirstHostFromStoragePool(storagePoolVO) :
hostDao.findById(vm.getHostId());
return new Pair<>(hostVO, storagePoolVO);
}
/**
* Find a host from storage pool access
*/
private HostVO getFirstHostFromStoragePool(StoragePoolVO storagePoolVO) {
List<HostVO> hosts = null;
if (storagePoolVO.getScope().equals(ScopeType.CLUSTER)) {
hosts = hostDao.findByClusterId(storagePoolVO.getClusterId());
} else if (storagePoolVO.getScope().equals(ScopeType.ZONE)) {
hosts = hostDao.findByDataCenterId(storagePoolVO.getDataCenterId());
}
return hosts.get(0);
}
/**
* Attach volume to VM
*/
private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, List<Backup.VolumeInfo> backedUpVolumes,
String volumeUuid, VMInstanceVO vm, String datastoreUuid, Backup backup) throws Exception {
HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType());
Backup.VolumeInfo volumeInfo = getVolumeInfo(backedUpVolumes, volumeUuid);
if (volumeInfo == null) {
throw new CloudRuntimeException("Failed to find volume in the backedup volumes of ID " + volumeUuid);
}
volumeInfo.setType(Volume.Type.DATADISK);
LOG.debug("Attaching the restored volume to VM " + vm.getId());
StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid);
try {
return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, volumeInfo, vm, pool.getId(), backup);
} catch (Exception e) {
throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage());
}
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
super.configure(name, params);
backgroundPollManager.submitTask(new BackupSyncTask(this));
return true;
}
public boolean isDisabled(final Long zoneId) {
return !(BackupFrameworkEnabled.value() && BackupFrameworkEnabled.valueIn(zoneId));
}
private void validateForZone(final Long zoneId) {
if (zoneId == null || isDisabled(zoneId)) {
throw new CloudRuntimeException("Backup and Recovery feature is disabled for the zone");
}
}
@Override
public List<BackupProvider> listBackupProviders() {
return backupProviders;
}
@Override
public BackupProvider getBackupProvider(final Long zoneId) {
final String name = BackupProviderPlugin.valueIn(zoneId);
return getBackupProvider(name);
}
public BackupProvider getBackupProvider(final String name) {
if (StringUtils.isEmpty(name)) {
throw new CloudRuntimeException("Invalid backup provider name provided");
}
if (!backupProvidersMap.containsKey(name)) {
throw new CloudRuntimeException("Failed to find backup provider by the name: " + name);
}
return backupProvidersMap.get(name);
}
@Override
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<Class<?>>();
if (!BackupFrameworkEnabled.value()) {
return cmdList;
}
// Offerings
cmdList.add(ListBackupProvidersCmd.class);
cmdList.add(ListBackupProviderOfferingsCmd.class);
cmdList.add(ImportBackupOfferingCmd.class);
cmdList.add(ListBackupOfferingsCmd.class);
cmdList.add(DeleteBackupOfferingCmd.class);
cmdList.add(UpdateBackupOfferingCmd.class);
// Assignment
cmdList.add(AssignVirtualMachineToBackupOfferingCmd.class);
cmdList.add(RemoveVirtualMachineFromBackupOfferingCmd.class);
// Schedule
cmdList.add(CreateBackupScheduleCmd.class);
cmdList.add(UpdateBackupScheduleCmd.class);
cmdList.add(ListBackupScheduleCmd.class);
cmdList.add(DeleteBackupScheduleCmd.class);
// Operations
cmdList.add(CreateBackupCmd.class);
cmdList.add(ListBackupsCmd.class);
cmdList.add(RestoreBackupCmd.class);
cmdList.add(DeleteBackupCmd.class);
cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class);
return cmdList;
}
@Override
public String getConfigComponentName() {
return BackupService.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[]{
BackupFrameworkEnabled,
BackupProviderPlugin,
BackupSyncPollingInterval,
BackupEnableAttachDetachVolumes
};
}
public void setBackupProviders(final List<BackupProvider> backupProviders) {
this.backupProviders = backupProviders;
}
private void initializeBackupProviderMap() {
if (backupProviders != null) {
for (final BackupProvider backupProvider : backupProviders) {
backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider);
}
}
}
public void poll(final Date timestamp) {
currentTimestamp = timestamp;
GlobalLock scanLock = GlobalLock.getInternLock("backup.poll");
try {
if (scanLock.lock(5)) {
try {
checkStatusOfCurrentlyExecutingBackups();
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
scanLock = GlobalLock.getInternLock("backup.poll");
try {
if (scanLock.lock(5)) {
try {
scheduleBackups();
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
}
@DB
private Date scheduleNextBackupJob(final BackupScheduleVO backupSchedule) {
final Date nextTimestamp = DateUtil.getNextRunTime(backupSchedule.getScheduleType(), backupSchedule.getSchedule(),
backupSchedule.getTimezone(), currentTimestamp);
return Transaction.execute(new TransactionCallback<Date>() {
@Override
public Date doInTransaction(TransactionStatus status) {
backupSchedule.setScheduledTimestamp(nextTimestamp);
backupSchedule.setAsyncJobId(null);
backupScheduleDao.update(backupSchedule.getId(), backupSchedule);
return nextTimestamp;
}
});
}
private void checkStatusOfCurrentlyExecutingBackups() {
final SearchCriteria<BackupScheduleVO> sc = backupScheduleDao.createSearchCriteria();
sc.addAnd("asyncJobId", SearchCriteria.Op.NNULL);
final List<BackupScheduleVO> backupSchedules = backupScheduleDao.search(sc, null);
for (final BackupScheduleVO backupSchedule : backupSchedules) {
final Long asyncJobId = backupSchedule.getAsyncJobId();
final AsyncJobVO asyncJob = asyncJobManager.getAsyncJob(asyncJobId);
switch (asyncJob.getStatus()) {
case SUCCEEDED:
case FAILED:
final Date nextDateTime = scheduleNextBackupJob(backupSchedule);
final String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextDateTime);
LOG.debug("Next backup scheduled time for VM ID " + backupSchedule.getVmId() + " is " + nextScheduledTime);
break;
default:
LOG.debug(String.format("Found async backup job [id: %s, vmId: %s] with status [%s] and cmd information: [cmd: %s, cmdInfo: %s].", asyncJob.getId(), backupSchedule.getVmId(),
asyncJob.getStatus(), asyncJob.getCmd(), asyncJob.getCmdInfo()));
break;
}
}
}
@DB
public void scheduleBackups() {
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
LOG.debug("Backup backup.poll is being called at " + displayTime);
final List<BackupScheduleVO> backupsToBeExecuted = backupScheduleDao.getSchedulesToExecute(currentTimestamp);
for (final BackupScheduleVO backupSchedule: backupsToBeExecuted) {
final Long backupScheduleId = backupSchedule.getId();
final Long vmId = backupSchedule.getVmId();
final VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null || vm.getBackupOfferingId() == null) {
backupScheduleDao.remove(backupScheduleId);
continue;
}
final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId());
if (offering == null || !offering.isUserDrivenBackupAllowed()) {
continue;
}
if (isDisabled(vm.getDataCenterId())) {
continue;
}
final Account backupAccount = accountService.getAccount(vm.getAccountId());
if (backupAccount == null || backupAccount.getState() == Account.State.DISABLED) {
LOG.debug(String.format("Skip backup for VM [uuid: %s, name: %s] since its account has been removed or disabled.", vm.getUuid(), vm.getInstanceName()));
continue;
}
if (LOG.isDebugEnabled()) {
final Date scheduledTimestamp = backupSchedule.getScheduledTimestamp();
displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
LOG.debug(String.format("Scheduling 1 backup for VM [ID: %s, name: %s, hostName: %s] for backup schedule id: [%s] at [%s].",
vm.getId(), vm.getInstanceName(), vm.getHostName(), backupSchedule.getId(), displayTime));
}
BackupScheduleVO tmpBackupScheduleVO = null;
try {
tmpBackupScheduleVO = backupScheduleDao.acquireInLockTable(backupScheduleId);
final Long eventId = ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(),
EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(),
vmId, ApiCommandResourceType.VirtualMachine.toString(),
true, 0);
final Map<String, String> params = new HashMap<String, String>();
params.put(ApiConstants.VIRTUAL_MACHINE_ID, "" + vmId);
params.put("ctxUserId", "1");
params.put("ctxAccountId", "" + vm.getAccountId());
params.put("ctxStartEventId", String.valueOf(eventId));
final CreateBackupCmd cmd = new CreateBackupCmd();
ComponentContext.inject(cmd);
apiDispatcher.dispatchCreateCmd(cmd, params);
params.put("id", "" + vmId);
params.put("ctxStartEventId", "1");
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), CreateBackupCmd.class.getName(),
ApiGsonHelper.getBuilder().create().toJson(params), vmId,
cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
job.setDispatcher(asyncJobDispatcher.getName());
final long jobId = asyncJobManager.submitAsyncJob(job);
tmpBackupScheduleVO.setAsyncJobId(jobId);
backupScheduleDao.update(backupScheduleId, tmpBackupScheduleVO);
} catch (Exception e) {
LOG.error(String.format("Scheduling backup failed due to: [%s].", e.getMessage()), e);
} finally {
if (tmpBackupScheduleVO != null) {
backupScheduleDao.releaseFromLockTable(backupScheduleId);
}
}
}
}
@Override
public boolean start() {
initializeBackupProviderMap();
currentTimestamp = new Date();
for (final BackupScheduleVO backupSchedule : backupScheduleDao.listAll()) {
scheduleNextBackupJob(backupSchedule);
}
final TimerTask backupPollTask = new ManagedContextTimerTask() {
@Override
protected void runInContext() {
try {
poll(new Date());
} catch (final Throwable t) {
LOG.warn("Catch throwable in backup scheduler ", t);
}
}
};
backupTimer = new Timer("BackupPollTask");
backupTimer.schedule(backupPollTask, BackupSyncPollingInterval.value() * 1000L, BackupSyncPollingInterval.value() * 1000L);
return true;
}
private VMInstanceVO findVmById(final Long vmId) {
final VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null) {
throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId));
}
return vm;
}
////////////////////////////////////////////////////
/////////////// Background Tasks ///////////////////
////////////////////////////////////////////////////
/**
* This background task syncs backups from providers side in CloudStack db
* along with creation of usage records
*/
private final class BackupSyncTask extends ManagedContextRunnable implements BackgroundPollTask {
private BackupManager backupManager;
public BackupSyncTask(final BackupManager backupManager) {
this.backupManager = backupManager;
}
@Override
protected void runInContext() {
try {
if (LOG.isTraceEnabled()) {
LOG.trace("Backup sync background task is running...");
}
for (final DataCenter dataCenter : dataCenterDao.listAllZones()) {
if (dataCenter == null || isDisabled(dataCenter.getId())) {
LOG.debug(String.format("Backup Sync Task is not enabled in zone [%s]. Skipping this zone!", dataCenter == null ? "NULL Zone!" : dataCenter.getId()));
continue;
}
final BackupProvider backupProvider = getBackupProvider(dataCenter.getId());
if (backupProvider == null) {
LOG.warn("Backup provider not available or configured for zone ID " + dataCenter.getId());
continue;
}
List<VMInstanceVO> vms = vmInstanceDao.listByZoneWithBackups(dataCenter.getId(), null);
if (vms == null || vms.isEmpty()) {
LOG.debug(String.format("Can't find any VM to sync backups in zone [id: %s].", dataCenter.getId()));
continue;
}
final Map<VirtualMachine, Backup.Metric> metrics = backupProvider.getBackupMetrics(dataCenter.getId(), new ArrayList<>(vms));
try {
for (final VirtualMachine vm : metrics.keySet()) {
final Backup.Metric metric = metrics.get(vm);
if (metric != null) {
// Sync out-of-band backups
backupProvider.syncBackups(vm, metric);
// Emit a usage event, update usage metric for the VM by the usage server
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_USAGE_METRIC, vm.getAccountId(),
vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(),
vm.getBackupOfferingId(), null, metric.getBackupSize(), metric.getDataSize(),
Backup.class.getSimpleName(), vm.getUuid());
}
}
} catch (final Throwable e) {
LOG.error(String.format("Failed to sync backup usage metrics and out-of-band backups due to: [%s].", e.getMessage()), e);
}
}
} catch (final Throwable t) {
LOG.error(String.format("Error trying to run backup-sync background task due to: [%s].", t.getMessage()), t);
}
}
@Override
public Long getDelay() {
return BackupSyncPollingInterval.value() * 1000L;
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_EDIT, eventDescription = "updating backup offering")
public BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupOfferingCmd) {
Long id = updateBackupOfferingCmd.getId();
String name = updateBackupOfferingCmd.getName();
String description = updateBackupOfferingCmd.getDescription();
Boolean allowUserDrivenBackups = updateBackupOfferingCmd.getAllowUserDrivenBackups();
BackupOfferingVO backupOfferingVO = backupOfferingDao.findById(id);
if (backupOfferingVO == null) {
throw new InvalidParameterValueException(String.format("Unable to find Backup Offering with id: [%s].", id));
}
LOG.debug(String.format("Trying to update Backup Offering %s to %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backupOfferingVO,"uuid", "name",
"description", "userDrivenBackupAllowed"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this,"name", "description", "allowUserDrivenBackups")));
BackupOfferingVO offering = backupOfferingDao.createForUpdate(id);
List<String> fields = new ArrayList<>();
if (name != null) {
offering.setName(name);
fields.add("name: " + name);
}
if (description != null) {
offering.setDescription(description);
fields.add("description: " + description);
}
if (allowUserDrivenBackups != null){
offering.setUserDrivenBackupAllowed(allowUserDrivenBackups);
fields.add("allowUserDrivenBackups: " + allowUserDrivenBackups);
}
if (!backupOfferingDao.update(id, offering)) {
LOG.warn(String.format("Couldn't update Backup offering [id: %s] with [%s].", id, String.join(", ", fields)));
}
BackupOfferingVO response = backupOfferingDao.findById(id);
CallContext.current().setEventDetails(String.format("Backup Offering updated [%s].",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(response, "id", "name", "description", "userDrivenBackupAllowed", "externalId")));
return response;
}
}