package org.ovirt.engine.core.bll; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.AddVdsActionParameters; import org.ovirt.engine.core.common.action.InstallVdsParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VdsActionParameters; import org.ovirt.engine.core.common.businessentities.StorageType; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VDSType; import org.ovirt.engine.core.common.businessentities.VdsDynamic; import org.ovirt.engine.core.common.businessentities.VdsStatic; import org.ovirt.engine.core.common.businessentities.VdsStatistics; import org.ovirt.engine.core.common.businessentities.storage_pool; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.VdcBLLException; import org.ovirt.engine.core.common.errors.VdcBllErrors; import org.ovirt.engine.core.common.glustercommands.GlusterHostVDSParameters; import org.ovirt.engine.core.common.utils.ValidationUtils; import org.ovirt.engine.core.common.validation.group.CreateEntity; import org.ovirt.engine.core.common.validation.group.PowerManagementCheck; import org.ovirt.engine.core.common.vdscommands.IrsBaseVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.NGuid; import org.ovirt.engine.core.compat.StringHelper; import org.ovirt.engine.core.dal.VdcBllMessages; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.CommandParametersInitializer; import org.ovirt.engine.core.utils.FileUtil; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class AddVdsCommand<T extends AddVdsActionParameters> extends VdsCommand<T> { private Boolean firstHostInStoragePool; static { CommandParametersInitializer initializer = new CommandParametersInitializer(); initializer.AddParameter(VdsStatic.class, "mVds"); } /** * Constructor for command creation when compensation is applied on startup * * @param commandId */ protected AddVdsCommand(Guid commandId) { super(commandId); } public AddVdsCommand(T parametars) { super(parametars); setVdsGroupId(parametars.getvds().getvds_group_id()); } @Override protected void executeCommand() { Guid oVirtId = getParameters().getVdsForUniqueId(); if (oVirtId != null) { // if fails to remove deprecated entry, we might attempt to add new oVirt host with an existing unique-id. if (!removeDeprecatedOvirtEntry(oVirtId)) { log.errorFormat("Failed to remove duplicated oVirt entry with id {0}. Abort adding oVirt Host type", oVirtId); throw new VdcBLLException(VdcBllErrors.HOST_ALREADY_EXISTS); } } TransactionSupport.executeInNewTransaction(new TransactionMethod<Void>() { @Override public Void runInTransaction() { AddVdsStaticToDb(); AddVdsDynamicToDb(); AddVdsStatisticsToDb(); getCompensationContext().stateChanged(); return null; } }); // set vds spm id if (getVdsGroup().getstorage_pool_id() != null) { VdsActionParameters tempVar = new VdsActionParameters(getVdsIdRef().getValue()); tempVar.setSessionId(getParameters().getSessionId()); tempVar.setCompensationEnabled(true); VdcReturnValueBase addVdsSpmIdReturn = Backend.getInstance().runInternalAction(VdcActionType.AddVdsSpmId, tempVar, getCompensationContext()); if (!addVdsSpmIdReturn.getSucceeded()) { setSucceeded(false); getReturnValue().setFault(addVdsSpmIdReturn.getFault()); return; } } TransactionSupport.executeInNewTransaction(new TransactionMethod<Void>() { @Override public Void runInTransaction() { InitializeVds(); AlertIfPowerManagementNotConfigured(getParameters().getVdsStaticData()); TestVdsPowerManagementStatus(getParameters().getVdsStaticData()); setSucceeded(true); setActionReturnValue(getVdsIdRef()); // If the installation failed, we don't want to compensate for the failure since it will remove the // host, but instead the host should be left in an "install failed" status. getCompensationContext().resetCompensation(); return null; } }); // do not install vds's which added in pending mode (currently power // clients). they are installed as part of the approve process if (Config.<Boolean> GetValue(ConfigValues.InstallVds) && !getParameters().getAddPending()) { InstallVdsParameters installVdsParameters = new InstallVdsParameters(getVdsId(), getParameters().getRootPassword()); installVdsParameters.setOverrideFirewall(getParameters().getOverrideFirewall()); Backend.getInstance().runInternalMultipleActions( VdcActionType.InstallVds, new java.util.ArrayList<VdcActionParametersBase>(java.util.Arrays .asList(new VdcActionParametersBase[] { installVdsParameters }))); } if (!isFirstHostInStoragePool()) { setSucceeded(Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.AddGlusterHost, new GlusterHostVDSParameters(getStoragePoolId().getValue(), getParameters().getVdsStaticData().gethost_name())) .getSucceeded()); } } /** * The scenario in which a host is already exists when adding new host after the canDoAction is when the existed * host type is oVirt and its status is 'Pending Approval'. In this case the old entry is removed from the DB, since * the oVirt node was added again, where the new host properties might be updated (e.g. cluster adjustment, data * center, host name, host address) and a new entry with updated properties is added. * * @param oVirtId * the deprecated host entry to remove */ private boolean removeDeprecatedOvirtEntry(final Guid oVirtId) { final VDS vds = DbFacade.getInstance().getVdsDAO().get(oVirtId); if (vds == null || !VdsHandler.isPendingOvirt(vds)) { return false; } String vdsName = getParameters().getVdsStaticData().getvds_name(); log.infoFormat("Host {0}, id {1} of type {2} is being re-registered as Host {3}", vds.getvds_name(), vds.getvds_id(), vds.getvds_type().name(), vdsName); VdcReturnValueBase result = TransactionSupport.executeInNewTransaction(new TransactionMethod<VdcReturnValueBase>() { @Override public VdcReturnValueBase runInTransaction() { return Backend.getInstance().runInternalAction(VdcActionType.RemoveVds, new VdsActionParameters(oVirtId)); } }); if (!result.getSucceeded()) { String errors = result.getCanDoAction() ? result.getFault().getError().name() : StringUtils.join(result.getCanDoActionMessages(), ","); log.warnFormat("Failed to remove Host {0}, id {1}, re-registering it as Host {2} fails with errors {3}", vds.getvds_name(), vds.getvds_id(), vdsName, errors); } else { log.infoFormat("Host {0} is now known as Host {2}", vds.getvds_name(), vdsName); } return result.getSucceeded(); } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_ADD_VDS : AuditLogType.USER_FAILED_ADD_VDS; } private void AddVdsStaticToDb() { getParameters().getVdsStaticData().setserver_SSL_enabled( Config.<Boolean> GetValue(ConfigValues.UseSecureConnectionWithServers)); DbFacade.getInstance().getVdsStaticDAO().save(getParameters().getVdsStaticData()); getCompensationContext().snapshotNewEntity(getParameters().getVdsStaticData()); setVdsIdRef(getParameters().getVdsStaticData().getId()); setVds(null); } private void AddVdsDynamicToDb() { VdsDynamic vdsDynamic = new VdsDynamic(); vdsDynamic.setId(getParameters().getVdsStaticData().getId()); // TODO: oVirt type - here oVirt behaves like power client? if (Config.<Boolean> GetValue(ConfigValues.InstallVds) && getParameters().getVdsStaticData().getvds_type() == VDSType.VDS) { vdsDynamic.setstatus(VDSStatus.Installing); } else if (getParameters().getAddPending()) { vdsDynamic.setstatus(VDSStatus.PendingApproval); } DbFacade.getInstance().getVdsDynamicDAO().save(vdsDynamic); getCompensationContext().snapshotNewEntity(vdsDynamic); } private void AddVdsStatisticsToDb() { VdsStatistics vdsStatistics = new VdsStatistics(); vdsStatistics.setId(getParameters().getVdsStaticData().getId()); DbFacade.getInstance().getVdsStatisticsDAO().save(vdsStatistics); getCompensationContext().snapshotNewEntity(vdsStatistics); } @Override protected boolean canDoAction() { boolean retrunValue = true; setVdsGroupId(getParameters().getVdsStaticData().getvds_group_id()); getParameters().setVdsForUniqueId(null); // Check if this is a valid cluster if (getVdsGroup() == null) { addCanDoActionMessage(VdcBllMessages.VDS_CLUSTER_IS_NOT_VALID); retrunValue = false; } else { VDS vds = getParameters().getvds(); String vdsName = vds.getvds_name(); String hostName = vds.gethost_name(); int maxVdsNameLength = Config.<Integer> GetValue(ConfigValues.MaxVdsNameLength); // check that vds name is not null or empty if (vdsName == null || vdsName.isEmpty()) { addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_NAME_MAY_NOT_BE_EMPTY); retrunValue = false; // check that VDS name is not too long } else if (vdsName.length() > maxVdsNameLength) { addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_NAME_LENGTH_IS_TOO_LONG); retrunValue = false; // check that VDS hostname does not contain special characters. } else if (!ValidationUtils.isVdsNameLegal(vdsName)) { addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_INVALID_VDS_NAME); retrunValue = false; } else if (!ValidationUtils.validHostname(hostName)) { addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_INVALID_VDS_HOSTNAME); retrunValue = false; } else { retrunValue = retrunValue && validateSingleHostAttachedToLocalStorage(); if (Config.<Boolean> GetValue(ConfigValues.UseSecureConnectionWithServers) && !FileUtil.fileExists(Config.resolveCertificatePath())) { addCanDoActionMessage(VdcBllMessages.VDS_TRY_CREATE_SECURE_CERTIFICATE_NOT_FOUND); retrunValue = false; } else if (!getParameters().getAddPending() && StringHelper.isNullOrEmpty(getParameters().getRootPassword())) { // We block vds installations if it's not a RHEV-H and password is empty // Note that this may override local host SSH policy. See BZ#688718. addCanDoActionMessage(VdcBllMessages.VDS_CANNOT_INSTALL_EMPTY_PASSWORD); retrunValue = false; } else if (!IsPowerManagementLegal(getParameters().getVdsStaticData(), getVdsGroup() .getcompatibility_version().toString())) { retrunValue = false; } else if (getParameters().getVdsStaticData().getport() < 1 || getParameters().getVdsStaticData().getport() > 65536) { addCanDoActionMessage(VdcBllMessages.VDS_PORT_IS_NOT_LEGAL); retrunValue = false; } else { retrunValue = retrunValue && validateHostUniqueness(vds); } } } // if this is not the first host in the storage pool, check that there is spm if (retrunValue && !isFirstHostInStoragePool()) { // check if there is SPM boolean isValid = (Boolean) Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.IsValid, new IrsBaseVDSCommandParameters(getVdsGroup().getstorage_pool_id().getValue())) .getReturnValue(); if (!isValid) { retrunValue = false; addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_NO_SPM); } } if (!retrunValue) { addCanDoActionMessage(VdcBllMessages.VAR__ACTION__ADD); addCanDoActionMessage(VdcBllMessages.VAR__TYPE__HOST); } return retrunValue; } private boolean isFirstHostInStoragePool() { if (firstHostInStoragePool == null) { NGuid storagePoolId = getVdsGroup().getstorage_pool_id(); firstHostInStoragePool = storagePoolId == null || getVdsDAO().getAllForStoragePool(storagePoolId.getValue()).isEmpty(); setStoragePoolId(storagePoolId); } return firstHostInStoragePool; } public VdsInstallHelper getVdsInstallHelper() { return new VdsInstallHelper(); } private boolean validateHostUniqueness(VDS vds) { boolean retrunValue = true; // execute the connectivity and id uniqueness validation for VDS type hosts if (vds.getvds_type() == VDSType.VDS) { VdsInstallHelper installHelper = getVdsInstallHelper(); try { Long timeout = TimeUnit.SECONDS.toMillis(Config.<Integer> GetValue(ConfigValues.ConnectToServerTimeoutInSeconds)); if (!installHelper.connectToServer(vds.gethost_name(), getParameters().getRootPassword(), timeout)) { addCanDoActionMessage(VdcBllMessages.VDS_CANNOT_CONNECT_TO_SERVER); retrunValue = false; } else { String serverUniqueId = installHelper.getServerUniqueId(); if (serverUniqueId != null) { serverUniqueId = serverUniqueId.trim(); } retrunValue = retrunValue && validateHostUniqueId(vds, serverUniqueId); } } finally { try { installHelper.wrapperShutdown(); } catch (Exception e) { log.errorFormat("Failed to terminate session with host {0} with message: {1}", vds.getvds_name(), ExceptionUtils.getMessage(e)); log.debug(e); } } } return retrunValue && validateHostUniqueNameAndAddress(getParameters().getVdsStaticData()); } private boolean validateHostUniqueNameAndAddress(VdsStatic vdsStaticData) { // having oVirt in pending approval state allows having a host with same name and address Guid vdsForUniqueId = getParameters().getVdsForUniqueId(); if (vdsForUniqueId == null) { return !VdsHandler.isVdsExist(getParameters().getVdsStaticData(), getReturnValue().getCanDoActionMessages()); } else { return !VdsHandler.isVdsExistForPendingOvirt(getParameters().getVdsStaticData(), getReturnValue().getCanDoActionMessages(), vdsForUniqueId); } } /** * Validate if same unique-id associated with the given host exists in other hosts.<br> * There should be up to one host with the same unique-id besides the new host.<br> * If host typed oVirt has the same unique id, in status PendingApproval, set that host-id<br> * on the parameters member of the command. * * @param vds * a new host to be added * @param serverUniqueId * a new host unique-id * @return true - if no host is associated with the unique-id, or if there is a single oVirt node in PendingApproval * status, else - false. */ private boolean validateHostUniqueId(VDS vds, String serverUniqueId) { boolean retrunValue = true; List<VDS> vdssByUniqueId = VdsInstallHelper.getVdssByUniqueId(vds.getvds_id(), serverUniqueId); if (!vdssByUniqueId.isEmpty()) { if (vdssByUniqueId.size() > 1) { StringBuilder sb = new StringBuilder(); sb.append(vdssByUniqueId.get(0)); for (int i = 1; i < vdssByUniqueId.size(); i++) { sb.append(", ").append(vdssByUniqueId.get(i).getvds_name()); } addCanDoActionMessage(VdcBllMessages.VDS_REGISTER_UNIQUE_ID_AMBIGUOUS); addCanDoActionMessage(String.format("$HostNameList %1$s", sb.toString())); retrunValue = false; } else { VDS existedVds = vdssByUniqueId.get(0); if (vds.getvds_type() == VDSType.VDS && existedVds.getvds_type() == VDSType.oVirtNode && (existedVds.getstatus() == VDSStatus.PendingApproval)) { getParameters().setVdsForUniqueId(existedVds.getvds_id()); } else { addCanDoActionMessage(VdcBllMessages.VDS_REGISTER_UNIQUE_ID_AMBIGUOUS); addCanDoActionMessage(String.format("$HostNameList %1$s", existedVds.getvds_name())); retrunValue = false; } } } return retrunValue; } private boolean validateSingleHostAttachedToLocalStorage() { boolean retrunValue = true; storage_pool storagePool = DbFacade.getInstance().getStoragePoolDAO().getForVdsGroup( getParameters().getVdsStaticData().getvds_group_id()); if (storagePool != null && storagePool.getstorage_pool_type() == StorageType.LOCALFS) { if (!DbFacade.getInstance() .getVdsStaticDAO() .getAllForVdsGroup(getParameters().getVdsStaticData().getvds_group_id()) .isEmpty()) { addCanDoActionMessage(VdcBllMessages.VDS_CANNOT_ADD_MORE_THEN_ONE_HOST_TO_LOCAL_STORAGE); retrunValue = false; } } return retrunValue; } @Override public Map<Guid, VdcObjectType> getPermissionCheckSubjects() { return Collections.singletonMap(getVdsGroupId(), VdcObjectType.VdsGroups); } @Override protected List<Class<?>> getValidationGroups() { addValidationGroup(CreateEntity.class); if (getParameters().getVdsStaticData().getpm_enabled()) { addValidationGroup(PowerManagementCheck.class); } return super.getValidationGroups(); } private static LogCompat log = LogFactoryCompat.getLog(AddVdsCommand.class); }