Alon Bar-Lev has uploaded a new change for review. Change subject: host-deploy: split logic and infra ......................................................................
host-deploy: split logic and infra Change-Id: If6055fc395765f054634afe2b06fd56158217154 Signed-off-by: Alon Bar-Lev <alo...@redhat.com> --- M backend/manager/modules/bll/exclude-filters.xml M backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeploy.java A backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeployBase.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java 4 files changed, 690 insertions(+), 594 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/64/39964/6 diff --git a/backend/manager/modules/bll/exclude-filters.xml b/backend/manager/modules/bll/exclude-filters.xml index 2cd6340..99a5335 100644 --- a/backend/manager/modules/bll/exclude-filters.xml +++ b/backend/manager/modules/bll/exclude-filters.xml @@ -121,7 +121,7 @@ </Match> <Match> - <Class name="org.ovirt.engine.core.bll.hostdeploy.VdsDeploy" /> + <Class name="org.ovirt.engine.core.bll.hostdeploy.VdsDeployBase" /> <Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" /> </Match> diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeploy.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeploy.java index a89eba5..8e3a101 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeploy.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeploy.java @@ -1,33 +1,12 @@ package org.ovirt.engine.core.bll.hostdeploy; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.security.KeyPair; -import java.security.KeyStoreException; -import java.text.SimpleDateFormat; import java.util.Arrays; -import java.util.Calendar; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.TimeZone; import java.util.concurrent.Callable; - -import javax.naming.TimeLimitExceededException; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.network.cluster.ManagementNetworkUtil; -import org.ovirt.engine.core.bll.utils.EngineSSHDialog; import org.ovirt.engine.core.common.businessentities.OpenstackNetworkProviderProperties; import org.ovirt.engine.core.common.businessentities.OpenstackNetworkProviderProperties.MessagingConfiguration; import org.ovirt.engine.core.common.businessentities.VDS; @@ -42,22 +21,17 @@ import org.ovirt.engine.core.utils.EngineLocalConfig; import org.ovirt.engine.core.utils.NetworkUtils; import org.ovirt.engine.core.utils.PKIResources; -import org.ovirt.engine.core.utils.archivers.tar.CachedTar; import org.ovirt.engine.core.utils.crypt.EngineEncryptionUtils; import org.ovirt.engine.core.utils.hostinstall.OpenSslCAWrapper; import org.ovirt.engine.core.utils.linq.LinqUtils; import org.ovirt.engine.core.utils.linq.Predicate; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; -import org.ovirt.engine.core.uutils.ssh.SSHDialog; -import org.ovirt.otopi.constants.BaseEnv; import org.ovirt.otopi.constants.Confirms; import org.ovirt.otopi.constants.CoreEnv; import org.ovirt.otopi.constants.NetEnv; -import org.ovirt.otopi.constants.Queries; import org.ovirt.otopi.constants.SysEnv; import org.ovirt.otopi.dialog.Event; -import org.ovirt.otopi.dialog.MachineDialogParser; import org.ovirt.otopi.dialog.SoftError; import org.ovirt.ovirt_host_deploy.constants.Const; import org.ovirt.ovirt_host_deploy.constants.Displays; @@ -85,33 +59,25 @@ * The installer environment is set according to the ovirt-host-deploy * documentation. */ -public class VdsDeploy implements SSHDialog.Sink, Closeable { +public class VdsDeploy extends VdsDeployBase { public static enum DeployStatus {Complete, Incomplete, Failed, Reboot}; - private static final int THREAD_JOIN_TIMEOUT = 20 * 1000; // milliseconds + private static final String IPTABLES_CUSTOM_RULES_PLACE_HOLDER = "@CUSTOM_RULES@"; private static final String IPTABLES_VDSM_PORT_PLACE_HOLDER = "@VDSM_PORT@"; private static final String IPTABLES_SSH_PORT_PLACE_HOLDER = "@SSH_PORT@"; - private static final String BOOTSTRAP_CUSTOM_ENVIRONMENT_PLACE_HOLDER = "@ENVIRONMENT@"; + + private static final String COND_IPTABLES_OVERRIDE = "IPTABLES_OVERRIDE"; + private static final String COND_NEUTRON_SETUP = "NEUTRON_SETUP"; + private static final String COND_NEUTRON_LINUX_BRIDGE_SETUP = "NEUTRON_LINUX_BRIDGE_SETUP"; + private static final String COND_NEUTRON_OPEN_VSWITCH_SETUP = "NEUTRON_OPEN_VSWITCH_SETUP"; private static final Logger log = LoggerFactory.getLogger(VdsDeploy.class); - private static volatile CachedTar s_deployPackage; - private SSHDialog.Control _control; - private Thread _thread; - private EngineSSHDialog _dialog; - private MachineDialogParser _parser; - private final InstallerMessages _messages; - - private VDS _vds; private boolean _isNode = false; private boolean _isLegacyNode = false; private boolean _reboot = false; - private String _correlationId = null; - private Exception _failException = null; - private boolean _resultError = false; private boolean _goingToReboot = false; - private boolean _aborted = false; private boolean _installIncomplete = false; private String _managementNetwork = null; private DeployStatus _deployStatus = DeployStatus.Failed; @@ -241,44 +207,7 @@ * Customization dialog. */ - /** - * Values to determine when customization should be performed. - */ - private static enum CustomizationCondition { - IPTABLES_OVERRIDE, - NEUTRON_SETUP, - NEUTRON_LINUX_BRIDGE_SETUP, - NEUTRON_OPEN_VSWITCH_SETUP - }; - /** - * Special annotation to specify when the customization is necessary. - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - private @interface CallWhen { - /** - * @return A condition that determines if the customization should run. - */ - CustomizationCondition[] value(); - } - /** - * A set of conditions under which the conditional customizations should run. - */ - private Set<CustomizationCondition> _customizationConditions = new HashSet<>(); - /** - * Customization tick. - */ - private int _customizationIndex = 0; - /** - * Customization aborting. - */ - private boolean _customizationShouldAbort = false; - /** - * Customization vector. - * This is tick based vector, every event execute the next - * tick. - */ - private final List<Callable<Boolean>> _customizationDialog = Arrays.asList( + private final List<Callable<Boolean>> _deployCustomizationDialog = Arrays.asList( new Callable<Boolean>() { public Boolean call() throws Exception { _parser.cliEnvironmentSet( "OVIRT_ENGINE/correlationId", @@ -351,7 +280,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.IPTABLES_OVERRIDE) + new Callable<Boolean>() {@CallWhen(COND_IPTABLES_OVERRIDE) public Boolean call() throws Exception { _parser.cliEnvironmentSet( NetEnv.IPTABLES_ENABLE, @@ -359,7 +288,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.IPTABLES_OVERRIDE) + new Callable<Boolean>() {@CallWhen(COND_IPTABLES_OVERRIDE) public Boolean call() throws Exception { _parser.cliEnvironmentSet( NetEnv.IPTABLES_RULES, @@ -506,7 +435,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _parser.cliEnvironmentSet( OpenStackEnv.NEUTRON_ENABLE, @@ -514,7 +443,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _parser.cliEnvironmentSet( OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/host", @@ -522,7 +451,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary( OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/" + _messagingConfiguration.getBrokerType().getHostKey(), @@ -530,7 +459,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary( OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/" + _messagingConfiguration.getBrokerType().getPortKey(), @@ -538,7 +467,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary( OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/" + @@ -547,7 +476,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary(OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/" + _messagingConfiguration.getBrokerType().getPasswordKey(), @@ -555,7 +484,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_SETUP) public Boolean call() throws Exception { _parser.cliEnvironmentSet( OpenStackEnv.NEUTRON_CONFIG_PREFIX + "DEFAULT/rpc_backend", @@ -563,7 +492,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_LINUX_BRIDGE_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_LINUX_BRIDGE_SETUP) public Boolean call() throws Exception { _parser.cliEnvironmentSet( OpenStackEnv.NEUTRON_LINUXBRIDGE_ENABLE, @@ -571,7 +500,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_LINUX_BRIDGE_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_LINUX_BRIDGE_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary( OpenStackEnv.NEUTRON_LINUXBRIDGE_CONFIG_PREFIX + "LINUX_BRIDGE/physical_interface_mappings", @@ -579,7 +508,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_OPEN_VSWITCH_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_OPEN_VSWITCH_SETUP) public Boolean call() throws Exception { _parser.cliEnvironmentSet( OpenStackEnv.NEUTRON_OPENVSWITCH_ENABLE, @@ -587,7 +516,7 @@ ); return true; }}, - new Callable<Boolean>() {@CallWhen(CustomizationCondition.NEUTRON_OPEN_VSWITCH_SETUP) + new Callable<Boolean>() {@CallWhen(COND_NEUTRON_OPEN_VSWITCH_SETUP) public Boolean call() throws Exception { _setCliEnvironmentIfNecessary( OpenStackEnv.NEUTRON_OPENVSWITCH_CONFIG_PREFIX + "OVS/bridge_mappings", @@ -641,12 +570,9 @@ Config.<Integer>getValue(ConfigValues.FenceKdumpMessageInterval) ); return true; - }}, - new Callable<Boolean>() { public Boolean call() throws Exception { - _parser.cliInstall(); - return true; }} ); + /** * Set the CLI environment variable if it's not <code>null</code>, otherwise perform a no-op so the dialog can * advance. @@ -662,75 +588,8 @@ _parser.cliEnvironmentSet(name, value); } } - /** - * Execute the next customization vector entry. - */ - private void _nextCustomizationEntry() throws Exception { - try { - if (_customizationShouldAbort) { - _parser.cliAbort(); - } - else { - boolean skip = false; - Callable<Boolean> customizationStep = _customizationDialog.get(_customizationIndex); - Method callMethod = customizationStep.getClass().getDeclaredMethod("call"); - if (callMethod != null) { - CallWhen ann = callMethod.getAnnotation(CallWhen.class); - skip = ann != null && !_customizationConditions.containsAll(Arrays.asList(ann.value())); - } - if (skip) { - _customizationIndex++; - _parser.cliNoop(); - } - else { - if (customizationStep.call()) { - _customizationIndex++; - } - } - } - } - catch (ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Protocol violation", e); - } - catch (SoftError e) { - log.error( - "Soft error during host {} customization dialog: {}", - _vds.getHostName(), - e.getMessage() - ); - log.debug("Exception", e); - _failException = e; - _customizationShouldAbort = true; - } - } - - /* - * Termination dialog. - */ - - /** - * Termination dialog tick. - */ - private int _terminationIndex = 0; - /** - * Termination vector. - * This is tick based vector, every event execute the next - * tick. - */ - private final List<Callable<Boolean>> _terminationDialog = Arrays.asList( - new Callable<Boolean>() { public Boolean call() throws Exception { - _resultError = (Boolean)_parser.cliEnvironmentGet( - BaseEnv.ERROR - ); - return true; - }}, - new Callable<Boolean>() { public Boolean call() throws Exception { - _aborted = (Boolean)_parser.cliEnvironmentGet( - BaseEnv.ABORTED - ); - return true; - }}, + private final List<Callable<Boolean>> _deployTerminationDialog = Arrays.asList( new Callable<Boolean>() { public Boolean call() throws Exception { _installIncomplete = (Boolean)_parser.cliEnvironmentGet( org.ovirt.ovirt_host_deploy.constants.CoreEnv.INSTALL_INCOMPLETE @@ -769,262 +628,74 @@ ); } return true; - }}, - new Callable<Boolean>() { public Boolean call() throws Exception { - File logFile = new File( - EngineLocalConfig.getInstance().getLogDir(), - String.format( - "%1$s%2$sovirt-%3$s-%4$s-%5$s.log", - "host-deploy", - File.separator, - new SimpleDateFormat("yyyyMMddHHmmss").format( - Calendar.getInstance().getTime() - ), - _vds.getHostName(), - _correlationId - ) - ); - _messages.post( - InstallerMessages.Severity.INFO, - String.format( - "Retrieving installation logs to: '%1$s'", - logFile - ) - ); - try (final OutputStream os = new FileOutputStream(logFile)) { - _parser.cliDownloadLog(os); - } - catch (IOException e) { - throw e; - } - catch (Exception e) { - log.error("Unexpected exception", e); - throw new RuntimeException(e); - } - return true; - }}, - new Callable<Boolean>() { public Boolean call() throws Exception { - _parser.cliEnvironmentSet( - CoreEnv.LOG_REMOVE_AT_EXIT, - true - ); - return true; - }}, - new Callable<Boolean>() { public Boolean call() throws Exception { - _parser.cliQuit(); - return true; }} ); - /** - * Execute the next termination vector entry. - */ - private void _nextTerminationEntry() throws Exception { - try { - if (_terminationDialog.get(_terminationIndex).call()) { - _terminationIndex++; + + protected boolean processEvent(Event.Base bevent) throws IOException { + boolean unknown = true; + + if (bevent instanceof Event.Confirm) { + Event.Confirm event = (Event.Confirm)bevent; + + if (Confirms.GPG_KEY.equals(event.what)) { + _messages.post(InstallerMessages.Severity.WARNING, event.description); + event.reply = true; + unknown = false; + } + else if (org.ovirt.ovirt_host_deploy.constants.Confirms.DEPLOY_PROCEED.equals(event.what)) { + event.reply = true; + unknown = false; } } - catch (ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Protocol violation", e); + else if (bevent instanceof Event.QueryMultiString) { + Event.QueryMultiString event = (Event.QueryMultiString)bevent; + + if (org.ovirt.ovirt_host_deploy.constants.Queries.CERTIFICATE_CHAIN.equals(event.name)) { + event.value = ( + PKIResources.Resource.CACertificate.toString(PKIResources.Format.X509_PEM) + + _certificate + ).split("\n"); + unknown = false; + } } - } + else if (bevent instanceof Event.DisplayMultiString) { + Event.DisplayMultiString event = (Event.DisplayMultiString)bevent; - /** - * Dialog implementation. - * Handle events incoming from host. - */ - private void _threadMain() { - try { - boolean terminate = false; - - while(!terminate) { - Event.Base bevent = _parser.nextEvent(); - - log.debug( - "Installation of {}: Event {}", - _vds.getHostName(), - bevent + if (Displays.CERTIFICATE_REQUEST.equals(event.name)) { + _messages.post( + InstallerMessages.Severity.INFO, + "Enrolling certificate" ); - - if (bevent instanceof Event.Terminate) { - terminate = true; - } - else if (bevent instanceof Event.Log) { - Event.Log event = (Event.Log)bevent; - InstallerMessages.Severity severity; - switch (event.severity) { - case INFO: - severity = InstallerMessages.Severity.INFO; - break; - case WARNING: - severity = InstallerMessages.Severity.WARNING; - break; - default: - severity = InstallerMessages.Severity.ERROR; - break; - } - _messages.post(severity, event.record); - } - else if (bevent instanceof Event.Confirm) { - Event.Confirm event = (Event.Confirm)bevent; - - if (Confirms.GPG_KEY.equals(event.what)) { - _messages.post(InstallerMessages.Severity.WARNING, event.description); - event.reply = true; - } - else if (org.ovirt.ovirt_host_deploy.constants.Confirms.DEPLOY_PROCEED.equals(event.what)) { - event.reply = true; - } - else { - log.warn( - "Installation of {}: Not confirming {}: {}", - _vds.getHostName(), - event.what, - event.description - ); - } - - _parser.sendResponse(event); - } - else if (bevent instanceof Event.QueryString) { - Event.QueryString event = (Event.QueryString)bevent; - - if (Queries.CUSTOMIZATION_COMMAND.equals(event.name)) { - _nextCustomizationEntry(); - } - else if (Queries.TERMINATION_COMMAND.equals(event.name)) { - _nextTerminationEntry(); - } - else { - throw new Exception( - String.format( - "Unexpected query %1$s", - event - ) - ); - } - } - else if (bevent instanceof Event.QueryValue) { - Event.QueryValue event = (Event.QueryValue)bevent; - - if (Queries.TIME.equals(event.name)) { - _messages.post( - InstallerMessages.Severity.INFO, - "Setting time" - ); - SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssZ"); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - event.value = format.format(Calendar.getInstance().getTime()); - } - else { - event.abort = true; - } - _parser.sendResponse(event); - } - else if (bevent instanceof Event.QueryMultiString) { - Event.QueryMultiString event = (Event.QueryMultiString)bevent; - - if (org.ovirt.ovirt_host_deploy.constants.Queries.CERTIFICATE_CHAIN.equals(event.name)) { - event.value = ( - PKIResources.Resource.CACertificate.toString(PKIResources.Format.X509_PEM) + - _certificate - ).split("\n"); - _parser.sendResponse(event); - } - else { - event.abort = true; - _parser.sendResponse(event); - } - } - else if (bevent instanceof Event.DisplayMultiString) { - Event.DisplayMultiString event = (Event.DisplayMultiString)bevent; - - if (Displays.CERTIFICATE_REQUEST.equals(event.name)) { - _messages.post( - InstallerMessages.Severity.INFO, - "Enrolling certificate" - ); - _certificate = OpenSslCAWrapper.signCertificateRequest( - StringUtils.join(event.value, "\n"), - _vds.getHostName() - ); - } - } - else { - throw new SoftError( - String.format( - "Unexpected event '%1$s'", - bevent - ) - ); - } + _certificate = OpenSslCAWrapper.signCertificateRequest( + StringUtils.join(event.value, "\n"), + _vds.getHostName() + ); + unknown = false; } } - catch (Exception e) { - _failException = e; - log.error("Error during deploy dialog", e); - try { - _control.close(); - } - catch (IOException ee) { - log.error("Error during close", e); - } - } + + return unknown; } - /* - * Constructor. - * @param vds vds to install. - */ - public VdsDeploy(VDS vds) { - _vds = vds; - - _messages = new InstallerMessages(_vds); - _dialog = new EngineSSHDialog(); - _parser = new MachineDialogParser(); - _thread = new Thread( - new Runnable() { - @Override - public void run() { - _threadMain(); - } - }, - "VdsDeploy" - ); - - if (s_deployPackage == null) { - s_deployPackage = new CachedTar( - new File( - EngineLocalConfig.getInstance().getCacheDir(), - Config.<String> getValue(ConfigValues.BootstrapPackageName) - ), - new File(Config.<String> getValue(ConfigValues.BootstrapPackageDirectory)) - ); - } - } - - /** - * Destructor. - */ @Override - protected void finalize() { - try { - close(); + protected void postExecute() { + if (_goingToReboot) { + _deployStatus = DeployStatus.Reboot; } - catch (IOException e) { - log.error("Exception during finalize", e); + else if (_installIncomplete) { + _deployStatus = DeployStatus.Incomplete; + } else { + _deployStatus = DeployStatus.Complete; } } - /** - * Release resources. - */ - public void close() throws IOException { - stop(); - if (_dialog != null) { - _dialog.close(); - _dialog = null; - } + public VdsDeploy(VDS vds) { + super("host-deploy", "ovirt-host-deploy", vds); + addCustomizationDialog(_deployCustomizationDialog); + addCustomizationDialog(CUSTOMIZATION_DIALOG_EPILOG); + addTerminationDialog(TERMINATION_DIALOG_PROLOG); + addTerminationDialog(_deployTerminationDialog); + addTerminationDialog(TERMINATION_DIALOG_EPILOG); } /** @@ -1045,42 +716,6 @@ _managementNetwork = managementNetwork; } - public void setCorrelationId(String correlationId) { - _correlationId = correlationId; - _messages.setCorrelationId(_correlationId); - } - - /** - * Set user. - * @param user user. - */ - public void setUser(String user) { - _dialog.setUser(user); - } - - /** - * Set key pair. - * @param keyPair key pair. - */ - public void setKeyPair(KeyPair keyPair) { - _dialog.setKeyPair(keyPair); - } - - /** - * Use engine default key pairs. - */ - public void useDefaultKeyPair() throws KeyStoreException { - _dialog.useDefaultKeyPair(); - } - - /** - * Set password. - * @param password password. - */ - public void setPassword(String password) { - _dialog.setPassword(password); - } - /** * Enable firewall setup. * @param doFirewall enable. @@ -1088,7 +723,7 @@ public void setFirewall(boolean doFirewall) { if (doFirewall) { _iptables = _getIpTables(); - _customizationConditions.add(CustomizationCondition.IPTABLES_OVERRIDE); + addCustomizationCondition(COND_IPTABLES_OVERRIDE); } } @@ -1101,164 +736,16 @@ return _deployStatus; } - /** - * Main method. - * Execute the command and initiate the dialog. - */ - public void execute() throws Exception { - try { - _dialog.setVds(_vds); - _dialog.connect(); - _messages.post( - InstallerMessages.Severity.INFO, - String.format( - "Connected to host %1$s with SSH key fingerprint: %2$s", - _vds.getHostName(), - _dialog.getHostFingerprint() - ) - ); - _dialog.authenticate(); - - String command = Config.<String> getValue(ConfigValues.BootstrapCommand); - - // in future we should set here LANG, LC_ALL - command = command.replace( - BOOTSTRAP_CUSTOM_ENVIRONMENT_PLACE_HOLDER, - "" - ); - - log.info( - "Installation of {}. Executing command via SSH {} < {}", - _vds.getHostName(), - command, - s_deployPackage.getFileNoUse() - ); - - try (final InputStream in = new FileInputStream(s_deployPackage.getFile())) { - _dialog.executeCommand( - this, - command, - new InputStream[] {in} - ); - } - - if (_failException != null) { - throw _failException; - } - - if (_resultError) { - // This is unlikeley as the ssh command will exit with failure. - throw new RuntimeException( - "Installation failed, please refer to installation logs" - ); - } - else if (_goingToReboot) { - _deployStatus = DeployStatus.Reboot; - } - else if (_installIncomplete) { - _deployStatus = DeployStatus.Incomplete; - } else { - _deployStatus = DeployStatus.Complete; - } - } - catch (TimeLimitExceededException e){ - log.error( - "Timeout during host {} install", - _vds.getHostName(), - e - ); - _messages.post( - InstallerMessages.Severity.ERROR, - "Processing stopped due to timeout" - ); - throw e; - } - catch(Exception e) { - log.error( - "Error during host {} install", - _vds.getHostName(), - e - ); - if (_failException == null) { - throw e; - } - else { - _messages.post( - InstallerMessages.Severity.ERROR, - e.getMessage() - ); - - log.error( - "Error during host {} install, prefering first exception: {}", - _vds.getHostName(), - _failException.getMessage() - ); - log.debug("Exception", _failException); - throw _failException; - } - } - } - - /* - * SSHDialog.Sink - */ - - @Override - public void setControl(SSHDialog.Control control) { - _control = control; - } - - @Override - public void setStreams(InputStream incoming, OutputStream outgoing) { - _parser.setStreams(incoming, outgoing); - } - - @Override - public void start() { - _thread.start(); - } - - @Override - public void stop() { - if (_thread != null) { - /* - * We cannot just interrupt the thread as the - * implementation of jboss connection pooling - * drops the connection when interrupted. - * As we may have log events pending to be written - * to database, we wait for some time for thread - * complete before interrupting. - */ - try { - _thread.join(THREAD_JOIN_TIMEOUT); - } - catch (InterruptedException e) { - log.error("interrupted", e); - } - if (_thread.isAlive()) { - _thread.interrupt(); - while(true) { - try { - _thread.join(); - break; - } - catch (InterruptedException e) {} - } - } - _thread = null; - } - } - public void setOpenStackAgentProperties(OpenstackNetworkProviderProperties properties) { _openStackAgentProperties = properties; if (_openStackAgentProperties != null) { _messagingConfiguration = _openStackAgentProperties.getAgentConfiguration().getMessagingConfiguration(); - _customizationConditions.add(CustomizationCondition.NEUTRON_SETUP); + addCustomizationCondition(COND_NEUTRON_SETUP); if (_openStackAgentProperties.isLinuxBridge()) { - _customizationConditions.add(CustomizationCondition.NEUTRON_LINUX_BRIDGE_SETUP); + addCustomizationCondition(COND_NEUTRON_LINUX_BRIDGE_SETUP); } else if (_openStackAgentProperties.isOpenVSwitch()) { - _customizationConditions.add(CustomizationCondition.NEUTRON_OPEN_VSWITCH_SETUP); + addCustomizationCondition(COND_NEUTRON_OPEN_VSWITCH_SETUP); } } } diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeployBase.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeployBase.java new file mode 100644 index 0000000..054355c --- /dev/null +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/hostdeploy/VdsDeployBase.java @@ -0,0 +1,609 @@ +package org.ovirt.engine.core.bll.hostdeploy; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.security.KeyPair; +import java.security.KeyStoreException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.Callable; + +import javax.naming.TimeLimitExceededException; + +import org.ovirt.engine.core.bll.utils.EngineSSHDialog; +import org.ovirt.engine.core.common.businessentities.VDS; +import org.ovirt.engine.core.common.config.Config; +import org.ovirt.engine.core.common.config.ConfigValues; +import org.ovirt.engine.core.utils.EngineLocalConfig; +import org.ovirt.engine.core.utils.archivers.tar.CachedTar; +import org.ovirt.engine.core.uutils.ssh.SSHDialog; +import org.ovirt.otopi.constants.BaseEnv; +import org.ovirt.otopi.constants.CoreEnv; +import org.ovirt.otopi.constants.Queries; +import org.ovirt.otopi.dialog.Event; +import org.ovirt.otopi.dialog.MachineDialogParser; +import org.ovirt.otopi.dialog.SoftError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class VdsDeployBase implements SSHDialog.Sink, Closeable { + + private static final int THREAD_JOIN_TIMEOUT = 20 * 1000; // milliseconds + private static final String BOOTSTRAP_CUSTOM_ENVIRONMENT_PLACE_HOLDER = "@ENVIRONMENT@"; + private static final String BOOTSTRAP_ENTRY_PLACE_HOLDER = "@ENTRY@"; + + private static final Logger log = LoggerFactory.getLogger(VdsDeployBase.class); + private static volatile CachedTar s_deployPackage; + + /** + * Customization vector. + * This is tick based vector, every event execute the next + * tick. + */ + // BUG: Arrays.asList() cannot handle single element correctly + protected final List<Callable<Boolean>> CUSTOMIZATION_DIALOG_EPILOG = new ArrayList() {{ add( + new Callable<Boolean>() { public Boolean call() throws Exception { + _parser.cliInstall(); + return true; + }} + );}}; + + /** + * Termination vector. + * This is tick based vector, every event execute the next + * tick. + */ + protected final List<Callable<Boolean>> TERMINATION_DIALOG_PROLOG = Arrays.asList( + new Callable<Boolean>() { public Boolean call() throws Exception { + _resultError = (Boolean)_parser.cliEnvironmentGet( + BaseEnv.ERROR + ); + return true; + }}, + new Callable<Boolean>() { public Boolean call() throws Exception { + _aborted = (Boolean)_parser.cliEnvironmentGet( + BaseEnv.ABORTED + ); + return true; + }} + ); + protected final List<Callable<Boolean>> TERMINATION_DIALOG_EPILOG = Arrays.asList( + new Callable<Boolean>() { public Boolean call() throws Exception { + File logFile = new File( + EngineLocalConfig.getInstance().getLogDir(), + String.format( + "%1$s%2$s%3$s-%4$s-%5$s-%6$s.log", + "host-deploy", + File.separator, + _logPrefix, + new SimpleDateFormat("yyyyMMddHHmmss").format( + Calendar.getInstance().getTime() + ), + _vds.getHostName(), + _correlationId + ) + ); + _messages.post( + InstallerMessages.Severity.INFO, + String.format( + "Retrieving installation logs to: '%1$s'", + logFile + ) + ); + try (final OutputStream os = new FileOutputStream(logFile)) { + _parser.cliDownloadLog(os); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + log.error("Unexpected exception", e); + throw new RuntimeException(e); + } + return true; + }}, + new Callable<Boolean>() { public Boolean call() throws Exception { + _parser.cliEnvironmentSet( + CoreEnv.LOG_REMOVE_AT_EXIT, + true + ); + return true; + }}, + new Callable<Boolean>() { public Boolean call() throws Exception { + _parser.cliQuit(); + return true; + }} + ); + + private SSHDialog.Control _control; + private Thread _thread; + private EngineSSHDialog _dialog; + + private final String _logPrefix; + private final String _entryPoint; + + protected MachineDialogParser _parser; + protected final InstallerMessages _messages; + protected VDS _vds; + protected String _correlationId = null; + protected Exception _failException = null; + protected boolean _resultError = false; + protected boolean _aborted = false; + + protected abstract boolean processEvent(Event.Base bevent) throws IOException; + protected void postExecute() {} + + /* + * Customization dialog. + */ + + /** + * Special annotation to specify when the customization is necessary. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + protected @interface CallWhen { + /** + * @return A condition that determines if the customization should run. + */ + String[] value(); + } + /** + * A set of conditions under which the conditional customizations should run. + */ + private Set<String> _customizationConditions = new HashSet<>(); + /** + * Customization tick. + */ + private int _customizationIndex = 0; + /** + * Customization aborting. + */ + private boolean _customizationShouldAbort = false; + private List<Callable<Boolean>> _customizationDialog = new ArrayList<>(); + /** + * Execute the next customization vector entry. + */ + private void _nextCustomizationEntry() throws Exception { + try { + if (_customizationShouldAbort) { + _parser.cliAbort(); + } + else { + boolean skip = false; + Callable<Boolean> customizationStep = _customizationDialog.get(_customizationIndex); + Method callMethod = customizationStep.getClass().getDeclaredMethod("call"); + if (callMethod != null) { + CallWhen ann = callMethod.getAnnotation(CallWhen.class); + skip = ann != null && !_customizationConditions.containsAll(Arrays.asList(ann.value())); + } + + if (skip) { + _customizationIndex++; + _parser.cliNoop(); + } + else { + if (customizationStep.call()) { + _customizationIndex++; + } + } + } + } + catch (ArrayIndexOutOfBoundsException e) { + throw new RuntimeException("Protocol violation", e); + } + catch (SoftError e) { + log.error( + "Soft error during host {} customization dialog: {}", + _vds.getHostName(), + e.getMessage() + ); + log.debug("Exception", e); + _failException = e; + _customizationShouldAbort = true; + } + } + protected void addCustomizationDialog(List<Callable<Boolean>> dialog) { + _customizationDialog.addAll(dialog); + } + protected void addCustomizationCondition(String cond) { + _customizationConditions.add(cond); + } + + /* + * Termination dialog. + */ + + /** + * Termination dialog tick. + */ + private int _terminationIndex = 0; + private final List<Callable<Boolean>> _terminationDialog = new ArrayList<>(); + /** + * Execute the next termination vector entry. + */ + private void _nextTerminationEntry() throws Exception { + try { + if (_terminationDialog.get(_terminationIndex).call()) { + _terminationIndex++; + } + } + catch (ArrayIndexOutOfBoundsException e) { + throw new RuntimeException("Protocol violation", e); + } + } + protected void addTerminationDialog(List<Callable<Boolean>> dialog) { + _terminationDialog.addAll(dialog); + } + + /** + * Dialog implementation. + * Handle events incoming from host. + */ + private void _threadMain() { + try { + boolean terminate = false; + + while(!terminate) { + Event.Base bevent = _parser.nextEvent(); + + log.debug( + "Installation of {}: Event {}", + _vds.getHostName(), + bevent + ); + + boolean unknown = true; + if (bevent instanceof Event.Terminate) { + terminate = true; + unknown = false; + } + else if (bevent instanceof Event.Log) { + Event.Log event = (Event.Log)bevent; + InstallerMessages.Severity severity; + switch (event.severity) { + case INFO: + severity = InstallerMessages.Severity.INFO; + break; + case WARNING: + severity = InstallerMessages.Severity.WARNING; + break; + default: + severity = InstallerMessages.Severity.ERROR; + break; + } + _messages.post(severity, event.record); + unknown = false; + } + else if (bevent instanceof Event.QueryString) { + Event.QueryString event = (Event.QueryString)bevent; + + if (Queries.CUSTOMIZATION_COMMAND.equals(event.name)) { + _nextCustomizationEntry(); + unknown = false; + } + else if (Queries.TERMINATION_COMMAND.equals(event.name)) { + _nextTerminationEntry(); + unknown = false; + } + } + else if (bevent instanceof Event.QueryValue) { + Event.QueryValue event = (Event.QueryValue)bevent; + + if (Queries.TIME.equals(event.name)) { + _messages.post( + InstallerMessages.Severity.INFO, + "Setting time" + ); + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssZ"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + event.value = format.format(Calendar.getInstance().getTime()); + unknown = false; + } + } + if (unknown) { + unknown = processEvent(bevent); + } + + if (bevent instanceof Event.Confirm) { + Event.Confirm event = (Event.Confirm)bevent; + if (unknown) { + log.warn( + "Installation of {}: Not confirming {}: {}", + _vds.getHostName(), + event.what, + event.description + ); + } + _parser.sendResponse(event); + } + else if (bevent instanceof Event.QueryValue) { + Event.QueryValue event = (Event.QueryValue)bevent; + if (unknown) { + event.abort = true; + } + _parser.sendResponse(event); + } + else if (bevent instanceof Event.QueryMultiString) { + Event.QueryMultiString event = (Event.QueryMultiString)bevent; + if (unknown) { + event.abort = true; + } + _parser.sendResponse(event); + } + + if (unknown) { + throw new SoftError( + String.format( + "Unexpected event '%1$s'", + bevent + ) + ); + } + } + } + catch (Exception e) { + _failException = e; + log.error("Error during deploy dialog", e); + try { + _control.close(); + } + catch (IOException ee) { + log.error("Error during close", e); + } + } + } + + /* + * Constructor. + * @param vds vds to install. + */ + public VdsDeployBase(String logPrefix, String entryPoint, VDS vds) { + _logPrefix = logPrefix; + _entryPoint = entryPoint; + _vds = vds; + + _messages = new InstallerMessages(_vds); + _dialog = new EngineSSHDialog(); + _parser = new MachineDialogParser(); + _thread = new Thread( + new Runnable() { + @Override + public void run() { + _threadMain(); + } + }, + "VdsDeploy" + ); + + if (s_deployPackage == null) { + s_deployPackage = new CachedTar( + new File( + EngineLocalConfig.getInstance().getCacheDir(), + Config.<String> getValue(ConfigValues.BootstrapPackageName) + ), + new File(Config.<String> getValue(ConfigValues.BootstrapPackageDirectory)) + ); + } + } + + /** + * Destructor. + */ + @Override + protected void finalize() { + try { + close(); + } + catch (IOException e) { + log.error("Exception during finalize", e); + } + } + + /** + * Release resources. + */ + public void close() throws IOException { + stop(); + if (_dialog != null) { + _dialog.close(); + _dialog = null; + } + } + + public void setCorrelationId(String correlationId) { + _correlationId = correlationId; + _messages.setCorrelationId(_correlationId); + } + + /** + * Set user. + * @param user user. + */ + public void setUser(String user) { + _dialog.setUser(user); + } + + /** + * Set key pair. + * @param keyPair key pair. + */ + public void setKeyPair(KeyPair keyPair) { + _dialog.setKeyPair(keyPair); + } + + /** + * Use engine default key pairs. + */ + public void useDefaultKeyPair() throws KeyStoreException { + _dialog.useDefaultKeyPair(); + } + + /** + * Set password. + * @param password password. + */ + public void setPassword(String password) { + _dialog.setPassword(password); + } + + /** + * Main method. + * Execute the command and initiate the dialog. + */ + public void execute() throws Exception { + try { + _dialog.setVds(_vds); + _dialog.connect(); + _messages.post( + InstallerMessages.Severity.INFO, + String.format( + "Connected to host %1$s with SSH key fingerprint: %2$s", + _vds.getHostName(), + _dialog.getHostFingerprint() + ) + ); + _dialog.authenticate(); + + String command = Config.<String> getValue(ConfigValues.BootstrapCommand).replace( + BOOTSTRAP_ENTRY_PLACE_HOLDER, + _entryPoint + ).replace( + // in future we should set here LANG, LC_ALL + BOOTSTRAP_CUSTOM_ENVIRONMENT_PLACE_HOLDER, + "" + ); + + log.info( + "Installation of {}. Executing command via SSH {} < {}", + _vds.getHostName(), + command, + s_deployPackage.getFileNoUse() + ); + + try (final InputStream in = new FileInputStream(s_deployPackage.getFile())) { + _dialog.executeCommand( + this, + command, + new InputStream[] {in} + ); + } + + if (_failException != null) { + throw _failException; + } + + if (_resultError) { + // This is unlikeley as the ssh command will exit with failure. + throw new RuntimeException( + "Installation failed, please refer to installation logs" + ); + } + else { + postExecute(); + } + } + catch (TimeLimitExceededException e){ + log.error( + "Timeout during host {} install", + _vds.getHostName(), + e + ); + _messages.post( + InstallerMessages.Severity.ERROR, + "Processing stopped due to timeout" + ); + throw e; + } + catch(Exception e) { + log.error( + "Error during host {} install", + _vds.getHostName(), + e + ); + if (_failException == null) { + throw e; + } + else { + _messages.post( + InstallerMessages.Severity.ERROR, + e.getMessage() + ); + + log.error( + "Error during host {} install, prefering first exception: {}", + _vds.getHostName(), + _failException.getMessage() + ); + log.debug("Exception", _failException); + throw _failException; + } + } + } + + /* + * SSHDialog.Sink + */ + + @Override + public void setControl(SSHDialog.Control control) { + _control = control; + } + + @Override + public void setStreams(InputStream incoming, OutputStream outgoing) { + _parser.setStreams(incoming, outgoing); + } + + @Override + public void start() { + _thread.start(); + } + + @Override + public void stop() { + if (_thread != null) { + /* + * We cannot just interrupt the thread as the + * implementation of jboss connection pooling + * drops the connection when interrupted. + * As we may have log events pending to be written + * to database, we wait for some time for thread + * complete before interrupting. + */ + try { + _thread.join(THREAD_JOIN_TIMEOUT); + } + catch (InterruptedException e) { + log.error("interrupted", e); + } + if (_thread.isAlive()) { + _thread.interrupt(); + while(true) { + try { + _thread.join(); + break; + } + catch (InterruptedException e) {} + } + } + _thread = null; + } + } +} diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java index fbea703..a09aa40 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java @@ -1179,7 +1179,7 @@ "MYTMP=\"$(TMPDIR=\"${OVIRT_TMPDIR}\" mktemp -d -t ovirt-XXXXXXXXXX)\"; " + "trap \"chmod -R u+rwX \\\"${MYTMP}\\\" > /dev/null 2>&1; rm -fr \\\"${MYTMP}\\\" > /dev/null 2>&1\" 0; " + "tar --warning=no-timestamp -C \"${MYTMP}\" -x && " + - "@ENVIRONMENT@ \"${MYTMP}\"/setup DIALOG/dialect=str:machine DIALOG/customization=bool:True" + "@ENVIRONMENT@ \"${MYTMP}\"/@ENTRY@ DIALOG/dialect=str:machine DIALOG/customization=bool:True" ) BootstrapCommand, -- To view, visit https://gerrit.ovirt.org/39964 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If6055fc395765f054634afe2b06fd56158217154 Gerrit-PatchSet: 6 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Alon Bar-Lev <alo...@redhat.com> Gerrit-Reviewer: Jenkins CI Gerrit-Reviewer: Moti Asayag <masa...@redhat.com> Gerrit-Reviewer: automat...@ovirt.org _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches