Alon Bar-Lev has uploaded a new change for review.
Change subject: bootstrap: detach OVirtUpgrader from VdsInstaller into
OVirtNodeUpgrade
......................................................................
bootstrap: detach OVirtUpgrader from VdsInstaller into OVirtNodeUpgrade
Currently we have 4 separate bootstrap methods:
1. Host.
2. Node registration.
3. Node installation.
4. Node upgrade.
While there is something common between (1-3), node upgrade is totally
different sequence:
1. Unlike bootstrap it is executing foreign code (not originated at
engine machine.
2. Unlike bootstrap it is non-customizable.
Current implementation shares the same VdsInstaller base class among
bootstrap and node upgrade, as these are totally different, we detach
the OVirtUpgrader from VdsInstaller.
VdsInstaller is soon to be retired.
Rename OVirtUpgrader to OVirtNodeUpgrade as it handles only ovirt-node.
This change also introduces the SSHDialog class, which is a class that
can be used to conduct a text dialog with a component at the other end
of the ssh session. It is a key component in the future vdsm-bootstrap
rewrite.
Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=875529
Change-Id: Iff19fdb9f717d424f23bc5d4e5a8df8fce8a58bf
Signed-off-by: Alon Bar-Lev <[email protected]>
---
M
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallVdsCommand.java
M
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallerMessages.java
A
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtNodeUpgrade.java
D
backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtUpgrader.java
A
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/hostinstall/SSHDialog.java
A
backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/hostinstall/SSHDialogTest.java
6 files changed, 1,048 insertions(+), 97 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/74/9174/1
diff --git
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallVdsCommand.java
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallVdsCommand.java
index a556caf..9a20ffb 100644
---
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallVdsCommand.java
+++
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallVdsCommand.java
@@ -81,16 +81,12 @@
getVds() != null &&
isOvirtReInstallOrUpgrade()
) {
- VdsInstaller vdsInstaller = null;
+ OVirtNodeUpgrade upgrade = null;
try {
T parameters = getParameters();
- vdsInstaller = new OVirtUpgrader(getVds(),
parameters.getoVirtIsoFile());
- Backend.getInstance().getResourceManager().RunVdsCommand(
- VDSCommandType.SetVdsStatus,
- new SetVdsStatusVDSCommandParameters(
- getVdsId(),
- VDSStatus.Installing
- )
+ upgrade = new OVirtNodeUpgrade(
+ getVds(),
+ parameters.getoVirtIsoFile()
);
log.infoFormat(
"Execute upgrade {0} host {0}, {1}",
@@ -98,9 +94,7 @@
getVds().getId(),
getVds().getvds_name()
);
- if (!vdsInstaller.Install()) {
- throw new Exception("Upgrade failed");
- }
+ upgrade.execute();
log.infoFormat(
"After upgrade {0}, host {0}, {1}: success",
Thread.currentThread().getName(),
@@ -108,8 +102,9 @@
getVds().getvds_name()
);
setSucceeded(true);
- setHostStatus(VDSStatus.Reboot);
- RunSleepOnReboot();
+ if (getVds().getstatus() == VDSStatus.Reboot) {
+ RunSleepOnReboot();
+ }
}
catch (Exception e) {
log.errorFormat(
@@ -119,9 +114,13 @@
e
);
setSucceeded(false);
- _failureMessage =
getErrorMessage(vdsInstaller.getErrorMessage());
+ _failureMessage = getErrorMessage(e.getMessage());
AddCustomValue("FailedInstallMessage", _failureMessage);
- setHostStatus(VDSStatus.InstallFailed);
+ }
+ finally {
+ if (upgrade != null) {
+ upgrade.close();
+ }
}
return;
}
diff --git
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallerMessages.java
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallerMessages.java
index 388588a..7d1551a 100644
---
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallerMessages.java
+++
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/InstallerMessages.java
@@ -2,7 +2,7 @@
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.AuditLogType;
-import org.ovirt.engine.core.compat.Guid;
+import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.compat.backendcompat.XmlDocument;
import org.ovirt.engine.core.compat.backendcompat.XmlNode;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
@@ -11,13 +11,44 @@
import org.ovirt.engine.core.utils.log.LogFactory;
public class InstallerMessages {
- private Guid _vdsId;
+ private VDS _vds;
+ private int _sequence = 0;
- public InstallerMessages(Guid vdsId) {
- _vdsId = vdsId;
+ public enum Severity {
+ INFO,
+ WARNING,
+ ERROR
+ };
+
+ public InstallerMessages(VDS vds) {
+ _vds = vds;
}
- public boolean AddMessage(String message) {
+ public void post(Severity severity, String text) {
+ AuditLogType logType;
+ AuditLogableBase logable = new AuditLogableBase(_vds.getId());
+ logable.AddCustomValue("Message", text);
+ switch (severity) {
+ case INFO:
+ logType = AuditLogType.VDS_INSTALL_IN_PROGRESS;
+ log.infoFormat("Installation {0}: {1}", _vds.gethost_name(),
text);
+ break;
+ default:
+ case WARNING:
+ logable.setCustomId(_sequence++);
+ logType = AuditLogType.VDS_INSTALL_IN_PROGRESS_WARNING;
+ log.warnFormat("Installation {0}: {1}", _vds.gethost_name(),
text);
+ break;
+ case ERROR:
+ logable.setCustomId(_sequence++);
+ logType = AuditLogType.VDS_INSTALL_IN_PROGRESS_ERROR;
+ log.errorFormat("Installation {0}: {1}", _vds.gethost_name(),
text);
+ break;
+ }
+ AuditLogDirector.log(logable, logType);
+ }
+
+ public boolean postOldXmlFormat(String message) {
boolean error = false;
if (StringUtils.isEmpty(message)) {
return error;
@@ -25,7 +56,7 @@
String[] msgs = message.split("[\\n]", -1);
if (msgs.length > 1) {
for (String msg : msgs) {
- error = AddMessage(msg) || error;
+ error = postOldXmlFormat(msg) || error;
}
return error;
}
@@ -33,7 +64,7 @@
if (StringUtils.isNotEmpty(message)) {
if (message.charAt(0) == '<') {
try {
- error = parseMessage(message);
+ error = _postOldXmlFormat(message);
} catch (RuntimeException e) {
log.errorFormat(
"Installation of Host. Received illegal XML from
Host. Message: {1}, Exception: {2}",
@@ -46,7 +77,7 @@
return error;
}
- private boolean parseMessage(String message) {
+ private boolean _postOldXmlFormat(String message) {
boolean error = false;
XmlDocument doc = new XmlDocument();
doc.LoadXml(message);
@@ -54,16 +85,16 @@
if (node != null) {
StringBuilder sb = new StringBuilder();
// check status
- AuditLogType logType;
+ Severity severity;
if (node.Attributes.get("status") == null) {
- logType = AuditLogType.VDS_INSTALL_IN_PROGRESS_WARNING;
+ severity = Severity.WARNING;
} else if (node.Attributes.get("status").getValue().equals("OK")) {
- logType = AuditLogType.VDS_INSTALL_IN_PROGRESS;
+ severity = Severity.INFO;
} else if
(node.Attributes.get("status").getValue().equals("WARN")) {
- logType = AuditLogType.VDS_INSTALL_IN_PROGRESS_WARNING;
+ severity = Severity.WARNING;
} else {
error = true;
- logType = AuditLogType.VDS_INSTALL_IN_PROGRESS_ERROR;
+ severity = Severity.ERROR;
}
if ((node.Attributes.get("component") != null)
@@ -82,9 +113,7 @@
&&
(StringUtils.isNotEmpty(node.Attributes.get("result").getValue()))) {
sb.append(" (" + node.Attributes.get("result").getValue() +
")");
}
- AuditLogableBase logable = new AuditLogableBase(_vdsId);
- logable.AddCustomValue("Message",
StringUtils.stripEnd(sb.toString(), " "));
- AuditLogDirector.log(logable, logType);
+ post(severity, sb.toString());
return error;
}
diff --git
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtNodeUpgrade.java
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtNodeUpgrade.java
new file mode 100644
index 0000000..699ef22
--- /dev/null
+++
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtNodeUpgrade.java
@@ -0,0 +1,225 @@
+package org.ovirt.engine.core.bll;
+
+import java.io.BufferedReader;
+import java.io.PrintWriter;
+
+import org.ovirt.engine.core.common.businessentities.VDS;
+import org.ovirt.engine.core.common.businessentities.VDSStatus;
+import org.ovirt.engine.core.common.config.Config;
+import org.ovirt.engine.core.common.config.ConfigValues;
+import org.ovirt.engine.core.compat.backendcompat.Path;
+import org.ovirt.engine.core.dal.dbbroker.DbFacade;
+import org.ovirt.engine.core.utils.hostinstall.SSHDialog;
+import org.ovirt.engine.core.utils.log.Log;
+import org.ovirt.engine.core.utils.log.LogFactory;
+import org.ovirt.engine.core.utils.transaction.TransactionMethod;
+import org.ovirt.engine.core.utils.transaction.TransactionSupport;
+
+/**
+ * ovirt-node upgrade.
+ */
+public class OVirtNodeUpgrade implements SSHDialog.Sink, Runnable {
+
+ private static Log log = LogFactory.getLog(OVirtNodeUpgrade.class);
+
+ private SSHDialog.Control _control;
+ private Thread _thread;
+ private SSHDialog _dialog;
+ private final InstallerMessages _messages;
+
+ private BufferedReader _incoming;
+
+ private VDS _vds;
+ private String _iso;
+
+ private Exception _failException = null;
+
+ /**
+ * Set vds object status.
+ * @param status new status.
+ *
+ * For this simple task, no need to go via command mechanism.
+ */
+ private void _setVdsStatus(VDSStatus status) {
+ _vds.setstatus(status);
+
+ TransactionSupport.executeInNewTransaction(new
TransactionMethod<Void>() {
+ @Override
+ public Void runInTransaction() {
+
DbFacade.getInstance().getVdsDynamicDao().update(_vds.getDynamicData());
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Constructor.
+ * @param vds vds to install.
+ * @param iso image to send.
+ */
+ public OVirtNodeUpgrade(VDS vds, String iso) {
+ _vds = vds;
+ _iso = Path.Combine(Config.resolveOVirtISOsRepositoryPath(), iso);
+
+ _messages = new InstallerMessages(_vds);
+ _thread = new Thread(this);
+ _dialog = new SSHDialog();
+ }
+
+ /**
+ * Destructor.
+ */
+ @Override
+ public void finalize() {
+ close();
+ }
+
+ /**
+ * Free resources.
+ */
+ public void close() {
+ stop();
+ if (_dialog != null) {
+ _dialog.disconnect();
+ _dialog = null;
+ }
+ }
+
+ /**
+ * Main function.
+ * Execute the command and initiate the dialog.
+ */
+ public void execute() throws Exception {
+ try {
+ _setVdsStatus(VDSStatus.Installing);
+
+ _dialog.useDefaultKeyPair();
+ _dialog.setHost(_vds.gethost_name());
+ _dialog.connect();
+ _messages.post(
+ InstallerMessages.Severity.INFO,
+ String.format(
+ "Connected to host %1$s with SSH key fingerprint: %2$s",
+ _vds.gethost_name(),
+ _dialog.getHostFingerprint()
+ )
+ );
+ _dialog.authenticate();
+
+ String dest = Config.<String>
GetValue(ConfigValues.oVirtUploadPath);
+
+ _messages.post(
+ InstallerMessages.Severity.INFO,
+ String.format(
+ "Sending file %1$s to %2$s",
+ _iso,
+ dest
+ )
+ );
+
+ _dialog.sendFile(
+ _iso,
+ dest
+ );
+
+ String command = Config.<String>
GetValue(ConfigValues.oVirtUpgradeScriptName);
+
+ _messages.post(
+ InstallerMessages.Severity.INFO,
+ String.format(
+ "Executing %1$s",
+ command
+ )
+ );
+
+ _dialog.executeCommand(
+ this,
+ command,
+ null
+ );
+
+ if (_failException != null) {
+ throw _failException;
+ }
+
+ _setVdsStatus(VDSStatus.Reboot);
+ }
+ catch (Exception e) {
+ log.errorFormat("Error during node {0} upgrade",
_vds.gethost_name(), e);
+ _setVdsStatus(VDSStatus.InstallFailed);
+
+ if (_failException == null) {
+ throw e;
+ }
+ else {
+ log.errorFormat(
+ "Error during node {0} upgrade, prefering first exception",
+ _vds.gethost_name(),
+ _failException
+ );
+ throw _failException;
+ }
+ }
+ }
+
+ /**
+ * Dialog implementation.
+ * Handle events incoming from host.
+ */
+ @Override
+ public void run() {
+ try {
+ String line;
+ while (
+ _incoming != null &&
+ (line = _incoming.readLine()) != null
+ ) {
+ log.infoFormat("update from host {0}: {1}",
_vds.gethost_name(), line);
+ if (_messages.postOldXmlFormat(line)) {
+ throw new RuntimeException(
+ "Error during upgrade, please refer to logs"
+ );
+ }
+ }
+ }
+ catch (Exception e) {
+ _failException = e;
+ log.error("Error during upgrade", e);
+ _control.disconnect();
+ }
+ }
+
+ /*
+ * SSHDialog.Sink
+ */
+
+ @Override
+ public void setControl(SSHDialog.Control control) {
+ _control = control;
+ }
+
+ @Override
+ public void setStreams(BufferedReader incoming, PrintWriter outgoing) {
+ _incoming = incoming;
+ }
+
+ @Override
+ public void start() {
+ _thread.start();
+ }
+
+ @Override
+ public void stop() {
+ if (_thread != null) {
+ _thread.interrupt();
+ while(true) {
+ try {
+ _thread.join();
+ break;
+ }
+ catch (InterruptedException e) {}
+ }
+ _thread = null;
+ }
+ }
+}
diff --git
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtUpgrader.java
b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtUpgrader.java
deleted file mode 100644
index fa2161f..0000000
---
a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/OVirtUpgrader.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.ovirt.engine.core.bll;
-
-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.log.Log;
-import org.ovirt.engine.core.utils.log.LogFactory;
-import org.ovirt.engine.core.compat.backendcompat.Path;
-
-public class OVirtUpgrader extends VdsInstaller {
-
- private final String runUpgradeCommand = Config.<String>
GetValue(ConfigValues.oVirtUpgradeScriptName);
- private String _oVirtISOFile;
-
- public OVirtUpgrader(VDS vds, String oVirtISOFile) // Call base constructor
- // with null password
- // (because we are
using
- // know public key)
- {
- super(vds, null, false);
- _oVirtISOFile = oVirtISOFile;
- _translatedMessages.put(VdsInstallStages.UploadScript, "Upload upgrade
ISO to oVirt Node");
- _translatedMessages.put(VdsInstallStages.RunScript, "Running upgrade /
reinstall script on oVirt Node");
-
- }
-
- @Override
- protected void RunStage() {
- switch (_currentInstallStage) {
- case Start: {
- log.infoFormat("Installation of {0}. Executing oVirt
reinstall/upgrade stage. (Stage: {1})", _serverName,
- getCurrentInstallStage());
- super.RunStage();
- break;
- }
- // Use connect method which does not need password
- // (relies on public key existing on cbc image)
- case ConnectToServer: {
- log.infoFormat("Installation of {0}. Executing oVirt
reinstall/upgrade stage. (Stage: {1})", _serverName,
- getCurrentInstallStage());
- _executionSucceded = _wrapper.connect(_serverName);
- break;
- }
- case UploadScript:
- log.infoFormat("Installation of {0}. Executing oVirt
reinstall/upgrade stage. (Stage: {1})", _serverName,
- getCurrentInstallStage());
- String path =
Path.Combine(Config.resolveOVirtISOsRepositoryPath(), _oVirtISOFile);
- _executionSucceded = _wrapper.sendFile(path, Config.<String>
GetValue(ConfigValues.oVirtUploadPath));
- break;
-
- case RunScript:
- log.infoFormat("Installation of {0}. Executing oVirt
reinstall/upgrade stage. (Stage: {1})", _serverName,
- getCurrentInstallStage());
- _wrapper.executeCommand(runUpgradeCommand);
- break;
- // Skip unused states
- default: {
- _executionSucceded = true;
- _currentInstallStage =
VdsInstallStages.forValue(_currentInstallStage.getValue() + 1);
- break;
- }
- }
- }
-
- private static Log log = LogFactory.getLog(OVirtUpgrader.class);
-}
diff --git
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/hostinstall/SSHDialog.java
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/hostinstall/SSHDialog.java
new file mode 100644
index 0000000..5d3975b
--- /dev/null
+++
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/hostinstall/SSHDialog.java
@@ -0,0 +1,363 @@
+package org.ovirt.engine.core.utils.hostinstall;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintWriter;
+import java.io.SequenceInputStream;
+import java.nio.charset.Charset;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.ovirt.engine.core.common.config.Config;
+import org.ovirt.engine.core.common.config.ConfigValues;
+
+import org.ovirt.engine.core.engineencryptutils.OpenSSHUtils;
+import org.ovirt.engine.core.utils.ssh.ConstraintByteArrayOutputStream;
+import org.ovirt.engine.core.utils.ssh.SSHClient;
+
+public class SSHDialog {
+
+ private static final int BUFFER_SIZE = 10 * 1024;
+ private static final int DEFAULT_SSH_PORT = 22;
+
+ public interface Control {
+ public void disconnect();
+ }
+
+ public interface Sink {
+ public void setControl(SSHDialog.Control control);
+ public void setStreams(BufferedReader incoming, PrintWriter outgoing);
+ public void start();
+ public void stop();
+ }
+
+ private static final Log log = LogFactory.getLog(SSHDialog.class);
+
+ private String _host;
+ private int _port;
+ private String _user = "root";
+ private KeyPair _keyPair;
+ private String _password;
+ private long _softTimeout;
+ private long _hardTimeout;
+
+ private SSHClient _client;
+ private String _hostFingerprint;
+
+ /**
+ * Get SSH Client.
+ * @internal
+ * Used for mocking.
+ */
+ SSHClient _getSSHClient() {
+ return new SSHClient();
+ }
+
+ public SSHDialog() {
+ _hardTimeout = Config.<Integer>GetValue(
+ ConfigValues.SSHInactivityHardTimoutSeconds
+ ) * 1000;
+ _softTimeout = Config.<Integer>GetValue(
+ ConfigValues.SSHInactivityTimoutSeconds
+ ) * 1000;
+ }
+
+ @Override
+ public void finalize() {
+ disconnect();
+ }
+
+ public PublicKey getPublicKey() {
+ if (_keyPair == null) {
+ return null;
+ }
+ else {
+ return _keyPair.getPublic();
+ }
+ }
+
+ public String getHostFingerprint() {
+ return _hostFingerprint;
+ }
+
+ public void setHost(String host, int port) {
+ _host = host;
+ _port = port;
+ }
+
+ public void setHost(String host) {
+ setHost(host, DEFAULT_SSH_PORT);
+ }
+
+ public void setPort(int port) {
+ _port = port;
+ }
+
+ public void setUser(String user) {
+ _user = user;
+ }
+
+ public void setPassword(String password) {
+ _password = password;
+ }
+
+ public void setKeyPair(KeyPair keyPair) {
+ _keyPair = keyPair;
+ }
+
+ public void setSoftTimeout(long timeout) {
+ _softTimeout = timeout;
+ }
+
+ public void setHardTimeout(long timeout) {
+ _hardTimeout = timeout;
+ }
+
+ public void useDefaultKeyPair() throws KeyStoreException {
+ String alias = Config.<String>GetValue(ConfigValues.CertAlias);
+ String p12 = Config.<String>GetValue(ConfigValues.keystoreUrl);
+ String password = Config.<String>GetValue(ConfigValues.keystorePass);
+
+ KeyStore.PrivateKeyEntry entry;
+ InputStream in = null;
+ try {
+ in = new FileInputStream(p12);
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(in, password.toCharArray());
+
+ entry = (KeyStore.PrivateKeyEntry)ks.getEntry(
+ alias,
+ new KeyStore.PasswordProtection(
+ password.toCharArray()
+ )
+ );
+ }
+ catch (Exception e) {
+ throw new KeyStoreException(
+ String.format(
+ "Failed to get certificate entry from key store:
%1$s/%2$s",
+ p12,
+ alias
+ ),
+ e
+ );
+ }
+ finally {
+ if (in != null) {
+ try {
+ in.close();
+ }
+ catch(IOException e) {
+ log.error("Cannot close key store", e);
+ }
+ }
+ }
+
+ if (entry == null) {
+ throw new KeyStoreException(
+ String.format(
+ "Bad key store: %1$s/%2$s",
+ p12,
+ alias
+ )
+ );
+ }
+
+ _keyPair = new KeyPair(
+ entry.getCertificate().getPublicKey(),
+ entry.getPrivateKey()
+ );
+ }
+
+ public void disconnect() {
+ if (_client != null) {
+ _client.disconnect();
+ _client = null;
+ }
+ }
+
+ public void connect() throws Exception {
+ log.debug(
+ String.format(
+ "connect enter (%1$s:%2$s, %3$d, %4$d)",
+ _host,
+ _port,
+ _hardTimeout,
+ _softTimeout
+ )
+ );
+
+ try {
+ if (_client != null) {
+ throw new IOException("Already connected");
+ }
+
+ _hostFingerprint = null;
+
+ _client = _getSSHClient();
+ _client.setHardTimeout(_hardTimeout);
+ _client.setSoftTimeout(_softTimeout);
+ _client.setHost(_host, _port);
+
+ log.debug("connecting");
+ _client.connect();
+
+ PublicKey serverKey = _client.getServerKey();
+
+ if (serverKey == null) {
+ throw new IOException("Unable to retrieve host key");
+ }
+ String fingerprint =
OpenSSHUtils.getKeyFingerprintString(serverKey);
+
+ if (fingerprint == null) {
+ throw new IOException("Unable to parse host key");
+ }
+
+ _hostFingerprint = fingerprint;
+ }
+ catch(Exception e) {
+ log.debug(
+ String.format(
+ "Could not connect to server %1$s",
+ _client.getDisplayHost()
+ ),
+ e
+ );
+ throw e;
+ }
+
+ log.debug(String.format("connect return fingerprint=%1$s",
_hostFingerprint));
+ }
+
+ public void authenticate() throws Exception {
+ _client.setUser(_user);
+ _client.setPassword(_password);
+ _client.setKeyPair(_keyPair);
+ _client.authenticate();
+ }
+
+ public void executeCommand(
+ Sink sink,
+ String command,
+ InputStream initial[]
+ ) throws Exception {
+
+ log.info(String.format("SSH execute %1$s '%2$s'",
_client.getDisplayHost(), command));
+
+ final PipedInputStream pinStdin = new PipedInputStream(BUFFER_SIZE);
+ final OutputStream poutStdin = new PipedOutputStream(pinStdin);
+ final PipedInputStream pinStdout = new PipedInputStream(BUFFER_SIZE);
+ final OutputStream poutStdout = new PipedOutputStream(pinStdout);
+ final ByteArrayOutputStream stderr = new
ConstraintByteArrayOutputStream(1024);
+ final PrintWriter writerStdin = new PrintWriter(
+ new OutputStreamWriter(
+ poutStdin,
+ Charset.forName("UTF-8")
+ ),
+ true
+ );
+ final BufferedReader readerStdout = new BufferedReader(
+ new InputStreamReader(
+ pinStdout,
+ Charset.forName("UTF-8")
+ ),
+ BUFFER_SIZE
+ );
+
+ List<InputStream> stdinList;
+ if (initial == null) {
+ stdinList = new LinkedList<InputStream>();
+ }
+ else {
+ stdinList = new LinkedList<InputStream>(Arrays.asList(initial));
+ }
+ stdinList.add(pinStdin);
+
+ try {
+ sink.setControl(
+ new Control() {
+ @Override
+ public void disconnect() {
+ if (_client != null) {
+ _client.disconnect();
+ }
+ }
+ }
+ );
+ sink.setStreams(readerStdout, writerStdin);
+ sink.start();
+
+ try {
+ _client.executeCommand(
+ command,
+ new
SequenceInputStream(Collections.enumeration(stdinList)),
+ poutStdout,
+ stderr
+ );
+ if (stderr.size() > 0) {
+ throw new IOException("Error messages during execution");
+ }
+ }
+ finally {
+ if (stderr.size() > 0) {
+ log.error(
+ String.format(
+ "SSH stderr during command %1$s:'%2$s': stderr:
%3$s",
+ _client.getDisplayHost(),
+ command,
+ new String(stderr.toByteArray(), "UTF-8")
+ )
+ );
+ }
+ }
+ }
+ catch (Exception e) {
+ log.error(
+ String.format(
+ "SSH error running command %1$s:'%2$s'",
+ _client.getDisplayHost(),
+ command
+ ),
+ e
+ );
+ throw e;
+ }
+ finally {
+ sink.stop();
+ sink.setStreams(null, null);
+ }
+
+ log.debug("execute leave");
+ }
+
+ public void sendFile(
+ String file1,
+ String file2
+ ) throws Exception {
+ _client.sendFile(file1, file2);
+ }
+
+ public void receiveFile(
+ String file1,
+ String file2
+ ) throws Exception {
+ _client.receiveFile(file1, file2);
+ }
+}
diff --git
a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/hostinstall/SSHDialogTest.java
b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/hostinstall/SSHDialogTest.java
new file mode 100644
index 0000000..e36cbd3
--- /dev/null
+++
b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/hostinstall/SSHDialogTest.java
@@ -0,0 +1,401 @@
+package org.ovirt.engine.core.utils.hostinstall;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.BufferedReader;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import javax.naming.AuthenticationException;
+import javax.naming.TimeLimitExceededException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.commons.lang.SystemUtils;
+
+import org.ovirt.engine.core.common.config.Config;
+import org.ovirt.engine.core.common.config.IConfigUtilsInterface;
+import org.ovirt.engine.core.engineencryptutils.OpenSSHUtils;
+import org.ovirt.engine.core.utils.ssh.SSHD;
+
+/*
+ * Test properties
+ * $ mvn -Dssh-host=host1 -Dssh-test-port=22 -Dssh-test-user=root
-Dssh-test-password=password -Dssh-test-p12=a.p12 -Dssh-test-p12-alias=alias
-Dssh-test-p12-password=password
+ */
+
+public class SSHDialogTest {
+
+ private class Sink
+ implements Runnable, SSHDialog.Sink {
+
+ private SSHDialog.Control _control;
+ private BufferedReader _incoming;
+ private PrintWriter _outgoing;
+ private List<String> _expect;
+ private List<String> _send;
+ private Throwable _throwable;
+ private Thread _thread;
+
+ public Sink(String expect[], String send[]) {
+ _expect = new LinkedList<String>(Arrays.asList(expect));
+ _send = new LinkedList<String>(Arrays.asList(send));
+ _thread = new Thread(this);
+ }
+
+ public void exception() throws Throwable {
+ if (_throwable != null) {
+ throw _throwable;
+ }
+ assertTrue(_expect.size() == 0);
+ assertTrue(_send.size() == 0);
+ }
+
+ @Override
+ public void setControl(SSHDialog.Control control) {
+ _control = control;
+ }
+
+ @Override
+ public void setStreams(BufferedReader incoming, PrintWriter outgoing) {
+ _incoming = incoming;
+ _outgoing = outgoing;
+ }
+
+ @Override
+ public void start() {
+ _thread.start();
+ }
+
+ @Override
+ public void stop() {
+ if (_thread != null) {
+ _thread.interrupt();
+ while(true) {
+ try {
+ _thread.join();
+ break;
+ }
+ catch (InterruptedException e) {}
+ }
+ _thread = null;
+ }
+ }
+
+ public void run() {
+ try {
+ while (_expect.size() > 0) {
+ assertEquals(_expect.remove(0), _incoming.readLine());
+ if (_send.size() > 0) {
+ String tosend = _send.remove(0);
+ if (tosend != null) {
+ for (String s : tosend.split("\n")) {
+ _outgoing.println(s);
+ }
+ }
+ }
+ }
+ }
+ catch (Throwable t) {
+ if (_throwable == null) {
+ _throwable = t;
+ }
+ }
+ finally {
+ _control.disconnect();
+ }
+ }
+ }
+
+ private static String s_host;
+ private static String s_user;
+ private static String s_password;
+ private static KeyPair s_keyPair;
+ private static int s_port;
+
+ private static SSHD s_sshd;
+
+ private SSHDialog _sshdialog;
+
+ private static KeyPair _getKeyPair(String p12, String alias, String
password) throws KeyStoreException {
+
+ KeyStore.PrivateKeyEntry entry;
+ InputStream in = null;
+ try {
+ in = new FileInputStream(p12);
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(in, password.toCharArray());
+
+ entry = (KeyStore.PrivateKeyEntry)ks.getEntry(
+ alias,
+ new KeyStore.PasswordProtection(
+ password.toCharArray()
+ )
+ );
+ }
+ catch (Exception e) {
+ throw new KeyStoreException(
+ String.format(
+ "Failed to get certificate entry from key store:
%1$s/%2$s",
+ p12,
+ alias
+ ),
+ e
+ );
+ }
+ finally {
+ if (in != null) {
+ try {
+ in.close();
+ }
+ catch(IOException e) {}
+ }
+ }
+
+ if (entry == null) {
+ throw new KeyStoreException(
+ String.format(
+ "Bad key store: %1$s/%2$s",
+ p12,
+ alias
+ )
+ );
+ }
+
+ return new KeyPair(
+ entry.getCertificate().getPublicKey(),
+ entry.getPrivateKey()
+ );
+ }
+
+ @BeforeClass
+ public static void init() throws IOException {
+ assumeTrue(SystemUtils.IS_OS_UNIX);
+ IConfigUtilsInterface confInstance = new DefaultValuesConfigUtil();
+ Config.setConfigUtils(confInstance);
+
+ s_host = System.getProperty("ssh-host");
+ if (s_host == null) {
+ s_host = "localhost";
+ s_user = "root";
+ s_password = "password";
+ try {
+ s_keyPair =
KeyPairGenerator.getInstance("RSA").generateKeyPair();
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+
+ s_sshd = new SSHD();
+ s_sshd.setUser(
+ s_user,
+ s_password,
+ s_keyPair.getPublic()
+ );
+ try {
+ s_sshd.start();
+ }
+ catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ s_port = s_sshd.getPort();
+ }
+ else {
+ s_port = Integer.parseInt(System.getProperty("ssh-test-port",
"22"));
+ s_user = System.getProperty("ssh-test-user", "root");
+ s_password = System.getProperty("ssh-test-password", "password");
+ try {
+ s_keyPair = _getKeyPair(
+ System.getProperty("ssh-test-p12",
"src/test/resources/key.p12"),
+ System.getProperty("ssh-test-p12-alias", "1"),
+ System.getProperty("ssh-test-p12-password", "NoSoup4U")
+ );
+ }
+ catch (KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ System.out.println("Key fingerprint is: " +
OpenSSHUtils.getKeyString(s_keyPair.getPublic(), "test"));
+ }
+
+ @AfterClass
+ public static void terminate() throws Exception {
+ if (s_sshd != null) {
+ s_sshd.stop();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ _sshdialog = new SSHDialog();
+ _sshdialog.setHost(s_host, s_port);
+ _sshdialog.setPassword(s_password);
+ _sshdialog.setKeyPair(s_keyPair);
+ _sshdialog.setSoftTimeout(10*1000);
+ _sshdialog.setHardTimeout(30*1000);
+ }
+
+ @After
+ public void tearDown() {
+ if (_sshdialog != null) {
+ _sshdialog.disconnect();
+ _sshdialog = null;
+ }
+ }
+
+ @Test
+ public void testKeyPair() throws Exception {
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ }
+
+ @Test
+ public void testPassword() throws Exception {
+ _sshdialog.setKeyPair(null);
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ }
+
+ @Test(expected=AuthenticationException.class)
+ public void testWrongKeyPair() throws Exception {
+ _sshdialog.setKeyPair(
+ KeyPairGenerator.getInstance("RSA").generateKeyPair()
+ );
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ }
+
+ @Test(expected=AuthenticationException.class)
+ public void testWrongPassword() throws Exception {
+ _sshdialog.setKeyPair(null);
+ _sshdialog.setPassword("bad");
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ }
+
+ @Test
+ public void testSimple() throws Throwable {
+ Sink sink = new Sink(
+ new String[] {
+ "start",
+ "text1",
+ "text2"
+ },
+ new String[] {
+ "text1",
+ "text2"
+ }
+ );
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ _sshdialog.executeCommand(
+ sink,
+ "cat",
+ new InputStream[] {
+ new ByteArrayInputStream("start\n".getBytes("UTF-8"))
+ }
+ );
+ sink.exception();
+ }
+
+ @Test(expected=TimeLimitExceededException.class)
+ public void testTimeout() throws Throwable {
+ Sink sink = new Sink(
+ new String[] {
+ "start"
+ },
+ new String[] {
+ }
+ );
+ _sshdialog.setSoftTimeout(1*1000);
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ _sshdialog.executeCommand(
+ sink,
+ "cat",
+ null
+ );
+ sink.exception();
+ }
+
+ @Test(expected=IOException.class)
+ public void testStderr() throws Throwable {
+ Sink sink = new Sink(
+ new String[] {
+ "start",
+ "text1",
+ "text2"
+ },
+ new String[] {
+ "text1",
+ "text2"
+ }
+ );
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ _sshdialog.executeCommand(
+ sink,
+ "echo message >&2 && cat",
+ new InputStream[] {
+ new ByteArrayInputStream("start\n".getBytes("UTF-8"))
+ }
+ );
+ sink.exception();
+ }
+
+ @Test
+ public void testLong() throws Throwable {
+ final String LINE =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASDSSSSSSSSSSSSSSSSSSSSSSSDDDDDD";
+ final int NUM = 10000;
+ final int FACTOR = 5;
+
+ String longText = "";
+ for (int i=0;i<NUM/FACTOR;i++) {
+ longText += LINE + "\n";
+ }
+
+ List<String> expect = new LinkedList<String>();
+ expect.add("start");
+ for (int i=0;i<NUM;i++) {
+ expect.add(LINE);
+ }
+
+ List<String> send = new LinkedList<String>();
+ for (int i=0;i<NUM;i++) {
+ if (i % (NUM/FACTOR) == 0) {
+ send.add(longText);
+ }
+ else {
+ send.add(null);
+ }
+ }
+
+ Sink sink = new Sink(
+ expect.toArray(new String[0]),
+ send.toArray(new String[0])
+ );
+ _sshdialog.connect();
+ _sshdialog.authenticate();
+ _sshdialog.executeCommand(
+ sink,
+ "echo start && sleep 4 && cat",
+ null
+ );
+ sink.exception();
+ }
+}
--
To view, visit http://gerrit.ovirt.org/9174
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Iff19fdb9f717d424f23bc5d4e5a8df8fce8a58bf
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Alon Bar-Lev <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches