package org.ovirt.engine.core.bll.hostdeploy; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.Backend; import org.ovirt.engine.core.bll.DisableInMaintenanceMode; import org.ovirt.engine.core.bll.QueriesCommandBase; import org.ovirt.engine.core.bll.VdsHandler; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VdsOperationActionParameters; import org.ovirt.engine.core.common.action.hostdeploy.AddVdsActionParameters; import org.ovirt.engine.core.common.action.hostdeploy.ApproveVdsParameters; import org.ovirt.engine.core.common.action.hostdeploy.UpdateVdsActionParameters; import org.ovirt.engine.core.common.businessentities.Cluster; 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.VdsStatic; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.queries.VdcQueryReturnValue; import org.ovirt.engine.core.common.queries.hostdeploy.RegisterVdsParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.TransactionScopeOption; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.dao.ClusterDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VdsDynamicDao; import org.ovirt.engine.core.dao.VdsStaticDao; import org.ovirt.engine.core.dao.VdsStatisticsDao; import org.ovirt.engine.core.di.Injector; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @DisableInMaintenanceMode public class RegisterVdsQuery<P extends RegisterVdsParameters> extends QueriesCommandBase<P> { private AuditLogType error = AuditLogType.UNASSIGNED; private String strippedVdsUniqueId; private final AuditLogableBase logable; private List<VDS> vdssByUniqueId; private static final Object doubleRegistrationLock = new Object(); @Inject private ClusterDao clusterDao; @Inject private VdsDao vdsDao; @Inject private VdsStaticDao vdsStaticDao; @Inject private VdsDynamicDao vdsDynamicDao; @Inject private VdsStatisticsDao vdsStatisticsDao; /** * 'z' has the highest ascii value from the acceptable characters, so the bit set size should be initiated to it. * the size is 'z'+1 so that each char will be represented in the BitSet with index which equals to it's char value. */ private static final BitSet validChars = new BitSet('z' + 1); static { // the set method sets the bits from the specified from index(inclusive) to the specified toIndex(exclusive) to // true so the toIndex should be incremented in 1 in order to include the char represented by that index in the // range. validChars.set('a', 'z' + 1); validChars.set('A', 'Z' + 1); validChars.set('0', '9' + 1); validChars.set('-'); validChars.set('.'); validChars.set(':'); validChars.set('_'); } public RegisterVdsQuery(P parameters) { super(parameters); logable = Injector.injectMembers(new AuditLogableBase(parameters.getVdsId())); } protected String getStrippedVdsUniqueId() { if (strippedVdsUniqueId == null) { // since we use the management IP field, makes sense to remove the // illegal characters in advance StringBuilder builder = new StringBuilder(); for (char ch : getParameters().getVdsUniqueId().toCharArray()) { if (validChars.get(ch)) { builder.append(ch); } } strippedVdsUniqueId = builder.toString(); } return strippedVdsUniqueId; } private List<VDS> getVdssByUniqueId() { if (vdssByUniqueId == null) { vdssByUniqueId = vdsDao.getAllWithUniqueId(getStrippedVdsUniqueId()); } return vdssByUniqueId; } @Override protected boolean validateInputs() { if (!super.validateInputs()) { return false; } VdcQueryReturnValue returnValue = getQueryReturnValue(); returnValue.setExceptionString(""); try { String hostName = getParameters().getVdsHostName(); if (StringUtils.isEmpty(hostName)) { returnValue.setExceptionString("Cannot register Host - no Hostname address specified."); return false; } String vdsUniqueId = getParameters().getVdsUniqueId(); if (StringUtils.isEmpty(vdsUniqueId)) { returnValue.setExceptionString( String.format( "Cannot register host '%1$s' - host id is empty.", hostName ) ); AuditLogableBase logable = Injector.injectMembers(new AuditLogableBase()); logable.addCustomValue("VdsHostName", hostName); auditLogDirector.log(logable, AuditLogType.VDS_REGISTER_EMPTY_ID); return false; } List<VDS> vdssByUniqueId = getVdssByUniqueId(); if (vdssByUniqueId.size() > 1) { returnValue.setExceptionString("Cannot register Host - unique id is ambigious."); return false; } if (vdssByUniqueId.size() == 1) { VDS vds = vdssByUniqueId.get(0); if (!VdsHandler.isPendingOvirt(vds)) { returnValue.setExceptionString(EngineMessage.VDS_STATUS_NOT_VALID_FOR_UPDATE.name()); return false; } } } catch (RuntimeException ex) { log.error("Exception", ex); returnValue.setExceptionString(String.format("Cannot register Host - An exception has been thrown: %1$s", ex.getMessage())); return false; } return true; } @Override protected void executeQueryCommand() { try { log.info("Running Command: RegisterVds"); executeRegisterVdsCommand(); } catch (RuntimeException ex) { log.error("RegisterVdsQuery::ExecuteWithoutTransaction: An exception has been thrown.", ex); } finally { writeToAuditLog(); } } protected void executeRegisterVdsCommand() { synchronized (doubleRegistrationLock) { List<VDS> hostsByHostName = vdsDao.getAllForHostname(getParameters().getVdsName()); VDS provisionedVds = hostsByHostName.size() != 0 ? hostsByHostName.get(0) : null; if (provisionedVds != null && provisionedVds.getStatus() != VDSStatus.InstallingOS) { // if not in InstallingOS status, this host is not provisioned. provisionedVds = null; } // force to reload vdss by unique ID used later on vdssByUniqueId = null; VDS vdsByUniqueId = getVdssByUniqueId().size() != 0 ? getVdssByUniqueId().get(0) : null; // in case oVirt host was added for the second time - perform approval if (vdsByUniqueId != null && vdsByUniqueId.getStatus() == VDSStatus.PendingApproval) { getQueryReturnValue().setSucceeded(dispatchOvirtApprovalCommand(vdsByUniqueId.getId())); return; } log.debug("RegisterVdsQuery::ExecuteCommand - Entering"); if (StringUtils.isEmpty(getParameters().getVdsName())) { getParameters().setVdsName(getParameters().getVdsUniqueId()); log.debug("RegisterVdsQuery::ExecuteCommand - VdsName empty, using VdsUnique ID as name"); } logable.addCustomValue("VdsName1", getParameters().getVdsName()); Guid clusterId = getClusterId(); if (Guid.isNullOrEmpty(clusterId)) { reportClusterError(); return; } if (provisionedVds != null) { // In provision don't set host on pending - isPending = false getQueryReturnValue().setSucceeded(register(provisionedVds, clusterId, false)); } else { // TODO: always add in pending state, and if auto approve call // approve command action after registration AtomicBoolean isPending = new AtomicBoolean(false); getQueryReturnValue().setSucceeded( handleOldVdssWithSameHostName(vdsByUniqueId) && handleOldVdssWithSameName(vdsByUniqueId) && checkAutoApprovalDefinitions(isPending) && register(vdsByUniqueId, clusterId, isPending.get())); } log.debug("RegisterVdsQuery::ExecuteCommand - Leaving Succeded value is '{}'", getQueryReturnValue().getSucceeded()); } } private void reportClusterError() { log.error("No default or valid cluster was found, host registration failed."); AuditLogableBase logableBase = Injector.injectMembers(new AuditLogableBase()); logableBase.setVdsId(getParameters().getVdsId()); auditLogDirector.log(logableBase, AuditLogType.HOST_REGISTRATION_FAILED_INVALID_CLUSTER); } private Guid getClusterId() { Guid clusterId = getParameters().getClusterId(); if (Guid.Empty.equals(getParameters().getClusterId())) { clusterId = Guid.createGuidFromStringDefaultEmpty( Config.getValue(ConfigValues.AutoRegistrationDefaultClusterID)); log.debug( "Cluster id was not provided for registering the host {}, the cluster id {} is taken from the config value {}", getParameters().getVdsName(), clusterId, ConfigValues.AutoRegistrationDefaultClusterID.name()); } if (Guid.isNullOrEmpty(clusterId)) { // try to get the default cluster id Cluster cluster = clusterDao.getByName("Default"); if (cluster != null) { clusterId = cluster.getId(); } else { // this may occur when the default cluster is removed List<Cluster> clusters = clusterDao.getAll(); if (!clusters.isEmpty()) { clusterId = clusters.get(0).getId(); } } } return clusterId; } private boolean dispatchOvirtApprovalCommand(Guid oVirtId) { boolean isApprovalDispatched = true; final ApproveVdsParameters params = new ApproveVdsParameters(); params.setVdsId(oVirtId); params.setApprovedByRegister(true); try { ThreadPoolUtil.execute(() -> { try { VdcReturnValueBase ret = Backend.getInstance().runInternalAction(VdcActionType.ApproveVds, params); if (ret == null || !ret.getSucceeded()) { log.error("Approval of oVirt '{}' failed. ", params.getVdsId()); } else if (ret.getSucceeded()) { log.info("Approval of oVirt '{}' ended successfully. ", params.getVdsId()); } } catch (RuntimeException ex) { log.error("Failed to Approve host", ex); } }); } catch (Exception e) { isApprovalDispatched = false; } return isApprovalDispatched; } private boolean register(VDS vds, Guid clusterId, boolean isPending) { boolean returnValue; log.debug("RegisterVdsQuery::register - Entering"); if (vds == null) { returnValue = registerNewHost(clusterId, isPending); } else { returnValue = updateExistingHost(vds, isPending); } log.debug("RegisterVdsQuery::register - Leaving with value {}", returnValue); return returnValue; } private boolean updateExistingHost(VDS vds, boolean pending) { boolean returnValue = true; vds.setHostName(vds.getHostName()); vds.setPort(getParameters().getVdsPort()); log.debug( "RegisterVdsQuery::register - Will try now to update VDS with existing unique id; Name: '{}', Hostname: '{}', Unique: '{}', VdsPort: '{}', isPending: '{}' with force synchronize", getParameters().getVdsHostName(), getStrippedVdsUniqueId(), getStrippedVdsUniqueId(), getParameters().getVdsPort(), pending); UpdateVdsActionParameters p = new UpdateVdsActionParameters(vds.getStaticData(), "", false); p.setInstallHost(!pending); p.setReinstallOrUpgrade(!pending); p.setAuthMethod(VdsOperationActionParameters.AuthenticationMethod.PublicKey); if (vds.isFenceAgentsExist()) { p.setFenceAgents(vds.getFenceAgents()); } p.setTransactionScopeOption(TransactionScopeOption.RequiresNew); VdcReturnValueBase rc = Backend.getInstance().runInternalAction(VdcActionType.UpdateVds, p); if (!rc.getSucceeded()) { error = AuditLogType.VDS_REGISTER_EXISTING_VDS_UPDATE_FAILED; log.debug( "RegisterVdsQuery::register - Failed to update existing VDS Name: '{}', Hostname: '{}', Unique: '{}', VdsPort: '{}', isPending: '{}'", getParameters().getVdsHostName(), getStrippedVdsUniqueId(), getStrippedVdsUniqueId(), getParameters().getVdsPort(), pending); captureCommandErrorsToLogger(rc, "RegisterVdsQuery::register"); returnValue = false; } else { log.info( "RegisterVdsQuery::register - Updated a '{}' registered VDS - Name: '{}', Hostname: '{}', UniqueID: '{}'", vds.getStatus() == VDSStatus.PendingApproval ? "Pending " : "", getParameters().getVdsName(), getParameters().getVdsHostName(), getStrippedVdsUniqueId()); } return returnValue; } private boolean registerNewHost(Guid clusterId, boolean pending) { boolean returnValue = true; VdsStatic vds = new VdsStatic(getParameters().getVdsHostName(), getStrippedVdsUniqueId(), getParameters().getVdsPort(), getParameters().getSSHPort(), getParameters().getSSHUser(), clusterId, Guid.Empty, getParameters().getVdsName(), Config.<Boolean> getValue(ConfigValues.SSLEnabled), VDSType.VDS, null); vds.setSshKeyFingerprint(getParameters().getSSHFingerprint()); log.debug( "RegisterVdsQuery::register - Will try now to add VDS from scratch; Name: '{}', Hostname: '{}', Unique: '{}', VdsPort: '{}',Subnet mask: '{}', isPending: '{}' with force synchronize", getParameters().getVdsName(), getParameters().getVdsHostName(), getStrippedVdsUniqueId(), getParameters().getVdsPort(), pending); AddVdsActionParameters p = new AddVdsActionParameters(vds, ""); p.setPending(pending); VdcReturnValueBase ret = Backend.getInstance().runInternalAction(VdcActionType.AddVds, p); if (!ret.getSucceeded()) { log.error( "RegisterVdsQuery::register - Registration failed for VDS - Name: '{}', Hostname: '{}', UniqueID: '{}', Subnet mask: '{}'", getParameters().getVdsName(), getParameters().getVdsHostName(), getStrippedVdsUniqueId()); captureCommandErrorsToLogger(ret, "RegisterVdsQuery::register"); error = AuditLogType.VDS_REGISTER_FAILED; returnValue = false; } else { log.info( "RegisterVdsQuery::register - Registered a new VDS '{}' - Name: '{}', Hostname: '{}', UniqueID: '{}'", pending ? "pending approval" : "automatically approved", getParameters().getVdsName(), getParameters().getVdsHostName(), getStrippedVdsUniqueId()); } return returnValue; } private boolean handleOldVdssWithSameHostName(VDS vdsByUniqueId) { // handle old VDSs with same host_name (IP) log.debug("RegisterVdsQuery::handleOldVdssWithSameHostName - Entering"); boolean returnValue = true; List<VDS> vdssByHostName = vdsDao.getAllForHostname(getParameters().getVdsHostName()); int lastIteratedIndex = 1; if (vdssByHostName.size() > 0) { log.debug( "RegisterVdsQuery::handleOldVdssWithSameHostName - found '{}' VDS(s) with the same host name '{}'. Will try to change their hostname to a different value", vdssByHostName.size(), getParameters().getVdsHostName()); for (VDS vdsByHostName : vdssByHostName) { // looping foreach VDS found with similar hostnames and change to each one to available hostname if ( vdsByUniqueId == null || !vdsByHostName.getId().equals(vdsByUniqueId.getId()) ) { boolean unique = false; String tryHostName = ""; for (int i = lastIteratedIndex; i <= 100; i++, lastIteratedIndex = i) { tryHostName = String.format("hostname-was-%1$s-%2$s", getParameters().getVdsHostName(), i); if (vdsDao.getAllForHostname(tryHostName).size() == 0) { unique = true; break; } } if (unique) { String oldHostName = vdsByHostName.getHostName(); vdsByHostName.setHostName(tryHostName); UpdateVdsActionParameters parameters = new UpdateVdsActionParameters( vdsByHostName.getStaticData(), "" , false); parameters.setShouldBeLogged(false); parameters.setTransactionScopeOption(TransactionScopeOption.RequiresNew); if (vdsByHostName.isFenceAgentsExist()) { parameters.setFenceAgents(vdsByHostName.getFenceAgents()); } // If host exists in InstallingOs status, remove it from DB and move on final VDS foundVds = vdsDao.getByName(parameters.getVdsStaticData().getName()); if ((foundVds != null) && (foundVds.getDynamicData().getStatus() == VDSStatus.InstallingOS)) { TransactionSupport.executeInScope(TransactionScopeOption.Required, () -> { vdsStatisticsDao.remove(foundVds.getId()); vdsDynamicDao.remove(foundVds.getId()); vdsStaticDao.remove(foundVds.getId()); return null; }); } VdcReturnValueBase ret = Backend.getInstance().runInternalAction(VdcActionType.UpdateVds, parameters); if (!ret.getSucceeded()) { error = AuditLogType.VDS_REGISTER_ERROR_UPDATING_HOST; logable.addCustomValue("VdsName2", vdsByHostName.getStaticData().getName()); log.error( "RegisterVdsQuery::handleOldVdssWithSameHostName - could not update VDS '{}'", vdsByHostName.getStaticData().getName()); captureCommandErrorsToLogger(ret, "RegisterVdsQuery::handleOldVdssWithSameHostName"); return false; } else { log.info( "RegisterVdsQuery::handleOldVdssWithSameHostName - Another VDS was using this IP '{}'. Changed to '{}'", oldHostName, tryHostName); } } else { log.error( "Engine::handleOldVdssWithSameHostName - Could not change the IP for an existing VDS. All available hostnames are taken (ID = '{}', name = '{}', management IP = '{}' , host name = '{}')", vdsByHostName.getId(), vdsByHostName.getName(), vdsByHostName.getFenceAgents().isEmpty() ? "" : vdsByHostName.getFenceAgents() .get(0) .getIp(), vdsByHostName.getHostName()); error = AuditLogType.VDS_REGISTER_ERROR_UPDATING_HOST_ALL_TAKEN; returnValue = false; } } log.info( "RegisterVdsQuery::handleOldVdssWithSameHostName - No Change required for VDS '{}'. Since it has the same unique Id", vdsByHostName.getId()); } } log.debug("RegisterVdsQuery::handleOldVdssWithSameHostName - Leaving with value '{}'", returnValue); return returnValue; } /** * Check if another host has the same name as hostToRegister and if yes append a number to it. Eventually if the * host is in the db, persist the changes. */ private boolean handleOldVdssWithSameName(VDS hostToRegister) { log.debug("Entering"); boolean returnValue = true; VDS storedHost = vdsDao.getByName(getParameters().getVdsName()); List<String> allHostNames = getAllHostNames(vdsDao.getAll()); boolean hostExistInDB = hostToRegister != null; if (storedHost != null) { log.debug( "found VDS with the same name {0}. Will try to register with a new name", getParameters().getVdsName()); String nameToRegister = getParameters().getVdsName(); String uniqueIdToRegister = getParameters().getVdsUniqueId(); String newName; // check different uniqueIds but same name if (!uniqueIdToRegister.equals(storedHost.getUniqueId()) && nameToRegister.equals(storedHost.getName())) { if (hostExistInDB) { // update the registered host if exist in db allHostNames.remove(hostToRegister.getName()); newName = generateUniqueName(nameToRegister, allHostNames); hostToRegister.setVdsName(newName); UpdateVdsActionParameters parameters = new UpdateVdsActionParameters(hostToRegister.getStaticData(), "", false); if (hostToRegister.isFenceAgentsExist()) { parameters.setFenceAgents(hostToRegister.getFenceAgents()); } VdcReturnValueBase ret = Backend.getInstance().runInternalAction(VdcActionType.UpdateVds, parameters); if (!ret.getSucceeded()) { error = AuditLogType.VDS_REGISTER_ERROR_UPDATING_NAME; logable.addCustomValue("VdsName2", newName); log.error("could not update VDS '{}'", nameToRegister); captureCommandErrorsToLogger(ret, "RegisterVdsQuery::handleOldVdssWithSameName"); return false; } else { log.info( "Another VDS was using this name with IP '{}'. Changed to '{}'", nameToRegister, newName); } } else { // host doesn't exist in db yet. not persisting changes just object values. newName = generateUniqueName(nameToRegister, allHostNames); getParameters().setVdsName(newName); } } } log.debug("Leaving with value '{}'", returnValue); return returnValue; } private List<String> getAllHostNames(List<VDS> allHosts) { List<String> allHostNames = new ArrayList<>(allHosts.size()); for (VDS vds : allHosts) { allHostNames.add(vds.getName()); } return allHostNames; } private String generateUniqueName(String val, List<String> allHostNames) { int i = 2; boolean postfixed = false; StringBuilder sb = new StringBuilder(val); while (allHostNames.contains(val)) { if (!postfixed) { val = sb.append("-").append(i).toString(); postfixed = true; } else { val = sb.replace(sb.lastIndexOf("-"), sb.length(), "-").append(i).toString(); } i++; } return val; } private boolean checkAutoApprovalDefinitions(AtomicBoolean isPending) { // check auto approval definitions log.debug("RegisterVdsQuery::checkAutoApprovalDefinitions - Entering"); isPending.set(true); if (!Config.<String> getValue(ConfigValues.AutoApprovePatterns).equals("")) { for (String pattern : Config.<String> getValue(ConfigValues.AutoApprovePatterns) .split("[,]", -1)) { try { String patternHelper = pattern.toLowerCase(); Pattern patternRegex = Pattern.compile(patternHelper); String vdsHostnameHelper = getParameters().getVdsHostName().toLowerCase(); String vdsUniqueIdHelper = getParameters().getVdsUniqueId().toLowerCase().replace(":", "-"); if (vdsHostnameHelper.startsWith(pattern) || vdsUniqueIdHelper.startsWith(pattern) || patternRegex.matcher(vdsHostnameHelper).find() || patternRegex.matcher(vdsUniqueIdHelper).find()) { isPending.set(false); break; } } catch (RuntimeException ex) { error = AuditLogType.VDS_REGISTER_AUTO_APPROVE_PATTERN; log.error( "RegisterVdsQuery ::checkAutoApprovalDefinitions(out bool) - Error in auto approve pattern: '{}'-'{}'", pattern, ex.getMessage()); return false; } } } log.debug("RegisterVdsQuery::checkAutoApprovalDefinitions - Leaving - return value '{}'", isPending.get()); return true; } private void captureCommandErrorsToLogger(VdcReturnValueBase retValue, String prefixToMessage) { if (retValue.getFault() != null) { log.error("{} - Fault - {}", prefixToMessage, retValue.getFault().getMessage()); } if (retValue.getValidationMessages().size() > 0) { List<String> msgs = retValue.getValidationMessages(); for (String s : msgs) { log.error("{} - Validate Fault - {}", prefixToMessage, s); } } if (retValue.getExecuteFailedMessages().size() > 0) { for (String s : retValue.getExecuteFailedMessages()) { log.error("{} - Execution Fault - {}", prefixToMessage, s); } } } private void writeToAuditLog() { try { auditLogDirector.log(logable, getAuditLogTypeValue()); } catch (RuntimeException ex) { log.error("RegisterVdsQuery::WriteToAuditLog: An exception has been thrown.", ex); } } protected AuditLogType getAuditLogTypeValue() { return getQueryReturnValue().getSucceeded() ? AuditLogType.VDS_REGISTER_SUCCEEDED : error; } }