package org.ovirt.engine.core.bll; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DurationFormatUtils; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.job.ExecutionHandler; import org.ovirt.engine.core.bll.migration.ConvergenceConfigProvider; import org.ovirt.engine.core.bll.migration.ConvergenceSchedule; import org.ovirt.engine.core.bll.scheduling.VdsFreeMemoryChecker; import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.MultipleVmsValidator; import org.ovirt.engine.core.bll.validator.VmValidator; import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.FeatureSupported; import org.ovirt.engine.core.common.action.ActivateDeactivateVmNicParameters; import org.ovirt.engine.core.common.action.ChangeVMClusterParameters; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.MigrateVmParameters; import org.ovirt.engine.core.common.action.PlugAction; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.MigrationMethod; import org.ovirt.engine.core.common.businessentities.MigrationSupport; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmPauseStatus; import org.ovirt.engine.core.common.businessentities.network.HostNetworkQos; import org.ovirt.engine.core.common.businessentities.network.InterfaceStatus; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.NetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.migration.MigrationPolicy; import org.ovirt.engine.core.common.migration.NoMigrationPolicy; import org.ovirt.engine.core.common.utils.NetworkCommonUtils; import org.ovirt.engine.core.common.utils.ObjectUtils; import org.ovirt.engine.core.common.vdscommands.MigrateStatusVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.MigrateVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VmDynamicDao; import org.ovirt.engine.core.dao.network.InterfaceDao; import org.ovirt.engine.core.dao.network.NetworkDao; import org.ovirt.engine.core.dao.network.VmNetworkInterfaceDao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @NonTransactiveCommandAttribute public class MigrateVmCommand<T extends MigrateVmParameters> extends RunVmCommandBase<T> { private Logger log = LoggerFactory.getLogger(MigrateVmCommand.class); @Inject ConvergenceConfigProvider convergenceConfigProvider; @Inject private VmNetworkInterfaceDao vmNetworkInterfaceDao; @Inject private InterfaceDao interfaceDao; @Inject private VmDynamicDao vmDynamicDao; @Inject private NetworkDao networkDao; @Inject private VdsDao vdsDao; @Inject private DiskDao diskDao; /** The VDS that the VM is going to migrate to */ private VDS destinationVds; /** Used to log the migration error. */ private EngineError migrationErrorCode; private Integer actualDowntime; private Object actualDowntimeLock = new Object(); private List<VmNetworkInterface> cachedVmPassthroughNics; private boolean passthroughNicsUnplugged; public MigrateVmCommand(T migrateVmParameters, CommandContext cmdContext) { super(migrateVmParameters, cmdContext); if (migrateVmParameters.getTargetClusterId() != null) { setClusterId(migrateVmParameters.getTargetClusterId()); // force reload setCluster(null); } } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Command); } /** * this property is used for audit log events */ public final String getDestinationVdsName() { VDS destinationVds = getDestinationVds(); return destinationVds != null ? destinationVds.getName() : null; } /** * @return Migration error text which is used in audit log message, if the migration status was queried from VDSM. */ @SuppressWarnings("unused") // used by AuditLogger via reflection public String getDueToMigrationError() { if (migrationErrorCode == null) { return " "; } return " due to Error: " + Backend.getInstance() .getVdsErrorsTranslator() .translateErrorTextSingle(migrationErrorCode.name(), true); } /** * Returns the VDS that the VM is about to migrate to */ protected VDS getDestinationVds() { return destinationVds; } @Override protected void processVmOnDown() { // In case the migration failed and the VM turned back to Up in the // source, we don't need to handle it as a VM that failed to run if (getVm().getStatus() != VMStatus.Up) { super.processVmOnDown(); } } private boolean initVdss() { try { setVdsIdRef(getVm().getRunOnVds()); Optional<Guid> vdsToRunOn = getVdsToRunOn(); setDestinationVdsId(vdsToRunOn.orElse(null)); if (vdsToRunOn.isPresent()) { getRunVdssList().add(vdsToRunOn.get()); } vmHandler.updateVmGuestAgentVersion(getVm()); if (!vdsToRunOn.isPresent()) { return false; } if (getDestinationVds() == null || getVds() == null) { return false; } return true; } catch (Exception e) { cleanupPassthroughVnics(getDestinationVdsId()); throw e; } } private Optional<Guid> getVdsToRunOn() { List<String> messages = new ArrayList<>(); Optional<Guid> vdsToRunOn = schedulingManager.schedule(getCluster(), getVm(), getVdsBlackList(), getVdsWhiteList(), getDestinationHostList(), messages, new VdsFreeMemoryChecker(this), getCorrelationId()); messages.forEach(this::addValidationMessage); return vdsToRunOn; } private List<Guid> getDestinationHostList() { List<Guid> destinationHostGuidList = new LinkedList<>(); if (getDestinationVdsId() != null){ destinationHostGuidList.add(getDestinationVdsId()); } return destinationHostGuidList; } @Override protected void executeVmCommand() { getVmManager().getStatistics().setMigrationProgressPercent(0); setSucceeded(initVdss() && perform()); } /** * all found passhthrough nics related to VM being migrated. We need to store them, so we can unplug them before * migration and re-plug them back after migration. */ private List<VmNetworkInterface> getAllVmPassthroughNics() { if (cachedVmPassthroughNics == null) { cachedVmPassthroughNics = vmNetworkInterfaceDao.getAllForVm(getVmId()).stream() .filter(vnic -> vnic.isPassthrough() && vnic.isPlugged()) .collect(Collectors.toList()); log.debug("Performing migration with following passthrough nics: {}", cachedVmPassthroughNics); } return cachedVmPassthroughNics; } protected boolean perform() { try { getParameters().setStartTime(new Date()); if (unplugPassthroughNics() && connectLunDisks(getDestinationVdsId()) && migrateVm()) { ExecutionHandler.setAsyncJob(getExecutionContext(), true); return true; } // otherwise runningFailed(); return false; } catch (Exception e) { runningFailed(); throw e; } } /** * When unplugging fails for any nic, we stop immediately. In that case we won't proceed with migration, and thus * it makes sense to stop asap to create minimum problems which needs to be fixed manually. * * @return false if unplugging failed. */ private boolean unplugPassthroughNics() { if (passthroughNicsUnplugged) { // no need to unplug more than once return true; } List<ActivateDeactivateVmNicParameters> parametersList = createActivateDeactivateVmNicParameters( getAllVmPassthroughNics(), PlugAction.UNPLUG); log.debug("About to call {} with parameters: {}", VdcActionType.ActivateDeactivateVmNic, Arrays.toString(parametersList.toArray())); for (ActivateDeactivateVmNicParameters parameter : parametersList) { VdcReturnValueBase returnValue = runInternalAction(VdcActionType.ActivateDeactivateVmNic, parameter); if (!returnValue.getSucceeded()) { returnValue.getValidationMessages().forEach(this::addValidationMessage); return false; } } passthroughNicsUnplugged = true; return true; } /** * This method is called when migration succeeded, and we're plugging vmnics back. If there some error with plugging * any of them back, we will create least problem, if we try to plug back each of them. Also for each such failure, * we must release preallocated VF. */ private void plugPassthroughNics() { try { List<ActivateDeactivateVmNicParameters> parametersList = createActivateDeactivateVmNicParameters( getAllVmPassthroughNics(), PlugAction.PLUG); List<VmNic> notRepluggedNics = replugNics(parametersList); if (!notRepluggedNics.isEmpty()) { Map<Guid, String> vnicToVfMap = getVnicToVfMap(getDestinationVdsId()); Set<String> vfsToUnregister = notRepluggedNics.stream() .map(VmNic::getId) .map(vnicToVfMap::get) .collect(Collectors.toSet()); networkDeviceHelper.setVmIdOnVfs(getDestinationVdsId(), null, vfsToUnregister); addCustomValue("NamesOfNotRepluggedNics", notRepluggedNics.stream().map(VmNic::getName).collect(Collectors.joining(","))); auditLogDirector.log(this, AuditLogType.VM_MIGRATION_NOT_ALL_VM_NICS_WERE_PLUGGED_BACK); } } catch(Exception e) { auditLogDirector.log(this, AuditLogType.VM_MIGRATION_PLUGGING_VM_NICS_FAILED); log.error("Failed to plug nics back after migration of vm {}: {}", getVmName(), e.getMessage()); log.debug("Exception: ", e); } } private List<VmNic> replugNics(List<ActivateDeactivateVmNicParameters> parametersList) { log.debug("About to call {} with parameters: {}", VdcActionType.ActivateDeactivateVmNic, Arrays.toString(parametersList.toArray())); List<VmNic> notRepluggedNics = new ArrayList<>(); for (ActivateDeactivateVmNicParameters parameter : parametersList) { VdcReturnValueBase returnValue = runInternalAction(VdcActionType.ActivateDeactivateVmNic, parameter); boolean nicPlugSucceeded = returnValue.getSucceeded(); if (!nicPlugSucceeded) { notRepluggedNics.add(parameter.getNic()); } } return notRepluggedNics; } private List<ActivateDeactivateVmNicParameters> createActivateDeactivateVmNicParameters(List<VmNetworkInterface> vmNics, PlugAction plugAction) { return vmNics .stream() .map(vmNic -> createActivateDeactivateVmNicParameters(vmNic, plugAction)) .collect(Collectors.toList()); } private ActivateDeactivateVmNicParameters createActivateDeactivateVmNicParameters(VmNic nic, PlugAction plugAction) { ActivateDeactivateVmNicParameters parameters = new ActivateDeactivateVmNicParameters(nic, plugAction, false); parameters.setVmId(getParameters().getVmId()); return parameters; } private boolean migrateVm() { setActionReturnValue(getVdsBroker() .runAsyncVdsCommand( VDSCommandType.Migrate, createMigrateVDSCommandParameters(), this) .getReturnValue()); return getActionReturnValue() == VMStatus.MigratingFrom; } private MigrateVDSCommandParameters createMigrateVDSCommandParameters() { String srcVdsHost = getVds().getHostName(); String dstVdsHost = String.format("%1$s:%2$s", getDestinationVds().getHostName(), getDestinationVds().getPort()); Map<String, Object> convergenceSchedule = null; Integer maxBandwidth = null; Boolean autoConverge = getAutoConverge(); Boolean migrateCompressed = getMigrateCompressed(); Boolean enableGuestEvents = null; Integer maxIncomingMigrations = null; Integer maxOutgoingMigrations = null; if (FeatureSupported.migrationPoliciesSupported(getVm().getCompatibilityVersion())) { MigrationPolicy clusterMigrationPolicy = convergenceConfigProvider.getMigrationPolicy( getCluster().getMigrationPolicyId(), getCluster().getCompatibilityVersion()); MigrationPolicy effectiveMigrationPolicy = findEffectiveConvergenceConfig(clusterMigrationPolicy); convergenceSchedule = ConvergenceSchedule.from(effectiveMigrationPolicy.getConfig()).asMap(); maxBandwidth = getMaxBandwidth(clusterMigrationPolicy); if (!NoMigrationPolicy.ID.equals(effectiveMigrationPolicy.getId())) { autoConverge = effectiveMigrationPolicy.isAutoConvergence(); migrateCompressed = effectiveMigrationPolicy.isMigrationCompression(); } enableGuestEvents = effectiveMigrationPolicy.isEnableGuestEvents(); maxIncomingMigrations = maxOutgoingMigrations = effectiveMigrationPolicy.getMaxMigrations(); } return new MigrateVDSCommandParameters(getVdsId(), getVmId(), srcVdsHost, getDestinationVdsId(), dstVdsHost, MigrationMethod.ONLINE, isTunnelMigrationUsed(), getLiteralMigrationNetworkIp(), getVds().getClusterCompatibilityVersion(), getMaximumMigrationDowntime(), autoConverge, migrateCompressed, getDestinationVds().getConsoleAddress(), maxBandwidth, convergenceSchedule, enableGuestEvents, maxIncomingMigrations, maxOutgoingMigrations); } /** * @return Maximum bandwidth of each migration in cluster in Mbps, `null` indicates that value in VDSM configuration * on source host should be used. * * @see org.ovirt.engine.core.common.businessentities.MigrationBandwidthLimitType */ private Integer getMaxBandwidth(MigrationPolicy migrationPolicy) { switch (getCluster().getMigrationBandwidthLimitType()) { case AUTO: return Optional.ofNullable(getAutoMaxBandwidth()) .map(bandwidth -> bandwidth / migrationPolicy.getMaxMigrations()) .orElse(null); case VDSM_CONFIG: return null; case CUSTOM: return getCluster().getCustomMigrationNetworkBandwidth() / migrationPolicy.getMaxMigrations(); default: throw new IllegalStateException( "Unexpected enum item: " + getCluster().getMigrationBandwidthLimitType()); } } private Integer getAutoMaxBandwidth() { final Guid sourceClusterId = getVm().getClusterId(); final Guid destinationClusterId = getClusterId(); final Guid sourceHostId = getVm().getRunOnVds(); final Guid destinationHostId = getDestinationVdsId(); return ObjectUtils.minIfExists( getAutoMaxBandwidth(sourceClusterId, sourceHostId), getAutoMaxBandwidth(destinationClusterId, destinationHostId)); } /** * @return `null` if it can't be computed, value in Mbps otherwise */ private Integer getAutoMaxBandwidth(Guid clusterId, Guid hostId) { Integer qosBandwidth = getQosBandwidth(clusterId); if (qosBandwidth != null) { return qosBandwidth; } return getLinkSpeedBandwidth(hostId); } /** * Note: Even if there is a QoS associated with migrational network, it may not contain neither * {@link HostNetworkQos#getOutAverageRealtime()} nor {@link HostNetworkQos#getOutAverageUpperlimit()} property. * @return `null` if it can't be obtained, value in Mbps otherwise */ private Integer getQosBandwidth(Guid clusterId) { final HostNetworkQos migrationHostNetworkQos = getDbFacade().getHostNetworkQosDao() .getHostNetworkQosOfMigrationNetworkByClusterId(clusterId); if (migrationHostNetworkQos == null) { return null; } if (migrationHostNetworkQos.getOutAverageRealtime() != null) { return migrationHostNetworkQos.getOutAverageRealtime(); } return migrationHostNetworkQos.getOutAverageUpperlimit(); } /** * Link speed of host network interface that is connected to migration network. * @return value in Mbps otherwise, `null` if it can't be computed (e.g. virtio NIC can't * report its speed) */ private Integer getLinkSpeedBandwidth(Guid hostId) { return interfaceDao.getActiveMigrationNetworkInterfaceForHost(hostId) .map(NetworkInterface::getSpeed) .map(speed -> speed > 0 ? speed : null) .orElse(null); } private MigrationPolicy findEffectiveConvergenceConfig(MigrationPolicy clusterMigrationPolicy) { Guid overriddenPolicyId = getVm().getMigrationPolicyId(); if (overriddenPolicyId == null) { return clusterMigrationPolicy; } return convergenceConfigProvider.getMigrationPolicy(overriddenPolicyId, getCluster().getCompatibilityVersion()); } @Override public void runningSucceded() { try { queryDowntime(); vmDynamicDao.clearMigratingToVds(getVmId()); updateVmAfterMigrationToDifferentCluster(); plugPassthroughNics(); } finally { super.runningSucceded(); } } @Override protected void runningFailed() { try { //this will clean all VF reservations made in {@link #initVdss}. cleanupPassthroughVnics(getDestinationVdsId()); } finally { super.runningFailed(); } } private void queryDowntime() { if (actualDowntime != null) { return; } try { VDSReturnValue retVal = runVdsCommand(VDSCommandType.MigrateStatus, new MigrateStatusVDSCommandParameters(getDestinationVdsId(), getVmId())); if (retVal != null && retVal.getReturnValue() != null) { setActualDowntime((int) retVal.getReturnValue()); } } catch (EngineException e) { migrationErrorCode = e.getErrorCode(); } } private void updateVmAfterMigrationToDifferentCluster() { if (getParameters().getTargetClusterId() == null || getVm().getClusterId().equals(getParameters().getTargetClusterId())) { return; } ChangeVMClusterParameters params = new ChangeVMClusterParameters( getParameters().getTargetClusterId(), getVmId(), getVm().getCustomCompatibilityVersion()); setSucceeded(getBackend().runInternalAction(VdcActionType.ChangeVMCluster, params).getSucceeded()); } private Boolean getAutoConverge() { if (getVm().getAutoConverge() != null) { return getVm().getAutoConverge(); } if (getCluster().getAutoConverge() != null) { return getCluster().getAutoConverge(); } return Config.getValue(ConfigValues.DefaultAutoConvergence); } private Boolean getMigrateCompressed() { if (getVm().getMigrateCompressed() != null) { return getVm().getMigrateCompressed(); } if (getCluster().getMigrateCompressed() != null) { return getCluster().getMigrateCompressed(); } return Config.getValue(ConfigValues.DefaultMigrationCompression); } private int getMaximumMigrationDowntime() { if (getVm().getMigrationDowntime() != null) { return getVm().getMigrationDowntime(); } return Config.getValue(ConfigValues.DefaultMaximumMigrationDowntime); } private boolean isTunnelMigrationUsed() { // if vm has no override for tunnel migration (its null), // use cluster's setting return getVm().getTunnelMigration() != null ? getVm().getTunnelMigration() : getCluster().isTunnelMigration(); } private String getLiteralMigrationNetworkIp() { Network migrationNetwork = null; // Find migrationNetworkCluster List<Network> allNetworksInCluster = networkDao.getAllForCluster(getVm().getClusterId()); for (Network tempNetwork : allNetworksInCluster) { if (tempNetwork.getCluster().isMigration()) { migrationNetwork = tempNetwork; break; } } if (migrationNetwork != null) { final String migrationDestinationIpv4Address = findValidMigrationIpAddress(migrationNetwork, VdsNetworkInterface::getIpv4Address); if (migrationDestinationIpv4Address != null) { return migrationDestinationIpv4Address; } final String migrationDestinationIpv6Address = findValidMigrationIpAddress(migrationNetwork, VdsNetworkInterface::getIpv6Address); if (migrationDestinationIpv6Address != null) { return formatIpv6AddressForUri(migrationDestinationIpv6Address); } } return null; } private String formatIpv6AddressForUri(String migrationDestinationIpv6Address) { if (FeatureSupported.isIpv6MigrationProperlyHandled(getCluster().getCompatibilityVersion())) { return migrationDestinationIpv6Address; } else { return String.format("[%s]", migrationDestinationIpv6Address); } } private String findValidMigrationIpAddress(Network migrationNetwork, Function<VdsNetworkInterface, String> ipAddressGetter) { // assure migration network is active on source host final String migrationSourceIpAddress = getMigrationNetworkAddress(getVds().getId(), migrationNetwork.getName(), ipAddressGetter); if (StringUtils.isNotEmpty(migrationSourceIpAddress)) { // find migration IP address on destination host final String migrationDestinationIpAddress = getMigrationNetworkAddress(getDestinationVds().getId(), migrationNetwork.getName(), ipAddressGetter); if (StringUtils.isNotEmpty(migrationDestinationIpAddress)) { return migrationDestinationIpAddress; } } return null; } private String getMigrationNetworkAddress(Guid hostId, String migrationNetworkName, Function<VdsNetworkInterface, String> ipAddressGetter) { final List<VdsNetworkInterface> nics = interfaceDao.getAllInterfacesForVds(hostId); for (VdsNetworkInterface nic : nics) { if (migrationNetworkName.equals(nic.getNetworkName()) && migrationInterfaceUp(nic, nics)) { return ipAddressGetter.apply(nic); } } return null; } private boolean migrationInterfaceUp(VdsNetworkInterface nic, List<VdsNetworkInterface> nics) { if (NetworkCommonUtils.isVlan(nic)) { String physicalNic = nic.getBaseInterface(); for (VdsNetworkInterface iface : nics) { if (iface.getName().equals(physicalNic)) { return iface.getStatistics().getStatus() == InterfaceStatus.UP; } } } return nic.getStatistics().getStatus() == InterfaceStatus.UP; } /** * command succeeded and VM is up => migration done * command succeeded and VM is not up => migration started * command failed and rerun flag is set => rerun migration was initiated * command failed and rerun flag is not set => migration failed */ @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? getActionReturnValue() == VMStatus.Up ? AuditLogType.VM_MIGRATION_DONE : getAuditLogForMigrationStarted() : _isRerun ? AuditLogType.VM_MIGRATION_TRYING_RERUN : getAuditLogForMigrationFailure(); } private AuditLogType getAuditLogForMigrationStarted() { addCustomValue("OptionalReason", getParameters().getReason()); return isInternalExecution() ? AuditLogType.VM_MIGRATION_START_SYSTEM_INITIATED : AuditLogType.VM_MIGRATION_START; } protected AuditLogType getAuditLogForMigrationFailure() { if (getVds().getStatus() == VDSStatus.PreparingForMaintenance) { return AuditLogType.VM_MIGRATION_FAILED_DURING_MOVE_TO_MAINTENANCE; } if (getDestinationVds() == null) { auditLogDirector.log(this, AuditLogType.VM_MIGRATION_NO_VDS_TO_MIGRATE_TO); } return AuditLogType.VM_MIGRATION_FAILED; } protected Guid getDestinationVdsId() { VDS destinationVds = getDestinationVds(); return destinationVds != null ? destinationVds.getId() : null; } protected void setDestinationVdsId(Guid vdsId) { destinationVds = vdsId != null ? vdsDao.get(vdsId) : null; } @Override protected boolean validate() { final VM vm = getVm(); if (vm == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND); } if (!canRunActionOnNonManagedVm()) { return false; } VmValidator vmValidator = getVmValidator(); if (!validate(vmValidator.isVmPluggedDiskNotUsingScsiReservation())) { return false; } if (!FeatureSupported.isMigrationSupported(getCluster().getArchitecture(), getCluster().getCompatibilityVersion())) { return failValidation(EngineMessage.MIGRATION_IS_NOT_SUPPORTED); } // If VM is pinned to host, no migration can occur if (vm.getMigrationSupport() == MigrationSupport.PINNED_TO_HOST) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_PINNED_TO_HOST); } if (vm.getMigrationSupport() == MigrationSupport.IMPLICITLY_NON_MIGRATABLE && !getParameters().isForceMigrationForNonMigratableVm()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NON_MIGRTABLE_AND_IS_NOT_FORCED_BY_USER_TO_MIGRATE); } switch (vm.getStatus()) { case MigratingFrom: return failValidation(EngineMessage.ACTION_TYPE_FAILED_MIGRATION_IN_PROGRESS); case NotResponding: return failVmStatusIllegal(); case Paused: if (vm.getVmPauseStatus() == VmPauseStatus.EIO) { return failValidation(EngineMessage.MIGRATE_PAUSED_EIO_VM_IS_NOT_SUPPORTED); } break; default: } if (!vm.isQualifyToMigrate()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_RUNNING); } if (!validate(new MultipleVmsValidator(vm).vmNotHavingPluggedDiskSnapshots(EngineMessage.ACTION_TYPE_FAILED_VM_HAS_PLUGGED_DISK_SNAPSHOT)) || !validate(vmValidator.allPassthroughVnicsMigratable())) { return false; } if (getParameters().getTargetClusterId() != null) { ChangeVmClusterValidator changeVmClusterValidator = ChangeVmClusterValidator.create( this, getParameters().getTargetClusterId(), getVm().getCustomCompatibilityVersion()); if (!changeVmClusterValidator.validate()) { return false; } } if (isInternalExecution() && getVm().getMigrationSupport() != MigrationSupport.MIGRATABLE) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NON_MIGRTABLE); } return validate(snapshotsValidator.vmNotDuringSnapshot(vm.getId())) // This check was added to prevent migration of VM while its disks are being migrated // TODO: replace it with a better solution && validate(new DiskImagesValidator(callFilterImageDisks(vm)).diskImagesNotLocked()) && schedulingManager.canSchedule(getCluster(), getVm(), getVdsBlackList(), getVdsWhiteList(), getReturnValue().getValidationMessages()); } @SuppressWarnings("unchecked") private List<DiskImage> callFilterImageDisks(VM vm) { return DisksFilter.filterImageDisks(diskDao.getAllForVm(vm.getId(), true), ONLY_NOT_SHAREABLE, ONLY_ACTIVE); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__MIGRATE); addValidationMessage(EngineMessage.VAR__TYPE__VM); } @Override public void rerun() { // make Vm property to null in order to refresh it from db setVm(null); determineMigrationFailureForAuditLog(); // if vm is up and rerun is called then it got up on the source, try to rerun if (getVm() != null && getVm().getStatus() == VMStatus.Up) { // this will clean all VF reservations made in {@link #initVdss}. cleanupPassthroughVnics(getDestinationVdsId()); super.rerun(); } else { // vm went down on the destination and source, migration failed. runningFailed(); // signal the caller that a rerun was made so that it won't log // the failure message again _isRerun = true; } } @Override protected void reexecuteCommand() { setDestinationVdsId(null); super.reexecuteCommand(); } /** * Log that the migration had failed with the error code that is in the VDS and needs to be retrieved. */ protected void determineMigrationFailureForAuditLog() { if (getVm() != null && getVm().getStatus() == VMStatus.Up) { try { runVdsCommand(VDSCommandType.MigrateStatus, new MigrateStatusVDSCommandParameters(getVdsId(), getVmId())); } catch (EngineException e) { migrationErrorCode = e.getErrorCode(); } } } @Override protected Guid getCurrentVdsId() { Guid destinationVdsId = getDestinationVdsId(); return destinationVdsId != null ? destinationVdsId : super.getCurrentVdsId(); } @SuppressWarnings("unused") // used by AuditLogger via reflection // Duration: time that took for the actual migration public String getDuration() { return DurationFormatUtils.formatDurationWords(new Date().getTime() - getParameters().getStartTime().getTime(), true, true); } @SuppressWarnings("unused") // used by AuditLogger via reflection // TotalDuration: time that took migration including retries (can be identical to Duration) public String getTotalDuration() { return DurationFormatUtils.formatDurationWords(new Date().getTime() - getParameters().getTotalMigrationTime().getTime(), true, true); } @SuppressWarnings("unused") // used by AuditLogger via reflection // ActualDowntime: returns the actual time that the vm was offline (not available for access) public String getActualDowntime() { return (actualDowntime == null) ? "(N/A)" : actualDowntime + "ms"; } private void setActualDowntime(int actualDowntime) { synchronized (actualDowntimeLock) { if (this.actualDowntime == null) { this.actualDowntime = actualDowntime; } } } @Override protected String getLockMessage() { return String.format("%1$s$VmName %2$s", EngineMessage.ACTION_TYPE_FAILED_VM_IS_BEING_MIGRATED.name(), getVmName()); } // hosts that cannot be selected for scheduling (failed hosts + VM source host) protected List<Guid> getVdsBlackList() { List<Guid> blackList = new ArrayList<>(getRunVdssList()); if (getVdsId() != null) { blackList.add(getVdsId()); } return blackList; } // initial hosts list picked for scheduling, currently // passed by load balancing process. protected List<Guid> getVdsWhiteList() { return getParameters().getInitialHosts() == null ? Collections.emptyList() : getParameters().getInitialHosts(); } @Override public List<PermissionSubject> getPermissionCheckSubjects() { List<PermissionSubject> permissionList = super.getPermissionCheckSubjects(); if (getParameters().getTargetClusterId() != null && getVm() != null && !Objects.equals(getParameters().getTargetClusterId(), getVm().getClusterId())) { // additional permissions needed since changing the cluster permissionList.addAll(VmHandler.getPermissionsNeededToChangeCluster(getParameters().getVmId(), getParameters().getTargetClusterId())); } return permissionList; } @Override public void onPowerringUp() { // nothing to do } @Override public void actualDowntimeReported(int actualDowntime) { setActualDowntime(actualDowntime); } protected VmValidator getVmValidator() { return new VmValidator(getVm()); } }