package org.ovirt.engine.core.bll; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.hostedengine.HostedEngineHelper; import org.ovirt.engine.core.bll.job.ExecutionContext; import org.ovirt.engine.core.bll.job.ExecutionHandler; import org.ovirt.engine.core.bll.scheduling.SchedulingManager; import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.MaintenanceVdsParameters; import org.ovirt.engine.core.common.action.MigrateVmParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.HaMaintenanceMode; 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.comparators.VmsComparer; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.job.Step; import org.ovirt.engine.core.common.job.StepEnum; import org.ovirt.engine.core.common.vdscommands.SetHaMaintenanceModeVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dal.job.ExecutionMessageDirector; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.di.Injector; @NonTransactiveCommandAttribute public class MaintenanceVdsCommand<T extends MaintenanceVdsParameters> extends VdsCommand<T> { private List<VM> vms; private boolean haMaintenanceFailed; @Inject private SchedulingManager schedulingManager; @Inject private VdsDao vdsDao; @Inject private VmDao vmDao; public MaintenanceVdsCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); haMaintenanceFailed = false; } @Override protected void executeCommand() { if (getVds().getStatus() == VDSStatus.Maintenance) { // nothing to do setSucceeded(true); } else { orderListOfRunningVmsOnVds(getVdsId()); if (getVds().getHighlyAvailableIsConfigured()) { SetHaMaintenanceModeVDSCommandParameters params = new SetHaMaintenanceModeVDSCommandParameters(getVds(), HaMaintenanceMode.LOCAL, true); if (!runVdsCommand(VDSCommandType.SetHaMaintenanceMode, params).getSucceeded()) { haMaintenanceFailed = true; // HA maintenance failure is fatal only if the Hosted Engine vm is running on this host if (isHostedEngineOnVds()) { setSucceeded(false); return; } } } setSucceeded(migrateAllVms(getExecutionContext())); // if non responsive move directly to maintenance if (getVds().getStatus() == VDSStatus.NonResponsive || getVds().getStatus() == VDSStatus.Connecting || getVds().getStatus() == VDSStatus.Down) { setVdsStatus(VDSStatus.Maintenance); } } // if there's VM(s) in this VDS which is migrating, mark this command as async // as the migration(s) is a step of this job, so this job must not be cleaned yet if (isVmsExist()) { ExecutionHandler.setAsyncJob(getExecutionContext(), true); } } protected boolean isVmsExist() { return vms != null && !vms.isEmpty(); } protected void orderListOfRunningVmsOnVds(Guid vdsId) { vms = vmDao.getAllRunningForVds(vdsId); vms.sort(new VmsComparer().reversed()); } /** * Note: you must call {@link #orderListOfRunningVmsOnVds(Guid)} before calling this method */ protected boolean migrateAllVms(ExecutionContext parentContext) { return migrateAllVms(parentContext, false); } private boolean canScheduleVm(VM vm) { List<Guid> blacklist = new ArrayList<>(); if (getVdsId() != null) { blacklist.add(getVdsId()); } return schedulingManager.canSchedule( getCluster(), vm, blacklist, //blacklist only contains the host we're putting to maintenance Collections.emptyList(), //no whitelist new ArrayList<>() ); } /** * Note: you must call {@link #orderListOfRunningVmsOnVds(Guid)} before calling this method */ protected boolean migrateAllVms(ExecutionContext parentContext, boolean HAOnly) { boolean succeeded = true; for (VM vm : vms) { if (vm.isHostedEngine()) { // check if there is host which can be used for HE if (!canScheduleVm(vm)) { succeeded = false; appendCustomCommaSeparatedValue("failedVms", vm.getName()); log.error("There is no host capable of running the hosted engine VM"); } // The Hosted Engine vm is migrated by the HA agent continue; } // if HAOnly is true check that vm is HA (auto_startup should be true) if (vm.getStatus() != VMStatus.MigratingFrom && (!HAOnly || vm.isAutoStartup())) { if (!migrateVm(vm, parentContext)) { succeeded = false; appendCustomCommaSeparatedValue("failedVms", vm.getName()); log.error("Failed to migrate VM '{}'", vm.getName()); } } } return succeeded; } protected boolean migrateVm(VM vm, ExecutionContext parentContext) { MigrateVmParameters parameters = new MigrateVmParameters(false, vm.getId()); parameters.setReason(AuditLogDirector.getMessage(AuditLogType.MIGRATION_REASON_HOST_IN_MAINTENANCE)); return runInternalAction(VdcActionType.MigrateVm, parameters, createMigrateVmContext(parentContext, vm)) .getSucceeded(); } protected CommandContext createMigrateVmContext(ExecutionContext parentContext, VM vm) { ExecutionContext ctx = new ExecutionContext(); try { Map<String, String> values = new HashMap<>(); values.put(VdcObjectType.VM.name().toLowerCase(), vm.getName()); values.put(VdcObjectType.VDS.name().toLowerCase(), vm.getRunOnVdsName()); Step step = executionHandler.addSubStep(getExecutionContext(), parentContext.getJob().getStep(StepEnum.EXECUTING), StepEnum.MIGRATE_VM, ExecutionMessageDirector.resolveStepMessage(StepEnum.MIGRATE_VM, values)); ctx.setJob(parentContext.getJob()); ctx.setStep(step); ctx.setMonitored(true); } catch (RuntimeException e) { log.error("Failed to create ExecutionContext for MigrateVmCommand", e); } return cloneContextAndDetachFromParent().withExecutionContext(ctx); } @Override protected boolean validate() { return canMaintenanceVds(getVdsId(), getReturnValue().getValidationMessages()); } @Override public AuditLogType getAuditLogTypeValue() { if (getParameters().isInternal()) { if (isSucceededWithHA()) { return AuditLogType.VDS_MAINTENANCE; } else if (getSucceeded()) { return AuditLogType.VDS_MAINTENANCE_MANUAL_HA; } else { return AuditLogType.VDS_MAINTENANCE_FAILED; } } else { if (isSucceededWithReasonGiven()){ addCustomValue("Reason", getVds().getMaintenanceReason()); return AuditLogType.USER_VDS_MAINTENANCE; } else if(isSucceededWithoutReasonGiven()) { return AuditLogType.USER_VDS_MAINTENANCE_WITHOUT_REASON; } else if (getSucceeded()) { return AuditLogType.USER_VDS_MAINTENANCE_MANUAL_HA; } else { return AuditLogType.USER_VDS_MAINTENANCE_MIGRATION_FAILED; } } } public boolean canMaintenanceVds(Guid vdsId, ArrayList<String> reasons) { boolean returnValue = true; // VDS vds = ResourceManager.Instance.getVds(vdsId); VDS vds = vdsDao.get(vdsId); // we can get here when vds status was set already to Maintenance if (vds.getStatus() != VDSStatus.Maintenance && vds.getStatus() != VDSStatus.NonResponsive && vds.getStatus() != VDSStatus.Up && vds.getStatus() != VDSStatus.Error && vds.getStatus() != VDSStatus.PreparingForMaintenance && vds.getStatus() != VDSStatus.Down && vds.getStatus() != VDSStatus.InstallFailed) { returnValue = false; reasons.add(EngineMessage.VDS_CANNOT_MAINTENANCE_VDS_IS_NOT_OPERATIONAL.toString()); } orderListOfRunningVmsOnVds(vdsId); for (VM vm : vms) { if (vm.isHostedEngine()) { // Check if there are available Hosted Engine hosts for that VM if (!HostedEngineHelper.haveHostsAvailableforHE( vdsDao.getAllForClusterWithStatus(vds.getClusterId(), VDSStatus.Up), Collections.singletonList(vds.getId()))) { reasons.add(EngineMessage.VDS_CANNOT_MAINTENANCE_NO_ALTERNATE_HOST_FOR_HOSTED_ENGINE.toString()); return false; } // The Hosted Engine vm is migrated by the HA agent continue; } if (vm.getMigrationSupport() != MigrationSupport.MIGRATABLE) { reasons.add(EngineMessage.VDS_CANNOT_MAINTENANCE_IT_INCLUDES_NON_MIGRATABLE_VM.toString()); return false; } } return returnValue; } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = Collections.singletonMap(VdcObjectType.VDS.name().toLowerCase(), getVdsName()); } return jobProperties; } private boolean isHostedEngineOnVds() { return vms.stream().anyMatch(VM::isHostedEngine); } @Override public CommandCallback getCallback() { if (getVds().getClusterSupportsGlusterService() && getParameters().isStopGlusterService()) { return Injector.injectMembers(new HostMaintenanceCallback()); } else { return super.getCallback(); } } private boolean isSucceededWithHA() { return getSucceeded() && !haMaintenanceFailed; } private boolean isSucceededWithReasonGiven(){ return isSucceededWithHA() && StringUtils.isNotEmpty(getVds().getMaintenanceReason()); } private boolean isSucceededWithoutReasonGiven(){ return isSucceededWithHA() && !haMaintenanceFailed && StringUtils.isEmpty(getVds().getMaintenanceReason()); } }