package org.ovirt.engine.core.bll; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; 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.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.compat.DateTime; 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.StringHelper; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.FileUtil; import org.ovirt.engine.core.utils.VdcException; import org.ovirt.engine.core.utils.hostinstall.ICAWrapper; import org.ovirt.engine.core.utils.hostinstall.IVdsInstallCallBack; import org.ovirt.engine.core.utils.hostinstall.IVdsInstallWrapper; import org.ovirt.engine.core.utils.hostinstall.VdsInstallerFactory; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; public class VdsInstaller implements IVdsInstallCallBack { private static final String _remoteDirectory = "/tmp"; private final String _certificatesDirectory = "certs"; private final String _requestsDirectory = "requests"; private String _certRequestFileName = "cert.req"; private String _certFileName = "cert.pem"; private String _certFileNameLocal; private String _caFileName = "ca.pem"; private static final String FIREWALL_CONFIG_FILENAME_PREFIX = "firewall.conf"; // Extract file name, since DB entry may include path parts. private String _bootstrapRunningScript = new java.io.File( Config.<String> GetValue(ConfigValues.BootstrapInstallerFileName)).getName(); final public static String _getUniqueIdCommand = "/bin/echo -e `/bin/bash -c /usr/sbin/dmidecode|/bin/awk ' /UUID/{ print $2; } ' | /usr/bin/tr '\n' '_' && cat /sys/class/net/*/address | /bin/grep -v '00:00:00:00' | /bin/sort -u | /usr/bin/head --lines=1`"; protected VdsInstallStages _prevInstallStage = VdsInstallStages.Start; protected VdsInstallStages _currentInstallStage = VdsInstallStages.Start; private String _failedMessage; // private readonly VdsInstallStatusContainer _messages = new // VdsInstallStatusContainer(); private final InstallerMessages _messages; private Guid _fileGuid = new Guid(); private final VDS _vds; private String serverInstallationTime; private String _bootStrapInitialCommand = "chmod +x {vdsInstaller}; {vdsInstaller} -c 'ssl={server_SSL_enabled};management_port={management_port}' -O '{OrganizationName}' {NetConsolePort} -t {utc_time} -u False {OverrideFirewall} {EnginePort} {URL1} {URL1} {vds-server} {GUID} {RunFlag}"; protected String _finishCommand = ""; protected final IVdsInstallWrapper _wrapper = VdsInstallerFactory.CreateVdsInstallWrapper(); private final ICAWrapper _caWrapper = VdsInstallerFactory.CreateCaWrapper(); protected String _serverName; private final String _rootPassword; private final String _remoteBootstrapRunningScriptPath; private final String remoteFwRulesFilePath; private boolean isAddOvirtFlow = false; protected static final java.util.HashMap<VdsInstallStages, String> _translatedMessages = new java.util.HashMap<VdsInstallStages, String>(); static { _translatedMessages.put(VdsInstallStages.Start, "Starting Host installation"); _translatedMessages.put(VdsInstallStages.ConnectToServer, "Connecting to Host"); _translatedMessages.put(VdsInstallStages.CheckUniqueVds, "Get the unique vds id"); _translatedMessages.put(VdsInstallStages.UploadScript, "Upload Installation script to Host"); _translatedMessages.put(VdsInstallStages.RunScript, "Running first installation script on Host"); _translatedMessages.put(VdsInstallStages.DownloadCertificateRequest, "Downloading certificate request from Host"); _translatedMessages.put(VdsInstallStages.UploadSignedCertificate, "Upload signed sertificate to Host"); _translatedMessages.put(VdsInstallStages.UploadCA, "Upload Cerficate Autority to Host"); _translatedMessages.put(VdsInstallStages.SignCertificateRequest, "Sign certificate request and generate certificate"); _translatedMessages.put(VdsInstallStages.FinishCommand, "Running second installation script on Host"); _translatedMessages.put(VdsInstallStages.End, "Host installation complete"); _translatedMessages.put(VdsInstallStages.Error, "Error during Host installation"); } public VdsInstaller(VDS vds, String rootPassword, boolean overrideFirewall) { super(); _vds = vds; this.overrideFirewall = overrideFirewall; _messages = new InstallerMessages(vds.getvds_id()); _fileGuid = Guid.NewGuid(); _certFileNameLocal = "cert.pem"; _certRequestFileName = String.format("cert_%1$s.req", _fileGuid); _certFileName = String.format("cert_%1$s.pem", _fileGuid); _caFileName = String.format("CA_%1$s.pem", _fileGuid); String[] parts = _bootstrapRunningScript.split("[.]", -1); if (parts.length > 1) { _bootstrapRunningScript = String.format("%1$s_%2$s.%3$s", parts[0], _fileGuid, parts[1]); } else { throw new RuntimeException(); } _remoteBootstrapRunningScriptPath = _remoteDirectory + "/" + _bootstrapRunningScript; // Always // runs // on // Linux remoteFwRulesFilePath = String.format("%s/%s.%s", _remoteDirectory,FIREWALL_CONFIG_FILENAME_PREFIX,_fileGuid); _bootStrapInitialCommand = InitInitialCommand(vds, _bootStrapInitialCommand); _finishCommand = _bootStrapInitialCommand; _bootStrapInitialCommand = _bootStrapInitialCommand.replace("{RunFlag}", "False"); _finishCommand = _finishCommand.replace("{RunFlag}", "True"); _serverName = vds.gethost_name(); _rootPassword = rootPassword; _wrapper.InitCallback(this); _certFileNameLocal = _serverName + _certFileNameLocal; } protected String InitInitialCommand(VDS vds, String initialCommand) { initialCommand = initialCommand.replace("{vdsInstaller}", _remoteBootstrapRunningScriptPath); initialCommand = initialCommand.replace("{vds-server}", vds.gethost_name()); initialCommand = initialCommand.replace("{URL1}", Config.<String> GetValue(ConfigValues.VdcBootStrapUrl)); initialCommand = initialCommand.replace("{GUID}", _fileGuid.toString()); initialCommand = initialCommand.replace("{server_SSL_enabled}", Config.<Boolean> GetValue(ConfigValues.UseSecureConnectionWithServers).toString()); initialCommand = initialCommand.replace("{OrganizationName}", HandleOrganizationNameString(Config.<String> GetValue(ConfigValues.OrganizationName))); DateTime utcNow = DateTime.getUtcNow(); serverInstallationTime = utcNow.toString("yyyy-MM-ddTHH:mm:ss"); initialCommand = initialCommand.replace("{utc_time}", serverInstallationTime); initialCommand = initialCommand.replace("{management_port}", (Integer.toString(vds.getport()))); if (StringHelper.isNullOrEmpty(Config.<String> GetValue(ConfigValues.NetConsolePort))) { // if empty send nothing initialCommand = initialCommand.replace("{NetConsolePort}", ""); } else { initialCommand = initialCommand.replace("{NetConsolePort}", String.format("-n %1$s", Config.<String> GetValue(ConfigValues.NetConsolePort))); } String publicUrlPort = Config.<String> GetValue(ConfigValues.PublicURLPort); if (StringHelper.isNullOrEmpty(publicUrlPort)) { initialCommand = initialCommand.replace("{EnginePort}", ""); } else { initialCommand = initialCommand.replace("{EnginePort}", String.format("-p %1$s", publicUrlPort)); } initialCommand = initialCommand.replace("{OverrideFirewall}", isOverrideFirewallAllowed() ? "-f " + remoteFwRulesFilePath : ""); return initialCommand; } private boolean isOverrideFirewallAllowed() { return overrideFirewall && _vds.getvds_type() == VDSType.VDS && StringUtils.isNotEmpty(Config.<String> GetValue(ConfigValues.IPTablesConfig)); } private String HandleOrganizationNameString(String oName) { oName = oName.replace("\\", "\\\\"); oName = oName.replace("'", "'\\''"); return oName; } public String getErrorMessage() { return _failedMessage; } protected String getCurrentInstallStage() { return _translatedMessages.get(_currentInstallStage); } protected boolean _executionSucceded = true; private boolean overrideFirewall = false; public boolean Install() { _prevInstallStage = VdsInstallStages.None; _currentInstallStage = VdsInstallStages.Start; while (_currentInstallStage.getValue() < VdsInstallStages.End.getValue()) { // check for error if (!_executionSucceded) { log.errorFormat("Installation of {0}. Operation failure. (Stage: {1})", _serverName, getCurrentInstallStage()); _currentInstallStage = VdsInstallStages.Error; break; } else if (_currentInstallStage == _prevInstallStage) { _executionSucceded = false; log.errorFormat("Installation of {0}. No meaningful response recieved from Host. (Stage: {1})", _serverName, getCurrentInstallStage()); _currentInstallStage = VdsInstallStages.Error; break; } // continue logic else { _executionSucceded = false; _prevInstallStage = _currentInstallStage; RunStage(); } } if (log.isDebugEnabled()) { log.debug("Closing installer connections"); } _wrapper.wrapperShutdown(); return _currentInstallStage != VdsInstallStages.Error; } protected void RunStage() { if (this.getClass() == VdsInstaller.class) { log.infoFormat("Installation of {0}. Executing installation stage. (Stage: {1})", _serverName, getCurrentInstallStage()); } switch (_currentInstallStage) { case Start: { _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); _executionSucceded = true; break; } case ConnectToServer: { _executionSucceded = _wrapper.ConnectToServer(_serverName, _rootPassword); break; } case CheckUniqueVds: { _wrapper.RunSSHCommand(_getUniqueIdCommand); break; } case UploadScript: { String path = Config.resolveBootstrapInstallerPath(); _executionSucceded = _wrapper.UploadFile(path, _remoteBootstrapRunningScriptPath); if (isOverrideFirewallAllowed() && _executionSucceded) { _currentInstallStage = VdsInstallStages.UploadScript; _executionSucceded = uploadFirewallRulesConfFile(); } break; } case RunScript: { log.infoFormat("Installation of {0}. Sending SSH Command {1}. (Stage: {2})", _serverName, _bootStrapInitialCommand, getCurrentInstallStage()); Boolean fRes = _wrapper.RunSSHCommand(_bootStrapInitialCommand); log.infoFormat(" RunScript ended:" + fRes.toString()); break; } case DownloadCertificateRequest: { // First parameter will always run on Linux, so use path.combine // just for the second param. Boolean fRes = _wrapper.DownloadFile(_remoteDirectory + "/" + _certRequestFileName, buildCAPath(_requestsDirectory, _certRequestFileName)); log.infoFormat(" DownloadCertificateRequest ended:" + fRes.toString()); break; } case SignCertificateRequest: { _executionSucceded = _caWrapper.SignCertificateRequest(_certRequestFileName, Config.<Integer> GetValue(ConfigValues.VdsCertificateValidityInYears) * 365, _certFileNameLocal); log.infoFormat(" SignCertificateRequest ended:" + _executionSucceded); if (_executionSucceded) { String currRequest = buildCAPath(_requestsDirectory, _certRequestFileName); try { FileUtil.deleteFile(currRequest); } catch (RuntimeException exp) { log.errorFormat( "Installation of {0}. Could not delete certificate request file from: {1}. error: {2}. (Stage: {3}", _serverName, currRequest, exp.getMessage(), getCurrentInstallStage()); } _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); } else { log.error("Error signing certificate request"); } break; } case UploadSignedCertificate: { // Second parameter will always run on Linux, so use // path.combine just for the first param. Boolean fRes = _wrapper.UploadFile(buildCAPath(_certificatesDirectory, _certFileNameLocal), _remoteDirectory + "/" + _certFileName); log.infoFormat(" UploadSignedCertificate ended:" + fRes.toString()); break; } case UploadCA: { String path = String.format("%1$s/%2$s", _remoteDirectory, _caFileName); _wrapper.UploadFile(Config.resolveCACertificatePath(), path); break; } case FinishCommand: { log.infoFormat("Installation of {0}. Sending SSH Command {1}. (Stage: {2})", _serverName, _finishCommand, getCurrentInstallStage()); Boolean fRes = _wrapper.RunSSHCommand(_finishCommand); log.infoFormat(" FinishCommand ended:" + fRes.toString()); break; } } } private boolean uploadFirewallRulesConfFile() { boolean isUploaded = false; String ipTableConfig = Config.<String> GetValue(ConfigValues.IPTablesConfig); if (StringUtils.isNotEmpty(ipTableConfig)) { String fwRulesFileNamePath = null; try { java.io.File fwRulesFile = java.io.File.createTempFile(FIREWALL_CONFIG_FILENAME_PREFIX, null); fwRulesFileNamePath = fwRulesFile.getAbsolutePath(); BufferedWriter out = new BufferedWriter(new FileWriter(fwRulesFile)); out.write(ipTableConfig); out.close(); isUploaded = _wrapper.UploadFile(fwRulesFileNamePath, remoteFwRulesFilePath); fwRulesFile.delete(); } catch (IOException e) { log.errorFormat("Error during create and upload firewall rules temp file {0} to destination {1} with error {2}", fwRulesFileNamePath == null ? FIREWALL_CONFIG_FILENAME_PREFIX : fwRulesFileNamePath, remoteFwRulesFilePath, ExceptionUtils.getMessage(e)); log.debug(e); } } return isUploaded; } @Override public void AddMessage(String message) { if (message.toUpperCase().indexOf("<BSTRAP COMPONENT='RHEV_INSTALL' STATUS='OK'/>") != -1 && (_currentInstallStage == VdsInstallStages.RunScript || _currentInstallStage == VdsInstallStages.FinishCommand)) { _executionSucceded = true; log.infoFormat("Installation of {0}. Recieved message: {1}. Stage completed. (Stage: {2})", _serverName, message, getCurrentInstallStage()); _messages.AddMessage(message); _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); } else if (message.toUpperCase().indexOf("<BSTRAP COMPONENT='RHEV_INSTALL' STATUS='FAIL'/>") != -1 && (_currentInstallStage == VdsInstallStages.RunScript || _currentInstallStage == VdsInstallStages.FinishCommand)) { _executionSucceded = false; log.errorFormat("Installation of {0}. Recieved message: {1}. Error occured. (Stage: {2})", _serverName, message, getCurrentInstallStage()); _messages.AddMessage(message); } else if (message.toUpperCase() .indexOf("<BSTRAP COMPONENT='RHEV_INSTALL' STATUS='OK' MESSAGE='RHEV-H ACCESSIBLE'/>") != -1 && _currentInstallStage == VdsInstallStages.RunScript) { log.infoFormat("Installation of {0}. Recieved message: {1}. Stage completed. (Stage: {2})", _serverName, message, getCurrentInstallStage()); // in case and RHEV-H installation was detected - update VDS entity updateOvirtHostEntity(); _messages.AddMessage(message); // skip all steps - vds_installer should update vdsm-reg.conf, restart vdsm-reg and quit. _currentInstallStage = VdsInstallStages.End; _executionSucceded = true; } else { log.infoFormat("Installation of {0}. Recieved message: {1}. FYI. (Stage: {2})", _serverName, message, getCurrentInstallStage()); // if its CheckUniqueVds stage assuming the message received is the unique id _executionSucceded = true; if (_currentInstallStage == VdsInstallStages.CheckUniqueVds) { _executionSucceded = InsertUniqueId(message); if (!_executionSucceded) { _messages.AddMessage( String.format( "<BSTRAP component='RHEV_INSTALL' status='FAIL' message='Failed to install host %1$s, host already exists in oVirt'/>", _serverName ) ); } } else { _messages.AddMessage(message); } } } private void updateOvirtHostEntity() { isAddOvirtFlow = true; _vds.setstatus(VDSStatus.PendingApproval); _vds.setvds_type(VDSType.oVirtNode); _vds.getStaticData().setOtpValidity(calculateOtp(serverInstallationTime)); TransactionSupport.executeInNewTransaction(new TransactionMethod<Void>() { @Override public Void runInTransaction() { DbFacade.getInstance().getVdsStaticDAO().update(_vds.getStaticData()); DbFacade.getInstance().getVdsDynamicDAO().update(_vds.getDynamicData()); return null; } }); } // for backward compatibility we update the old id to new id // new id: BoardId_MacAddress // old id: BoardId public static void UpdateUniqueId(String id) { String uniqueId = (id.indexOf('_') > 0) ? id.substring(0, id.indexOf('_')) : id; List<VDS> list = DbFacade.getInstance().getVdsDAO().getAllWithUniqueId(uniqueId); if (list.size() > 0 && !id.equals(uniqueId)) { // save the new format of uniqueid list.get(0).setUniqueId(id); DbFacade.getInstance().getVdsStaticDAO().update(list.get(0).getStaticData()); } } private boolean InsertUniqueId(String message) { String uniqueId = message.trim(); UpdateUniqueId(uniqueId); if (VdsInstallHelper.isVdsUnique(_vds.getvds_id(), uniqueId)) { log.infoFormat("Installation of {0}. Assigning unique id {1} to Host. (Stage: {2})", _serverName, uniqueId, getCurrentInstallStage()); _vds.setUniqueId(uniqueId); DbFacade.getInstance().getVdsStaticDAO().update(_vds.getStaticData()); _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); return true; } log.errorFormat("Installation of {0}. Host with unique id {1} is already present in system. (Stage: {2})", _serverName, uniqueId, getCurrentInstallStage()); return false; } @Override public void Connected() { if (_currentInstallStage == VdsInstallStages.ConnectToServer) { log.infoFormat("Installation of {0}. Successfully connected to server ssh. (Stage: {1})", _serverName, getCurrentInstallStage()); _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); _executionSucceded = true; } else if (_currentInstallStage != VdsInstallStages.Error) { log.warnFormat("Installation of {0}. Illegal stage to connect to Host. (Stage: {1})", _serverName, getCurrentInstallStage()); _currentInstallStage = VdsInstallStages.Error; } } @Override public void EndTransfer() { if (_currentInstallStage == VdsInstallStages.UploadScript || _currentInstallStage == VdsInstallStages.DownloadCertificateRequest || _currentInstallStage == VdsInstallStages.UploadSignedCertificate || _currentInstallStage == VdsInstallStages.UploadCA) { log.infoFormat("Installation of {0}. successfully done sftp operation ( Stage: {1})", _serverName, _translatedMessages.get(_currentInstallStage)); _currentInstallStage = VdsInstallStages.forValue(_currentInstallStage.getValue() + 1); _executionSucceded = true; } else if (_currentInstallStage != VdsInstallStages.Error) { log.warnFormat("Installation of {0}. Illegal stage for sftp operation. (Stage: {1})", _serverName, getCurrentInstallStage()); _currentInstallStage = VdsInstallStages.Error; } } @Override public void AddError(String error) { log.errorFormat("Installation of {0}. Error: {1}. (Stage: {2})", _serverName, error, getCurrentInstallStage()); _messages.AddMessage(error); } @Override public void Failed(String error) { log.errorFormat("Installation of {0} has failed. Failure details: {1}. (Stage: {2})", _serverName, error, getCurrentInstallStage()); _messages.AddMessage(error); _currentInstallStage = VdsInstallStages.Error; _failedMessage = error; } public static String buildCAPath(String suffix1, String suffix2) { return Config.resolveCABasePath() + java.io.File.separator + suffix1 + java.io.File.separator + suffix2; } public boolean isAddOvirtFlow() { return isAddOvirtFlow; } /** * Converts string material which represents a date in "yyyy-MM-dd'T'HH:mm:ss" into epoch in seconds * * @param dateMaterial * the date string * @return */ private static long calculateOtp(String dateMaterial) { SimpleDateFormat utcDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); utcDateTimeFormat.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); Date parsedDate; try { parsedDate = utcDateTimeFormat.parse(dateMaterial); } catch (ParseException e) { throw new VdcException(String.format("Failed to parse the date of server installation time %s", dateMaterial), e); } return TimeUnit.MILLISECONDS.toSeconds(parsedDate.getTime()); } private static LogCompat log = LogFactoryCompat.getLog(VdsInstaller.class); }